<commit_before>/**
 * @ngdoc service
 * @module superdesk.apps.authoring
 * @name confirm
 *
 * @requires $window
 * @requires $q
 * @requires $filter
 * @requires api
 * @requires modal
 * @requires $interpolate
 * @requires $modal
 *
 * @description Confirm Service is responsible for displaying user prompts for content authoring.
 */
ConfirmDirtyService.$inject = ['$window', '$q', '$filter', 'api', 'modal', 'gettextCatalog',
    '$interpolate', '$modal'];
export function ConfirmDirtyService($window, $q, $filter, api, modal, gettextCatalog, $interpolate, $modal) {
    /**
     * Will ask for user confirmation for user confirmation if there are some changes which are not saved.
     * - Detecting changes via $scope.dirty - it's up to the controller to set it.
     */
    this.setupWindow = function setupWindow($scope) {
        $window.onbeforeunload = function() {
            if ($scope.dirty) {
                return gettextCatalog.getString(
                    'There are unsaved changes. If you navigate away, your changes will ' +
                    'be lost.');
            }

            $scope.$on('$destroy', () => {
                $window.onbeforeunload = angular.noop;
            });
        };
    };

    // Flag to set for instant checking if $scope is dirty for current item
    this.dirty = null;

    /**
     * Called from workqueue in case of unsaved changes.
     */
    this.reopen = function() {
        return modal.confirm(
            gettextCatalog.getString('There are some unsaved changes, go to the article to save changes?'),
            gettextCatalog.getString('Save changes?'),
            gettextCatalog.getString('Go-To'),
            gettextCatalog.getString('Ignore'),
            gettextCatalog.getString('Cancel')
        );
    };

    /**
     * In case $scope is dirty ask user if he want's to loose his changes.
     */
    this.confirm = function confirm() {
        return modal.confirm(
            gettextCatalog.getString('There are some unsaved changes, do you want to save it now?'),
            gettextCatalog.getString('Save changes?'),
            gettextCatalog.getString('Save'),
            gettextCatalog.getString('Ignore'),
            gettextCatalog.getString('Cancel')
        );
    };

    /**
     * In case publish is triggered by quick buttons, show confirmation dialog
     */
    this.confirmQuickPublish = function() {
        return modal.confirm(
            gettextCatalog.getString('Do you want to publish the article?'),
            gettextCatalog.getString('Publish'),
            gettextCatalog.getString('Publish'),
            gettextCatalog.getString('Cancel')
        );
    };

    /**
     * In case $scope is dirty ask user if he want's to save changes and publish.
     */
    this.confirmPublish = function() {
        return modal.confirm(
            gettextCatalog.getString('There are some unsaved changes, do you want to save and publish it now?'),
            gettextCatalog.getString('Save changes?'),
            gettextCatalog.getString('Save and publish'),
            gettextCatalog.getString('Cancel')
        );
    };

    /**
     * In case $scope is dirty ask user if he want's to save changes and publish.
     */
    this.confirmSendTo = function confirmSendTo(action) {
        return modal.confirm(
            $interpolate(gettextCatalog.getString(
                'There are some unsaved changes, do you want to save and send it now?'
            ))({action: action}),
            gettextCatalog.getString('Save changes?'),
            $interpolate(gettextCatalog.getString('Save and send'))({action: action}),
            gettextCatalog.getString('Cancel')
        );
    };

    this.confirmSaveWork = function confirmSavework(msg) {
        return modal.confirm(
            $interpolate(
                gettextCatalog.getString(
                    'Configuration has changed. {{ message }} Would you like to save the story to your workspace?'
                )
            )({message: msg})
        );
    };

    /**
     * Make user aware that an item was unlocked
     *
     * @param {string} userId Id of user who unlocked an item.
     * @param {string} headline Headline of item which is unlocked
     */
    this.unlock = function unlock(userId, headline) {
        api.find('users', userId).then((user) => {
            var itemHeading = headline ? 'Item ' + headline + '' : 'This item';
            var msg = gettext(itemHeading + ' was unlocked by ' + $filter('username')(user) + '.');

            return modal.confirm(msg, gettextCatalog.getString('Item Unlocked'), gettextCatalog.getString('OK'), false);
        });
    };

    /**
     * Make user aware that an item was locked
     *
     * @param {string} userId Id of user who locked an item.
     */
    this.lock = function lock(userId) {
        api.find('users', userId).then((user) => {
            var msg = gettextCatalog.getString('This item was locked by ' + $filter('username')(user) + '.');

            return modal.confirm(msg, gettextCatalog.getString('Item locked'), gettext('OK'), false);
        });
    };

    /**
     * @ngdoc method
     * @name confirm#confirmFeatureMedia
     * @public
     * @returns {promise}
     * @description Prompts the user to add the associated media to the Update.
     * @param {Object} item
     */
    this.confirmFeatureMedia = function(item) {
        let defered = $q.defer();

        $modal.open({
            templateUrl: 'scripts/apps/authoring/views/confirm-media-associated.html',
            controller: ['$scope', '$modalInstance', function($scope, $modalInstance) {
                $scope.item = item;

                $scope.useMedia = function() {
                    defered.resolve($scope.item);
                    $modalInstance.close();
                };

                $scope.cancel = function() {
                    defered.reject(false);
                    $modalInstance.dismiss();
                };

                $scope.publishWithoutMedia = function() {
                    defered.resolve({});
                    $modalInstance.dismiss();
                };
            }]
        });

        return defered.promise;
    };
}
<commit_msg>fix(authoring): fix label for publish confirm modal<commit_after>/**
 * @ngdoc service
 * @module superdesk.apps.authoring
 * @name confirm
 *
 * @requires $window
 * @requires $q
 * @requires $filter
 * @requires api
 * @requires modal
 * @requires $interpolate
 * @requires $modal
 *
 * @description Confirm Service is responsible for displaying user prompts for content authoring.
 */
ConfirmDirtyService.$inject = ['$window', '$q', '$filter', 'api', 'modal', 'gettextCatalog',
    '$interpolate', '$modal'];
export function ConfirmDirtyService($window, $q, $filter, api, modal, gettextCatalog, $interpolate, $modal) {
    /**
     * Will ask for user confirmation for user confirmation if there are some changes which are not saved.
     * - Detecting changes via $scope.dirty - it's up to the controller to set it.
     */
    this.setupWindow = function setupWindow($scope) {
        $window.onbeforeunload = function() {
            if ($scope.dirty) {
                return gettextCatalog.getString(
                    'There are unsaved changes. If you navigate away, your changes will ' +
                    'be lost.');
            }

            $scope.$on('$destroy', () => {
                $window.onbeforeunload = angular.noop;
            });
        };
    };

    // Flag to set for instant checking if $scope is dirty for current item
    this.dirty = null;

    /**
     * Called from workqueue in case of unsaved changes.
     */
    this.reopen = function() {
        return modal.confirm(
            gettextCatalog.getString('There are some unsaved changes, go to the article to save changes?'),
            gettextCatalog.getString('Save changes?'),
            gettextCatalog.getString('Go-To'),
            gettextCatalog.getString('Ignore'),
            gettextCatalog.getString('Cancel')
        );
    };

    /**
     * In case $scope is dirty ask user if he want's to loose his changes.
     */
    this.confirm = function confirm() {
        return modal.confirm(
            gettextCatalog.getString('There are some unsaved changes, do you want to save it now?'),
            gettextCatalog.getString('Save changes?'),
            gettextCatalog.getString('Save'),
            gettextCatalog.getString('Ignore'),
            gettextCatalog.getString('Cancel')
        );
    };

    /**
     * In case publish is triggered by quick buttons, show confirmation dialog
     */
    this.confirmQuickPublish = function() {
        return modal.confirm(
            gettextCatalog.getString('Do you want to publish the article?'),
            gettextCatalog.getString('Publishing'),
            gettextCatalog.getString('Publish'),
            gettextCatalog.getString('Cancel')
        );
    };

    /**
     * In case $scope is dirty ask user if he want's to save changes and publish.
     */
    this.confirmPublish = function() {
        return modal.confirm(
            gettextCatalog.getString('There are some unsaved changes, do you want to save and publish it now?'),
            gettextCatalog.getString('Save changes?'),
            gettextCatalog.getString('Save and publish'),
            gettextCatalog.getString('Cancel')
        );
    };

    /**
     * In case $scope is dirty ask user if he want's to save changes and publish.
     */
    this.confirmSendTo = function confirmSendTo(action) {
        return modal.confirm(
            $interpolate(gettextCatalog.getString(
                'There are some unsaved changes, do you want to save and send it now?'
            ))({action: action}),
            gettextCatalog.getString('Save changes?'),
            $interpolate(gettextCatalog.getString('Save and send'))({action: action}),
            gettextCatalog.getString('Cancel')
        );
    };

    this.confirmSaveWork = function confirmSavework(msg) {
        return modal.confirm(
            $interpolate(
                gettextCatalog.getString(
                    'Configuration has changed. {{ message }} Would you like to save the story to your workspace?'
                )
            )({message: msg})
        );
    };

    /**
     * Make user aware that an item was unlocked
     *
     * @param {string} userId Id of user who unlocked an item.
     * @param {string} headline Headline of item which is unlocked
     */
    this.unlock = function unlock(userId, headline) {
        api.find('users', userId).then((user) => {
            var itemHeading = headline ? 'Item ' + headline + '' : 'This item';
            var msg = gettext(itemHeading + ' was unlocked by ' + $filter('username')(user) + '.');

            return modal.confirm(msg, gettextCatalog.getString('Item Unlocked'), gettextCatalog.getString('OK'), false);
        });
    };

    /**
     * Make user aware that an item was locked
     *
     * @param {string} userId Id of user who locked an item.
     */
    this.lock = function lock(userId) {
        api.find('users', userId).then((user) => {
            var msg = gettextCatalog.getString('This item was locked by ' + $filter('username')(user) + '.');

            return modal.confirm(msg, gettextCatalog.getString('Item locked'), gettext('OK'), false);
        });
    };

    /**
     * @ngdoc method
     * @name confirm#confirmFeatureMedia
     * @public
     * @returns {promise}
     * @description Prompts the user to add the associated media to the Update.
     * @param {Object} item
     */
    this.confirmFeatureMedia = function(item) {
        let defered = $q.defer();

        $modal.open({
            templateUrl: 'scripts/apps/authoring/views/confirm-media-associated.html',
            controller: ['$scope', '$modalInstance', function($scope, $modalInstance) {
                $scope.item = item;

                $scope.useMedia = function() {
                    defered.resolve($scope.item);
                    $modalInstance.close();
                };

                $scope.cancel = function() {
                    defered.reject(false);
                    $modalInstance.dismiss();
                };

                $scope.publishWithoutMedia = function() {
                    defered.resolve({});
                    $modalInstance.dismiss();
                };
            }]
        });

        return defered.promise;
    };
}
<|endoftext|>


<commit_before>/* qqq : implement */
let _;

if( typeof module !== 'undefined' )
{
  _ = require( 'wTools' );
  require( 'wConsequence' );
}

const startTime = _.time.now();
const con = new _.Consequence();
const waitingPas = [];

const passagers =
[
  { id : 1, fraction : 'employee', delay : 1000 },
  { id : 2, fraction : 'hacker', delay : 1500 },
  { id : 3, fraction : 'employee', delay : 2500 },
  { id : 4, fraction : 'employee', delay : 3500 },
]

//

function run()
{

}

//

function status()
{

}

//

function hackerArrives()
{

}

//

function employeeArrives()
{

}

//

function rowBoat()
{

}

//

function boardBoat()
{

}
<commit_msg>RiverCrossing task, progress<commit_after>/* qqq : implement */
let _;

if( typeof module !== 'undefined' )
{
  _ = require( 'wTools' );
  require( 'wConsequence' );
}

const startTime = _.time.now();
const con = new _.Consequence().take( null );
const waitingH = [];
const waitingE = [];
const passOnBoard = [];
const riverCrossingTime = 10000;
let crossingRiver = false;

const passengers =
[
  { id : 1, fraction : 'employee', arrival : 1000 },
  { id : 2, fraction : 'hacker', arrival : 1500 },
  { id : 3, fraction : 'employee', arrival : 2500 },
  { id : 4, fraction : 'employee', arrival : 3500 },

  { id : 5, fraction : 'hacker', arrival : 5500 },
  { id : 6, fraction : 'employee', arrival : 6500 },
  { id : 7, fraction : 'hacker', arrival : 8000 },
  { id : 8, fraction : 'employee', arrival : 9500 },

  { id : 9, fraction : 'hacker', arrival : 10500 },
  { id : 10, fraction : 'employee', arrival : 12000 },
  { id : 11, fraction : 'hacker', arrival : 13500 },
  { id : 12, fraction : 'hacker', arrival : 15000 },

  { id : 13, fraction : 'hacker', arrival : 16500 },
  { id : 14, fraction : 'employee', arrival : 17500 },
  { id : 15, fraction : 'employee', arrival : 18000 },
  { id : 16, fraction : 'hacker', arrival : 19500 },
]

run();

//

function run()
{
  for( let i = 0; i < passengers.length; i++ )
  _.time.out( passengers[ i ].arrival, () => passengerArrives( passengers[ i ] ) );
}

//

function status()
{
  const h = passOnBoard.filter( ( p ) => p.fraction === 'hacker' );
  const e = passOnBoard.filter( ( p ) => p.fraction === 'employee' );
  return `${_.time.spent( startTime )}, boatCrossesRiver: ${crossingRiver}, onBoard: h:${h.length} e:${e.length}, waitingH: ${waitingH.length}, waitingE: ${waitingE.length}`;
}

//

function nextBoat()
{
  if( !waitingH.length && !waitingE.length )
  return null;
}

//

function passengerArrives( p )
{
  console.log( `+ pass_${p.id} ${p.fraction} arrived - ${status()}` );

  p.fraction === 'hacker' ? hackerArrives( p ) : employeeArrives( p );

  if( passOnBoard.length === 4 && !crossingRiver )
  con.then( () => rowBoat );
}

//

function boardBoat( p )
{
  passOnBoard.push( p );
  console.log( `  pass_${p.id} ${p.fraction} boarded - ${status()}` );
}

//

function rowBoat()
{
  crossingRiver = true;
  console.log( `- boat starts to cross the river - ${status}` );
  return _.time.out( riverCrossingTime ).then( () => nextBoat() || null );
}

//

function hackerArrives( h )
{
  if( crossingRiver || waitingH.length )
  {
    waitingH.push( h );
  }
  else
  {
    boardBoat( h );
  }
}

//

function employeeArrives( e )
{
  waitingE.length ? waitingE.push( e ) : boardBoat( e );
}
<|endoftext|>


<commit_before>let Cycle = require("@cycle/core")
let {br, button, div, h1, h2, hr, input, label, makeDOMDriver, p, pre} = require("@cycle/dom")

// main :: {Observable *} -> {Observable *}
let main = function (src) {
  let username = src.DOM.select("#username")
    .events("input")
    .map((event) => event.target.value)
    .startWith("")

  let email = src.DOM.select("#email")
    .events("input")
    .map((event) => event.target.value)
    .startWith("")

  return {
    DOM: $.combineLatest(
      username, email,
      (username, email) => {
        return div([
          h1("Registration"),
          div(".form-element", [
            label({htmlFor: "username"}, "Username:"),
            br(),
            input("#username", {type: "text", autocomplete: "off"}),
          ]),
          div(".form-element", [
            label({htmlFor: "email"}, "Email:"),
            br(),
            input("#email", {type: "text", autocomplete: "off"}),
          ]),
          hr(),
          h2("State SPY"),
          pre(JSON.stringify({username, email}, null, 2)),
        ])
      }
    )
  }
}

Cycle.run(main, {
  DOM: makeDOMDriver("#app"),
})<commit_msg>Missing reference<commit_after>let Cycle = require("@cycle/core")
let {Observable: $} = require("rx")
let {br, button, div, h1, h2, hr, input, label, makeDOMDriver, p, pre} = require("@cycle/dom")

// main :: {Observable *} -> {Observable *}
let main = function (src) {
  let username = src.DOM.select("#username")
    .events("input")
    .map((event) => event.target.value)
    .startWith("")

  let email = src.DOM.select("#email")
    .events("input")
    .map((event) => event.target.value)
    .startWith("")

  return {
    DOM: $.combineLatest(
      username, email,
      (username, email) => {
        return div([
          h1("Registration"),
          div(".form-element", [
            label({htmlFor: "username"}, "Username:"),
            br(),
            input("#username", {type: "text", autocomplete: "off"}),
          ]),
          div(".form-element", [
            label({htmlFor: "email"}, "Email:"),
            br(),
            input("#email", {type: "text", autocomplete: "off"}),
          ]),
          hr(),
          h2("State SPY"),
          pre(JSON.stringify({username, email}, null, 2)),
        ])
      }
    )
  }
}

Cycle.run(main, {
  DOM: makeDOMDriver("#app"),
})
<|endoftext|>


<commit_before>(function (Q, $) {

/**
 * @module Streams-tools
 */

/**
 * Lets the user search for streams by type and title, and choose them
 * @class Streams lookup
 * @constructor
 * @param {Array} [options] Override various options for this tool
 * @param {String} [options.publisherId=Q.Users.communityId] id of the user publishing the streams
 * @param {Array} options.types the types of streams the user can select
 * @param {Object} [options.typeNames] pairs of {type: typeName} to override names of the types, which would otherwise be taken from the types
 * @param {Object} [options.typeIconsSize] pairs of {type: iconSize} to override default icon size used in results.
 * @param {Boolean} [options.multiple=false] whether the user can select multiple types
 * @param {Q.Event} [options.onChoose] This event handler occurs when one of the elements with class "Q_filter_results" is chosen. It is passed (streamName, element, obj) where you can modify obj.text to set the text which will be displayed in the text input to represent the chosen item.
 * @param {Object} [options.filter] any options for the Q/filter tool
 */
Q.Tool.define("Streams/lookup", function _Streams_lookup_tool (options) {
	var state = this.state;
	if (Q.isEmpty(state.types)) {
		throw new Q.Error("Streams/lookup tool: missing types");
	}

	Q.addStylesheet("{{Streams}}/css/tools/lookup.css", this.refresh.bind(this));

}, {
	publisherId: Q.info.app,
	types: [],
	typeNames: {},
	typeIconsSize: {},
	multiple: false,
	filter: {
		placeholder: "Start typing..."
	},
	onRefresh: new Q.Event(),
	onChoose: new Q.Event()
}, {
	/**
	 * Call this method to refresh the contents of the tool, requesting only
	 * what's needed and redrawing only what's needed.
	 * @method refresh
	 * @param {Function} callback - An optional callback to call after refresh has completed.
	 *  It receives (result, entering, exiting, updating) arguments.
	 */
	refresh: function (callback) {
		var tool = this;
		var state = tool.state;
		for (var i=0; i<state.types.length; ++i) {
			var type = state.types[i];
			if (!state.typeNames[type]) {
				state.typeNames[type] = state.types[i].split('/').pop().toCapitalized();
			}
		}
		var fields = Q.extend({
			multiple: state.multiple
		}, state);
		Q.Template.render('Streams/lookup/tool', fields, function (err, html) {
			var msg;
			if (msg = Q.firstErrorMessage(err)) {
				return alert(msg);
			}
			tool.element.innerHTML = html;
			tool.$select = tool.$('select');
			Q.activate(tool.element, {
				'.Q_filter_tool': state.filter
			}, function () {
				var filter = tool.filter = Q.Tool.from($(".Q_filter_tool", tool.element)[0], "Q/filter")
				filter.state.onFilter.set(function (query, element) {
					if (Q.isEmpty(query)) {
						return;
					}

					var latest = Q.latest(filter);
					var types = state.multiple ? state.types : tool.$select.val();
					tool.getResults(query+'%', types, state.publisherId,
					function ($content) {
						if (Q.latest(filter, latest)) {
							$(element).empty().append($content);
						}
					});
				}, tool);
				filter.state.onChoose.set(function (element, obj) {
					var streamName = $(element).attr('data-streamName');
					Q.handle(state.onChoose, tool, [streamName, element, obj]);
				}, tool);
				tool.$select.on('change', function () {
					Q.handle(filter.state.onFilter, filter, ['', filter.$results[0]]);
				});
			});
			Q.handle(callback, tool);
			Q.handle(state.onRefresh, tool);
		});
	},
	/**
	 * @method getResults
	 * @param {String} title - streams title to search
	 * @param {array} types - streams types to filter search
	 * @param {String} publisherId - streams publisherId
	 * @param {Function} callback
	 */
	getResults: Q.getter(function (title, types, publisherId, callback) {
		var tool = this;
		var state = this.state;

		Q.req('Streams/lookup', 'results', function (err, data) {
			var results, msg;
			if (msg = Q.firstErrorMessage(err, data && data.errors)) {
				results = $('
')
					.html("No results");
				callback(results);
				return;
			}
			var $table = $('
');
			Q.each(data.slots.results, function (i, result) {
				var $tr = $('
')
					.attr('data-streamName', result.name)
					.attr('data-publisherId', result.publisherId)
					.attr('data-type', result.type)
					.appendTo($table);
				$('')
					.append($('', {'src': result.icon+'/' + (Q.getObject(["typeIconsSize", result.type], state) || 80) + '.png'}))
					.appendTo($tr);
				$('')
					.text(result.title)
					.appendTo($tr);
			});
			callback($table);
		}, { fields: {
				publisherId: publisherId,
				title: title,
				types: types
			}})
	}),
	Q: {
		beforeRemove: function () {

		}
	}
});

Q.Template.set('Streams/lookup/tool',
'{{#unless multiple}}'
	+ '<select name="streamType">\n'
	+ '{{#each types}}\n'
	+ '<option value="{{this}}">{{lookup ../typeNames this}}</option>\n'
	+ '{{/each}}\n'
	+ '</select>\n'
	+ '{{/unless}}'
	+ '{{&tool "Q/filter" ""}}'
);

})(Q, jQuery);<commit_msg>Updated Streams/lookup tool. If error or empty results for some query, run callback with empty string to force Q/filter slear results.<commit_after>(function (Q, $) {

/**
 * @module Streams-tools
 */

/**
 * Lets the user search for streams by type and title, and choose them
 * @class Streams lookup
 * @constructor
 * @param {Array} [options] Override various options for this tool
 * @param {String} [options.publisherId=Q.Users.communityId] id of the user publishing the streams
 * @param {Array} options.types the types of streams the user can select
 * @param {Object} [options.typeNames] pairs of {type: typeName} to override names of the types, which would otherwise be taken from the types
 * @param {Object} [options.typeIconsSize] pairs of {type: iconSize} to override default icon size used in results.
 * @param {Boolean} [options.multiple=false] whether the user can select multiple types
 * @param {Q.Event} [options.onChoose] This event handler occurs when one of the elements with class "Q_filter_results" is chosen. It is passed (streamName, element, obj) where you can modify obj.text to set the text which will be displayed in the text input to represent the chosen item.
 * @param {Object} [options.filter] any options for the Q/filter tool
 */
Q.Tool.define("Streams/lookup", function _Streams_lookup_tool (options) {
	var state = this.state;
	if (Q.isEmpty(state.types)) {
		throw new Q.Error("Streams/lookup tool: missing types");
	}

	Q.addStylesheet("{{Streams}}/css/tools/lookup.css", this.refresh.bind(this));

}, {
	publisherId: Q.info.app,
	types: [],
	typeNames: {},
	typeIconsSize: {},
	multiple: false,
	filter: {
		placeholder: "Start typing..."
	},
	onRefresh: new Q.Event(),
	onChoose: new Q.Event()
}, {
	/**
	 * Call this method to refresh the contents of the tool, requesting only
	 * what's needed and redrawing only what's needed.
	 * @method refresh
	 * @param {Function} callback - An optional callback to call after refresh has completed.
	 *  It receives (result, entering, exiting, updating) arguments.
	 */
	refresh: function (callback) {
		var tool = this;
		var state = tool.state;
		for (var i=0; i<state.types.length; ++i) {
			var type = state.types[i];
			if (!state.typeNames[type]) {
				state.typeNames[type] = state.types[i].split('/').pop().toCapitalized();
			}
		}
		var fields = Q.extend({
			multiple: state.multiple
		}, state);
		Q.Template.render('Streams/lookup/tool', fields, function (err, html) {
			var msg;
			if (msg = Q.firstErrorMessage(err)) {
				return alert(msg);
			}
			tool.element.innerHTML = html;
			tool.$select = tool.$('select');
			Q.activate(tool.element, {
				'.Q_filter_tool': state.filter
			}, function () {
				var filter = tool.filter = Q.Tool.from($(".Q_filter_tool", tool.element)[0], "Q/filter")
				filter.state.onFilter.set(function (query, element) {
					if (Q.isEmpty(query)) {
						return;
					}

					var latest = Q.latest(filter);
					var types = state.multiple ? state.types : tool.$select.val();
					tool.getResults(query+'%', types, state.publisherId,
					function ($content) {
						if (Q.latest(filter, latest)) {
							$(element).empty().append($content);
						}
					});
				}, tool);
				filter.state.onChoose.set(function (element, obj) {
					var streamName = $(element).attr('data-streamName');
					Q.handle(state.onChoose, tool, [streamName, element, obj]);
				}, tool);
				tool.$select.on('change', function () {
					Q.handle(filter.state.onFilter, filter, ['', filter.$results[0]]);
				});
			});
			Q.handle(callback, tool);
			Q.handle(state.onRefresh, tool);
		});
	},
	/**
	 * @method getResults
	 * @param {String} title - streams title to search
	 * @param {array} types - streams types to filter search
	 * @param {String} publisherId - streams publisherId
	 * @param {Function} callback
	 */
	getResults: Q.getter(function (title, types, publisherId, callback) {
		var tool = this;
		var state = this.state;

		Q.req('Streams/lookup', 'results', function (err, data) {
			var results, msg;
			if (msg = Q.firstErrorMessage(err, data && data.errors)) {
				results = $('
')
					.html("No results");
				callback(results);
				return callback("");
			}

			if (Q.isEmpty(data.slots.results)) {
				return callback("");
			}
			var $table = $('
');
			Q.each(data.slots.results, function (i, result) {
				var $tr = $('
')
					.attr('data-streamName', result.name)
					.attr('data-publisherId', result.publisherId)
					.attr('data-type', result.type)
					.appendTo($table);
				$('')
					.append($('', {'src': result.icon+'/' + (Q.getObject(["typeIconsSize", result.type], state) || 80) + '.png'}))
					.appendTo($tr);
				$('')
					.text(result.title)
					.appendTo($tr);
			});
			callback($table);
		}, { fields: {
			publisherId: publisherId,
			title: title,
			types: types
		}})
	}),
	Q: {
		beforeRemove: function () {

		}
	}
});

Q.Template.set('Streams/lookup/tool',
'{{#unless multiple}}'
	+ '<select name="streamType">\n'
	+ '{{#each types}}\n'
	+ '<option value="{{this}}">{{lookup ../typeNames this}}</option>\n'
	+ '{{/each}}\n'
	+ '</select>\n'
	+ '{{/unless}}'
	+ '{{&tool "Q/filter" ""}}'
);

})(Q, jQuery);<|endoftext|>


<commit_before>/*
    Action class
    ----------------------------------------

    Defines the action object.

    Actions describe a process, be it an animation or a pointer tracking

    Properties
	    active: false,
	    alternate: false,
	    amp: 0,
	    duration: defaults.duration,
	    ease: defaults.ease,
	    link: defaults.link,
	    onEnd: function () {},
	    onFrame: function () {},
	    origin: {},
	    output: {},
	    progress: 0,
	    tick: 0,
	    time: {},
	    token: 0,
	    type: defaults.type,
	    valueIndex: 0,
	    values: {}

*/
"use strict";

var KEY = require('../opts/keys.js'),
    defaults = require('../opts/defaults.js'),
    calc = require('../utils/calc.js'),
    utils = require('../utils/utils.js'),
    Value = require('./value.js'),
    Token = require('../bobs/token.js'),
    callback = function () {},
    token = new Token(),
    Action = function () {
        this.created = utils.currentTime();
        this.token = token.generate();
        this.data = {};
    };

Action.prototype = {

    /*
	    Set action properties

	    @param [object]: Properties
    */
    set: function (opts) {

	    this.link = opts.link || this.link || defaults.link;

        // Action parameters
        this.amp = utils.isNum(opts.amp) ? opts.amp : defaults.amp;
        this.escapeAmp = utils.isNum(opts.escapeAmp) ? opts.escapeAmp : defaults.escapeAmp;
        this.delay = utils.isNum(opts.delay) ? opts.delay : defaults.delay;
        this.duration = utils.isNum(opts.duration) ? opts.duration : defaults.duration;
        this.ease = opts.ease || defaults.ease;
        this.math = opts.math;
        this.steps = utils.isNum(opts.steps) ? opts.steps : defaults.steps;
        this.alternate = opts.alternate;
        this.pointerOffset = opts.pointerOffset;

        // Play list
        this.playlist = opts.playlist || this.playlist || [];
        this.playhead = utils.isNum(opts.playhead) ? opts.playhead : this.playhead;

        // Looping
        this.loop = opts.loop || false;
        this.loopCount = 0;
        this.yoyo = opts.yoyo || false;
        this.yoyoCount = 0;

        // Callbacks
        this.onStart = opts.onStart || callback;
        this.onEnd = opts.onEnd || callback;
        this.onFrame = opts.onFrame || this.onFrame || callback;

		// Values
        this.setValues(opts.values);
        this.origin = {};

        for (var key in this.values) {
	        if (this.values.hasOwnProperty(key)) {
	            this.origin[key] = this.values[key].current;
	        }
        }

    	// Set last modified timestamp
	    this.lastModified = utils.currentTime();
    },

    setValues: function (values) {
        this.values = this.values || {};

        for (var key in values) {
	        if (values.hasOwnProperty(key)) {
	        	if (this.values[key]) {
		        	this.values[key].update(values[key], this);
	        	} else {
		        	this.values[key] = new Value(values[key], this);
	        	}
	        }
        }

        if (this.values.angle) {
        	this.values.x = this.values.x || new Value(0, this);
            this.values.y = this.values.y || new Value(0, this);
        }
    },

    /*
        Reset values to their most recently set amounts
    */
    resetValues: function () {
        for (var key in this.values) {
            this.values[key].current = this.values[key].from;
        }

        this.progress = 0;
    },

    reverseValues: function () {
    	var key, to, from;

    	this.progress = calc.difference(this.progress, 1);
console.log(this.progress);
	    for (key in this.values) {
		    if (this.values.hasOwnProperty(key)) {
		    	to = this.values[key].to;
		    	from = this.values[key].from;

		    	this.values[key].to = from;
			    this.values[key].from = to;
		    }
	    }
    },

    /*
        Set start time and activate
    */
    start: function () {
        this.active = true;
        this.started = utils.currentTime() + this.delay;
        this.firstFrame = true;
        //this.started = utils.currentTime() + this.delay - calc.value(this.progress, this.duration);
    },

    stop: function () {
        this.active = false;
    }
};

module.exports = Action;<commit_msg>removing console.log<commit_after>/*
    Action class
    ----------------------------------------

    Defines the action object.

    Actions describe a process, be it an animation or a pointer tracking

    Properties
	    active: false,
	    alternate: false,
	    amp: 0,
	    duration: defaults.duration,
	    ease: defaults.ease,
	    link: defaults.link,
	    onEnd: function () {},
	    onFrame: function () {},
	    origin: {},
	    output: {},
	    progress: 0,
	    tick: 0,
	    time: {},
	    token: 0,
	    type: defaults.type,
	    valueIndex: 0,
	    values: {}

*/
"use strict";

var KEY = require('../opts/keys.js'),
    defaults = require('../opts/defaults.js'),
    calc = require('../utils/calc.js'),
    utils = require('../utils/utils.js'),
    Value = require('./value.js'),
    Token = require('../bobs/token.js'),
    callback = function () {},
    token = new Token(),
    Action = function () {
        this.created = utils.currentTime();
        this.token = token.generate();
        this.data = {};
    };

Action.prototype = {

    /*
	    Set action properties

	    @param [object]: Properties
    */
    set: function (opts) {

	    this.link = opts.link || this.link || defaults.link;

        // Action parameters
        this.amp = utils.isNum(opts.amp) ? opts.amp : defaults.amp;
        this.escapeAmp = utils.isNum(opts.escapeAmp) ? opts.escapeAmp : defaults.escapeAmp;
        this.delay = utils.isNum(opts.delay) ? opts.delay : defaults.delay;
        this.duration = utils.isNum(opts.duration) ? opts.duration : defaults.duration;
        this.ease = opts.ease || defaults.ease;
        this.math = opts.math;
        this.steps = utils.isNum(opts.steps) ? opts.steps : defaults.steps;
        this.alternate = opts.alternate;
        this.pointerOffset = opts.pointerOffset;

        // Play list
        this.playlist = opts.playlist || this.playlist || [];
        this.playhead = utils.isNum(opts.playhead) ? opts.playhead : this.playhead;

        // Looping
        this.loop = opts.loop || false;
        this.loopCount = 0;
        this.yoyo = opts.yoyo || false;
        this.yoyoCount = 0;

        // Callbacks
        this.onStart = opts.onStart || callback;
        this.onEnd = opts.onEnd || callback;
        this.onFrame = opts.onFrame || this.onFrame || callback;

		// Values
        this.setValues(opts.values);
        this.origin = {};

        for (var key in this.values) {
	        if (this.values.hasOwnProperty(key)) {
	            this.origin[key] = this.values[key].current;
	        }
        }

    	// Set last modified timestamp
	    this.lastModified = utils.currentTime();
    },

    setValues: function (values) {
        this.values = this.values || {};

        for (var key in values) {
	        if (values.hasOwnProperty(key)) {
	        	if (this.values[key]) {
		        	this.values[key].update(values[key], this);
	        	} else {
		        	this.values[key] = new Value(values[key], this);
	        	}
	        }
        }

        if (this.values.angle) {
        	this.values.x = this.values.x || new Value(0, this);
            this.values.y = this.values.y || new Value(0, this);
        }
    },

    /*
        Reset values to their most recently set amounts
    */
    resetValues: function () {
        for (var key in this.values) {
            this.values[key].current = this.values[key].from;
        }

        this.progress = 0;
    },

    reverseValues: function () {
    	var key, to, from;

    	this.progress = calc.difference(this.progress, 1);

	    for (key in this.values) {
		    if (this.values.hasOwnProperty(key)) {
		    	to = this.values[key].to;
		    	from = this.values[key].from;

		    	this.values[key].to = from;
			    this.values[key].from = to;
		    }
	    }
    },

    /*
        Set start time and activate
    */
    start: function () {
        this.active = true;
        this.started = utils.currentTime() + this.delay;
        this.firstFrame = true;
        //this.started = utils.currentTime() + this.delay - calc.value(this.progress, this.duration);
    },

    stop: function () {
        this.active = false;
    }
};

module.exports = Action;<|endoftext|>


<commit_before>#!/usr/bin/env node

var log = function() {
	var args = Array.prototype.map.call(arguments, function(value) {
		if (typeof value === 'string') {
			return value;
		} else {
			return JSON.stringify(value, null, '\t')
		}
	});
	process.stdout.write(args.join('') + '\n');
}

module.exports = function(context) {
	var fs = context.requireCordovaModule('fs');
	var path = context.requireCordovaModule('path');
	var glob = context.requireCordovaModule('glob');
	var deferral = context.requireCordovaModule('q').defer();
	var async = context.requireCordovaModule(path.join('request', 'node_modules', 'form-data', 'node_modules', 'async'));

	var platformDir = path.join(context.opts.projectRoot, 'platforms', 'android');

	var write_properties = function(next) {
		var file_path = path.join(platformDir, 'fabric.properties');
		var lines = [ "apiSecret=" + process.env.FABRIC_BUILD_SECRET, "apiKey=" + process.env.FABRIC_API_KEY,
				"betaDistributionReleaseNotesFilePath=" + process.env.RELEASE_NOTE_PATH,
				"betaDistributionGroupAliases=" + process.env.CRASHLYTICS_GROUPS, "" ]
		log("Writing ", file_path);
		fs.writeFile(file_path, lines.join("\n"), 'utf-8', next);
	}

	var adjustIndent = function(base, content) {
		var first = base.match(/^[ \t]*/);
		var indent = (first && first.length > 0) ? first[0] : '';
		return indent + content;
	}

	var build_gradle = function(next) {
		var target = path.join(platformDir, 'build.gradle');
		log("Editing ", target);
		async.waterfall(
				[
				function(next) {
					fs.readFile(target, 'utf-8', next);
				},
				function(content, next) {
					var cond = {
							compile: 0,
							classpath: 0,
							plugin: 0
					}
					var lines = content.split('\n').map(function (line) {
						var result = [line];
						var adding;
						if (cond.compile === 1) {
							adding = "compile('com.crashlytics.sdk.android:crashlytics:2.5.2@aar') { transitive = true }";
							cond.compile = 0;
						}
						if (cond.classpath === 0) {
							var found = line.match(/classpath 'com\.android\.tools\.build:gradle:1\.[1-9]\.0\+'/);
							if (found && found.length > 0) {
								adding = "classpath 'io.fabric.tools:gradle:1.+'";
								cond.classpath = 1;
							}
						}
						if (cond.plugin == 0 && line.indexOf('apply plugin:') > -1) {
							adding = "apply plugin: 'io.fabric'";
							cond.plugin = 1;
						}
						if (line.indexOf('mavenCentral') > -1) {
							adding = "maven { url 'https://maven.fabric.io/public' }";
						}
						if (line.indexOf('dependencies') === 0) {
							cond.compile = 1;
						}
						if (adding) result.push(adjustIndent(line, adding));
						return result;
					}).reduce(function(a, b) {
						return a.concat(b);
					}, []);
					next(null, lines);
				},
				function(lines, next) {
					fs.writeFile(target, lines.join('\n'), 'utf-8', next);
				}
				 ], next);
	}

	var main_activity = function(next) {
		var target_name = 'MainActivity.java';

		var modify = function(target, next) {
			log("Editing ", target);
			async.waterfall(
					[
					function(next) {
						fs.readFile(target, 'utf-8', next);
					},
					function(content, next) {
						var lines = content.split('\n').map(function (line) {
							var result = [line];
							if (line.indexOf('super.onCreate') > -1) {
								var content = 'io.fabric.sdk.android.Fabric.with(this, new com.crashlytics.android.Crashlytics());';
								result.push(adjustIndent(line, content));
							}
							return result;
						}).reduce(function(a, b) {
							return a.concat(b);
						}, []);
						next(null, lines);
					},
					function(lines, next) {
						fs.writeFile(target, lines.join('\n'), 'utf-8', next);
					}
					 ], next);
		}
		async.waterfall(
				[
				function(next) {
					glob(path.join(platformDir, 'src', '**', target_name), null, next);
				},
				function(files, next) {
					if (files && files.length > 0) {
						next(null, files[0]);
					} else {
						next('NotFound: ' + target_name);
					}
				},
				modify
				 ], next);
	}

	var main = function() {
		async.parallel(
				[
				write_properties,
				build_gradle,
				main_activity
				],
				function(err, result) {
					if (err) {
						log(err);
						deferral.reject(err);
					} else {
						deferral.resolve(result);
					}
				});
	}
	main();
	return deferral.promise;
};
<commit_msg>念のため '===' に修正<commit_after>#!/usr/bin/env node

var log = function() {
	var args = Array.prototype.map.call(arguments, function(value) {
		if (typeof value === 'string') {
			return value;
		} else {
			return JSON.stringify(value, null, '\t')
		}
	});
	process.stdout.write(args.join('') + '\n');
}

module.exports = function(context) {
	var fs = context.requireCordovaModule('fs');
	var path = context.requireCordovaModule('path');
	var glob = context.requireCordovaModule('glob');
	var deferral = context.requireCordovaModule('q').defer();
	var async = context.requireCordovaModule(path.join('request', 'node_modules', 'form-data', 'node_modules', 'async'));

	var platformDir = path.join(context.opts.projectRoot, 'platforms', 'android');

	var write_properties = function(next) {
		var file_path = path.join(platformDir, 'fabric.properties');
		var lines = [ "apiSecret=" + process.env.FABRIC_BUILD_SECRET, "apiKey=" + process.env.FABRIC_API_KEY,
				"betaDistributionReleaseNotesFilePath=" + process.env.RELEASE_NOTE_PATH,
				"betaDistributionGroupAliases=" + process.env.CRASHLYTICS_GROUPS, "" ]
		log("Writing ", file_path);
		fs.writeFile(file_path, lines.join("\n"), 'utf-8', next);
	}

	var adjustIndent = function(base, content) {
		var first = base.match(/^[ \t]*/);
		var indent = (first && first.length > 0) ? first[0] : '';
		return indent + content;
	}

	var build_gradle = function(next) {
		var target = path.join(platformDir, 'build.gradle');
		log("Editing ", target);
		async.waterfall(
				[
				function(next) {
					fs.readFile(target, 'utf-8', next);
				},
				function(content, next) {
					var cond = {
							compile: 0,
							classpath: 0,
							plugin: 0
					}
					var lines = content.split('\n').map(function (line) {
						var result = [line];
						var adding;
						if (cond.compile === 1) {
							adding = "compile('com.crashlytics.sdk.android:crashlytics:2.5.2@aar') { transitive = true }";
							cond.compile = 0;
						}
						if (cond.classpath === 0) {
							var found = line.match(/classpath 'com\.android\.tools\.build:gradle:1\.[1-9]\.0\+'/);
							if (found && found.length > 0) {
								adding = "classpath 'io.fabric.tools:gradle:1.+'";
								cond.classpath = 1;
							}
						}
						if (cond.plugin === 0 && line.indexOf('apply plugin:') > -1) {
							adding = "apply plugin: 'io.fabric'";
							cond.plugin = 1;
						}
						if (line.indexOf('mavenCentral') > -1) {
							adding = "maven { url 'https://maven.fabric.io/public' }";
						}
						if (line.indexOf('dependencies') === 0) {
							cond.compile = 1;
						}
						if (adding) result.push(adjustIndent(line, adding));
						return result;
					}).reduce(function(a, b) {
						return a.concat(b);
					}, []);
					next(null, lines);
				},
				function(lines, next) {
					fs.writeFile(target, lines.join('\n'), 'utf-8', next);
				}
				 ], next);
	}

	var main_activity = function(next) {
		var target_name = 'MainActivity.java';

		var modify = function(target, next) {
			log("Editing ", target);
			async.waterfall(
					[
					function(next) {
						fs.readFile(target, 'utf-8', next);
					},
					function(content, next) {
						var lines = content.split('\n').map(function (line) {
							var result = [line];
							if (line.indexOf('super.onCreate') > -1) {
								var content = 'io.fabric.sdk.android.Fabric.with(this, new com.crashlytics.android.Crashlytics());';
								result.push(adjustIndent(line, content));
							}
							return result;
						}).reduce(function(a, b) {
							return a.concat(b);
						}, []);
						next(null, lines);
					},
					function(lines, next) {
						fs.writeFile(target, lines.join('\n'), 'utf-8', next);
					}
					 ], next);
		}
		async.waterfall(
				[
				function(next) {
					glob(path.join(platformDir, 'src', '**', target_name), null, next);
				},
				function(files, next) {
					if (files && files.length > 0) {
						next(null, files[0]);
					} else {
						next('NotFound: ' + target_name);
					}
				},
				modify
				 ], next);
	}

	var main = function() {
		async.parallel(
				[
				write_properties,
				build_gradle,
				main_activity
				],
				function(err, result) {
					if (err) {
						log(err);
						deferral.reject(err);
					} else {
						deferral.resolve(result);
					}
				});
	}
	main();
	return deferral.promise;
};
<|endoftext|>


<commit_before>/* Get Programming with JavaScript
 * Listing 13.10
 * Using an extra variable rather than an IIFE
 */

var getSecretInterface = function () {
    var message = "Who let you in here?";

    return {
        share: function () {
            console.log(message);
        }
    };
};

var secret = getSecretInterface();

/* Further Adventures
 *
 * 1) Run the program.
 *
 * 2) Call the share method at
 *    the prompt.
 *
 *    > secret.share()
 *
 * The share method is executed,
 * logging the message.
 *
 * When functions are called from
 * the prompt, their code is executed
 * and then their return value
 * is displayed.
 *
 * The share method does not have
 * a return statement so it
 * returns 'undefined'.
 *
 * 3) Update the share method so
 *    it returns a value.
 *
 *    share: function () {
 *        console.log(message);
 *        return "Did you log this?";
 *    }
 *
 * 4) Run the program.
 *
 * 5) Call the share method at
 *    the prompt.
 *
 */
<commit_msg>Kallie's code causes trouble<commit_after>/* Get Programming with JavaScript
 * Listings 13.09 and 13.10
 * Importing Kallie's code
 */

var getGuesser = function (lowest, highest) {
  var secret = between(lowest, highest);

  return function (userNumber) {
    var msg;
    if (userNumber === secret) {
      msg = dasher("Well done!");
      return spacer.wrap(msg, msg.length + 4, "=");
    } else if (userNumber > secret) {
      msg = dasher("Too high!");
      return spacer.wrap(msg, msg.length + 4, "+");
    } else {
      msg = dasher("Too low!");
      return spacer.wrap(msg, msg.length + 4, "-");
    }
  };
};

var guess = getGuesser(5, 10);

/* Further Adventures
 *
 * 1) Run the program.
 *
 * 2) Make a guess.
 *
 * !!! Where has spacer.wrap gone? !!!
 *
 */
<|endoftext|>


<commit_before>// Get URL parameters as an object
// USAGE:
// getAllUrlParams().product; // 'shirt'
// getAllUrlParams().color; // 'blue'
// getAllUrlParams().newuser; // true
// getAllUrlParams().nonexistent; // undefined
// getAllUrlParams('http://test.com/?a=abc').a; // 'abc'

function getAllUrlParams(url) {

  // get query string from url (optional) or window
  var queryString = url ? url.split('?')[1] : window.location.search.slice(1);

  // we'll store the parameters here
  var obj = {};

  // if query string exists
  if (queryString) {

    // stuff after # is not part of query string, so get rid of it
    queryString = queryString.split('#')[0];

    // split our query string into its component parts
    var arr = queryString.split('&');

    for (var i=0; i<arr.length; i++) {
      // separate the keys and the values
      var a = arr[i].split('=');

      // in case params look like: list[]=thing1&list[]=thing2
      var paramNum = undefined;
      var paramName = a[0].replace(/\[\d*\]/, function(v) {
        paramNum = v.slice(1,-1);
        return '';
      });

      // set parameter value (use 'true' if empty)
      var paramValue = typeof(a[1])==='undefined' ? true : a[1];

      // (optional) keep case consistent
      paramName = paramName.toLowerCase();
      paramValue = paramValue.toLowerCase();

      // if parameter name already exists
      if (obj[paramName]) {
        // convert value to array (if still string)
        if (typeof obj[paramName] === 'string') {
          obj[paramName] = [obj[paramName]];
        }
        // if no array index number specified...
        if (typeof paramNum === 'undefined') {
          // put the value on the end of the array
          obj[paramName].push(paramValue);
        }
        // if array index number specified...
        else {
          // put the value at that index number
          obj[paramName][paramNum] = paramValue;
        }
      }
      // if param name doesn't exist yet, set it
      else {
        obj[paramName] = paramValue;
      }
    }
  }

  return obj;
}

// Strip the + delimeters
function stripDelimeters(string) {
	return string.split('+').join(' ');
}

// only run Lunr code on the search page
// as Lunr.min.js is not loaded by default
if (window.location.pathname == "/search/" ) {

	var rawQuery = getAllUrlParams().query;

	// strip + delimeters from query
	var query = stripDelimeters(rawQuery);
	//var query = rawQuery.replace("+"," ");

	var searchresults = document.getElementById("searchresults__query")
	var searchresults__resultslist = document.getElementById("searchresults__resultslist")
	var resultsObj = new Object();
	var htmlstring = "";

	searchresults.innerHTML = query;

	function reqListener() {
	  var obj = JSON.parse(this.responseText);
		var index = lunr.Index.load(obj);
		var searchresults_json = index.search(query + "~1");
		resultsObj = searchresults_json;

		// fetch the path map to lookup title and form the link
		var load_pathmap = new XMLHttpRequest();
		load_pathmap.onload = pmListener;
		load_pathmap.onerror = reqError;
		load_pathmap.open('get', '../pathmap.json', true);
		load_pathmap.send();

	}

	function pmListener() {
		var documentsjson = JSON.parse(this.responseText);

		// form the results list
		resultsObj.forEach (function (result) {

			// lookup the title
			var pagetitle = "";
			var url = "";

			for (var i = 0; i < documentsjson.length; i++ ) {
				if (documentsjson[i].id == result.ref) {
					pagetitle = documentsjson[i].title;
					url = documentsjson[i].path;
					continue;
				}
			}

			htmlstring = htmlstring + "
" + pagetitle + "
";
		});
		searchresults__resultslist.innerHTML = htmlstring;
	}

	function reqError(err) {
	  console.log('Fetch Error :-S', err);
	}

	var load_index = new XMLHttpRequest();
	load_index.onload = reqListener;
	load_index.onerror = reqError;
	load_index.open('get', '../search_index.json', true);
	load_index.send();

}
<commit_msg>Limit to 15 results<commit_after>// Get URL parameters as an object
// USAGE:
// getAllUrlParams().product; // 'shirt'
// getAllUrlParams().color; // 'blue'
// getAllUrlParams().newuser; // true
// getAllUrlParams().nonexistent; // undefined
// getAllUrlParams('http://test.com/?a=abc').a; // 'abc'

function getAllUrlParams(url) {

  // get query string from url (optional) or window
  var queryString = url ? url.split('?')[1] : window.location.search.slice(1);

  // we'll store the parameters here
  var obj = {};

  // if query string exists
  if (queryString) {

    // stuff after # is not part of query string, so get rid of it
    queryString = queryString.split('#')[0];

    // split our query string into its component parts
    var arr = queryString.split('&');

    for (var i=0; i<arr.length; i++) {
      // separate the keys and the values
      var a = arr[i].split('=');

      // in case params look like: list[]=thing1&list[]=thing2
      var paramNum = undefined;
      var paramName = a[0].replace(/\[\d*\]/, function(v) {
        paramNum = v.slice(1,-1);
        return '';
      });

      // set parameter value (use 'true' if empty)
      var paramValue = typeof(a[1])==='undefined' ? true : a[1];

      // (optional) keep case consistent
      paramName = paramName.toLowerCase();
      paramValue = paramValue.toLowerCase();

      // if parameter name already exists
      if (obj[paramName]) {
        // convert value to array (if still string)
        if (typeof obj[paramName] === 'string') {
          obj[paramName] = [obj[paramName]];
        }
        // if no array index number specified...
        if (typeof paramNum === 'undefined') {
          // put the value on the end of the array
          obj[paramName].push(paramValue);
        }
        // if array index number specified...
        else {
          // put the value at that index number
          obj[paramName][paramNum] = paramValue;
        }
      }
      // if param name doesn't exist yet, set it
      else {
        obj[paramName] = paramValue;
      }
    }
  }

  return obj;
}

// Strip the + delimeters
function stripDelimeters(string) {
	return string.split('+').join(' ');
}

// only run Lunr code on the search page
// as Lunr.min.js is not loaded by default
if (window.location.pathname == "/search/" ) {

	var rawQuery = getAllUrlParams().query;

	// strip + delimeters from query
	var query = stripDelimeters(rawQuery);
	//var query = rawQuery.replace("+"," ");

	var searchresults = document.getElementById("searchresults__query")
	var searchresults__resultslist = document.getElementById("searchresults__resultslist")
	var resultsObj = new Object();
	var htmlstring = "";

	searchresults.innerHTML = query;

	function reqListener() {
	  var obj = JSON.parse(this.responseText);
		var index = lunr.Index.load(obj);
		var searchresults_json = index.search(query + "~1");
		resultsObj = searchresults_json;
		//console.log(JSON.stringify(searchresults_json));

		// fetch the path map to lookup title and form the link
		var load_pathmap = new XMLHttpRequest();
		load_pathmap.onload = pmListener;
		load_pathmap.onerror = reqError;
		load_pathmap.open('get', '../pathmap.json', true);
		load_pathmap.send();

	}

	function pmListener() {
		var documentsjson = JSON.parse(this.responseText);
		var resultcount = 0;
		// form the results list
		resultsObj.forEach (function (result) {
			resultcount ++;
			// lookup the title
			var pagetitle = "";
			var url = "";

			for (var i = 0; i < documentsjson.length; i++ ) {
				if (documentsjson[i].id == result.ref) {
					pagetitle = documentsjson[i].title;
					url = documentsjson[i].path;
					continue;
				}
			}
			if (resultcount < 15) {
				htmlstring = htmlstring + "
" + pagetitle + "
";
			}
		});
		searchresults__resultslist.innerHTML = htmlstring;
	}

	function reqError(err) {
	  console.log('Fetch Error :-S', err);
	}

	var load_index = new XMLHttpRequest();
	load_index.onload = reqListener;
	load_index.onerror = reqError;
	load_index.open('get', '../search_index.json', true);
	load_index.send();

}
<|endoftext|>


<commit_before>if (Meteor.isServer) {
  Meteor.methods({
    addGiblet: function () {
      var initActive = false;
      var initFrequency = 1;

      var gibletId = Giblets.insert({
        createdAt: new Date(),
        owner: Meteor.userId(),
        taskname: '',
        url: [undefined],
        keywords: [],
        SMS: false,
        email: false,
        webData: {},
        frequency: initFrequency,
        active: initActive
      });
      if (initActive) {
        Meteor.call('scheduleGiblet', gibletId, initFrequency);
      }
    },
    removeGiblet: function(id) {
      Meteor.call('stopGiblet', id);
      Giblets.remove(id);
    },
    // updateGibletValue: function(id, key, val) {
    //   var updateObj = {};
    //   updateObj[key] = val;
    //   var updateConfirm = Giblets.update({'_id': id}, {$set: updateObj});
    // },
    updateTitle: function(id, value) {
      console.log('update title');
      var giblet = Giblets.findOne({'_id': id});
      Giblets.update({'_id': id}, {$set: {taskname: value}});
    },
    modifyUrlInArray: function(id, urlIndex, value) {
      console.log('modify url server side', id, urlIndex, value);
      var giblet = Giblets.findOne({'_id': id});
      var urlArray = giblet.url;
      urlArray[urlIndex] = value;
      Giblets.update({'_id': id}, {$set: {url: urlArray}});
    },
    addUrlToArray: function(id) {
      var giblet = Giblets.findOne({'_id': id});
      var urlArray = giblet.url;
      urlArray[urlArray.length] = undefined;
      Giblets.update({'_id': id}, {$set: {url: urlArray}});
    },
    removeUrlFromArray: function(id, urlIndex) {
      var giblet = Giblets.findOne({'_id': id});
      var urlArray = giblet.url;
      urlArray.splice(urlIndex, 1);
      var key = 'url';
      Giblets.update({'_id': id}, {$set: {url: urlArray}});
      // Meteor.call('updateGibletValue', id, key, urlArray);
    },
    updateKeywordArray: function() {
      // DO all this
    },
    toggleSmsStatus: function(id) {
      var giblet = Giblets.findOne({'_id': id});
      Giblets.update({'_id': id}, {$set: {SMS: !giblet.SMS}});
    },
    toggleEmailStatus: function(id) {
      var giblet = Giblets.findOne({'_id': id});
      Giblets.update({'_id': id}, {$set: {email: !giblet.email}});
    },
    toggleGibletRunningStatus: function(id) {
        var giblet = Giblets.findOne({'_id': id});
        Giblets.update({'_id': id}, {$set: {active: !giblet.active}});
    },
    updateCronTimer: function(id, cronTime, url) {
      console.log('Update cron timer', id, cronTime);
      Giblets.update({'_id': id}, {$set: {frequency: cronTime}});
      Meteor.call('scheduleGiblet', id, cronTime);
    }
  });
}

if (Meteor.isClient) {

  Template.big_plus.events({
    'click .big_plus_div': function(event) {
      event.preventDefault();
      Meteor.call('addGiblet');
    }
  });

  var enterReminderShow = function(event) {
    var reminder = event.target.form.children[0];
    reminder.style.visibility = 'unset';
  };
  var enterReminderHide = function(event) {
    var reminder = event.target.form.children[0];
    reminder.style.visibility = 'hidden';
  };

  Template.giblet.events({
    // Possibly throttle some of this
    'input .gibletTitleInput, change .gibletTitleInput, paste .gibletTitleInput, mouseup .gibletTitleInput, keyup .gibletTitleInput': function(event) {
      console.log('Title modify client side');
      enterReminderShow(event);

      if (event.which === 13) {
        var gibletId = event.currentTarget.form.attributes['mongoid'].value;
        var newTitle = event.currentTarget.value;
        // console.log(gibletId, newTitle);
        Meteor.call('updateTitle', gibletId, newTitle);
        enterReminderHide(event);
      }
    },
    'input .urlTextInput, change .urlTextInput, paste .urlTextInput, mouseup .urlTextInput, keyup .urlTextInput': function(event) {
      console.log('url modify client side');
      enterReminderShow(event);
      if (event.which === 13) {
        var id = event.currentTarget.form.attributes['mongoid'].value;
        var urlIndex = event.currentTarget.parentNode.attributes['urlIndex'].value;
        var value = event.target.value;
        // console.log('urlindex:', id, urlIndex, value)
        Meteor.call('modifyUrlInArray', id, urlIndex, value);
        enterReminderHide(event);
      }
    },
    'click div.addUrlButton': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('addUrlToArray', id);
    },
    'click div.subtractUrlButton': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      var urlIndex = event.currentTarget.attributes['urlindex'].value;
      // console.log(event);
      Meteor.call('removeUrlFromArray', id, urlIndex);
    },
    'input .keywordInput, change .keywordInput, paste .keywordInput, mouseup .keywordInput, keyup .keywordInput': function(event) {
      if (event.which === 13) {
        console.log('keypress enter keyword');
        // TODO: this also needs to be cleaned up.
        // No referencing parent nodes !!
        var gibletId = event.target.parentNode.parentNode.attributes.gibletId.value;
        var newKeywords = event.target.value;

        var dbTarget = 'keywords';

        var cleanCommaSeperatedString = function(string) {
          var finalKeywords = [];
          var stringArray = string.split(',');
          for( var i = 0; i < stringArray.length; i++ ) {
            var thisEntry = stringArray[i];
            if (thisEntry === '') {
            } else {
              finalKeywords.push( thisEntry.trim() );
            }
          }
          return finalKeywords;
        };
        var keywordArray = cleanCommaSeperatedString(newKeywords);
        // Meteor.call('updateKeywordArray', gibletId, keywordArray);
      }
    },
    'input .cronJobTimer, change .cronJobTimer, paste .cronJobTimer, mouseup .cronJobTimer, keyup .cronJobTimer': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      var input = event.target.value;
      console.log('cron change', id, input);
      if (!input) {
        input = 1;
      }
      Meteor.call('updateCronTimer', id, input);
    },
    'click .smsStatus': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('toggleSmsStatus', id);
    },
    'click .emailStatus': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('toggleEmailStatus', id);
    },
    'click .gibletRunningStatusForm': function(event) {
      // console.log('click client side', event);
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('toggleGibletRunningStatus', id)
    },
    'click .removeGibletButton': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('removeGiblet', id);
    }
  });
}

<commit_msg>implement edtiting and saving keywords<commit_after>if (Meteor.isServer) {
  Meteor.methods({
    addGiblet: function () {
      var initActive = false;
      var initFrequency = 1;

      var gibletId = Giblets.insert({
        createdAt: new Date(),
        owner: Meteor.userId(),
        taskname: '',
        url: [undefined],
        keywords: [],
        SMS: false,
        email: false,
        webData: {},
        frequency: initFrequency,
        active: initActive
      });
      if (initActive) {
        Meteor.call('scheduleGiblet', gibletId, initFrequency);
      }
    },
    removeGiblet: function(id) {
      Meteor.call('stopGiblet', id);
      Giblets.remove(id);
    },
    // updateGibletValue: function(id, key, val) {
    //   var updateObj = {};
    //   updateObj[key] = val;
    //   var updateConfirm = Giblets.update({'_id': id}, {$set: updateObj});
    // },
    updateTitle: function(id, value) {
      console.log('update title');
      var giblet = Giblets.findOne({'_id': id});
      Giblets.update({'_id': id}, {$set: {taskname: value}});
    },
    modifyUrlInArray: function(id, urlIndex, value) {
      console.log('modify url server side', id, urlIndex, value);
      var giblet = Giblets.findOne({'_id': id});
      var urlArray = giblet.url;
      urlArray[urlIndex] = value;
      Giblets.update({'_id': id}, {$set: {url: urlArray}});
    },
    addUrlToArray: function(id) {
      var giblet = Giblets.findOne({'_id': id});
      var urlArray = giblet.url;
      urlArray[urlArray.length] = undefined;
      Giblets.update({'_id': id}, {$set: {url: urlArray}});
    },
    removeUrlFromArray: function(id, urlIndex) {
      var giblet = Giblets.findOne({'_id': id});
      var urlArray = giblet.url;
      urlArray.splice(urlIndex, 1);
      var key = 'url';
      Giblets.update({'_id': id}, {$set: {url: urlArray}});
    },
    updateKeywordArray: function(id, keywordArray) {
      var giblet = Giblets.findOne({'_id': id});
      Giblets.update({'_id': id}, {$set: {keywords: keywordArray}})
    },
    toggleSmsStatus: function(id) {
      var giblet = Giblets.findOne({'_id': id});
      Giblets.update({'_id': id}, {$set: {SMS: !giblet.SMS}});
    },
    toggleEmailStatus: function(id) {
      var giblet = Giblets.findOne({'_id': id});
      Giblets.update({'_id': id}, {$set: {email: !giblet.email}});
    },
    toggleGibletRunningStatus: function(id) {
        var giblet = Giblets.findOne({'_id': id});
        Giblets.update({'_id': id}, {$set: {active: !giblet.active}});
    },
    updateCronTimer: function(id, cronTime, url) {
      console.log('Update cron timer', id, cronTime);
      Giblets.update({'_id': id}, {$set: {frequency: cronTime}});
      Meteor.call('scheduleGiblet', id, cronTime);
    }
  });
}

if (Meteor.isClient) {

  Template.big_plus.events({
    'click .big_plus_div': function(event) {
      event.preventDefault();
      Meteor.call('addGiblet');
    }
  });

  var enterReminderShow = function(event) {
    var reminder = event.target.form.children[0];
    reminder.style.visibility = 'unset';
  };
  var enterReminderHide = function(event) {
    var reminder = event.target.form.children[0];
    reminder.style.visibility = 'hidden';
  };

  Template.giblet.events({
    // Possibly throttle some of this
    'input .gibletTitleInput, change .gibletTitleInput, paste .gibletTitleInput, mouseup .gibletTitleInput, keyup .gibletTitleInput': function(event) {
      console.log('Title modify client side');
      enterReminderShow(event);

      if (event.which === 13) {
        var gibletId = event.currentTarget.form.attributes['mongoid'].value;
        var newTitle = event.currentTarget.value;
        // console.log(gibletId, newTitle);
        Meteor.call('updateTitle', gibletId, newTitle);
        enterReminderHide(event);
      }
    },
    'input .urlTextInput, change .urlTextInput, paste .urlTextInput, mouseup .urlTextInput, keyup .urlTextInput': function(event) {
      console.log('url modify client side');
      enterReminderShow(event);
      if (event.which === 13) {
        var id = event.currentTarget.form.attributes['mongoid'].value;
        var urlIndex = event.currentTarget.parentNode.attributes['urlIndex'].value;
        var value = event.target.value;
        // console.log('urlindex:', id, urlIndex, value)
        Meteor.call('modifyUrlInArray', id, urlIndex, value);
        enterReminderHide(event);
      }
    },
    'click div.addUrlButton': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('addUrlToArray', id);
    },
    'click div.subtractUrlButton': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      var urlIndex = event.currentTarget.attributes['urlindex'].value;
      // console.log(event);
      Meteor.call('removeUrlFromArray', id, urlIndex);
    },
    'input .keywordInput, change .keywordInput, paste .keywordInput, mouseup .keywordInput, keyup .keywordInput': function(event) {
      enterReminderShow(event);

      if (event.which === 13) {
        console.log('keypress enter keyword');

        var id = event.currentTarget.form.attributes['mongoid'].value;
        var newKeywords = event.currentTarget.value;

        var cleanCommaSeperatedString = function(string) {
          var finalKeywords = [];
          var stringArray = string.split(',');
          for( var i = 0; i < stringArray.length; i++ ) {
            var thisEntry = stringArray[i];
            if (thisEntry === '') {
            } else {
              finalKeywords.push( thisEntry.trim() );
            }
          }
          return finalKeywords;
        };
        var keywordArray = cleanCommaSeperatedString(newKeywords);
        Meteor.call('updateKeywordArray', id, keywordArray);
        enterReminderHide(event);
      }
    },
    'input .cronJobTimer, change .cronJobTimer, paste .cronJobTimer, mouseup .cronJobTimer, keyup .cronJobTimer': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      var input = event.target.value;
      console.log('cron change', id, input);
      if (!input) {
        input = 1;
      }
      Meteor.call('updateCronTimer', id, input);
    },
    'click .smsStatus': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('toggleSmsStatus', id);
    },
    'click .emailStatus': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('toggleEmailStatus', id);
    },
    'click .gibletRunningStatusForm': function(event) {
      // console.log('click client side', event);
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('toggleGibletRunningStatus', id)
    },
    'click .removeGibletButton': function(event) {
      var id = event.currentTarget.attributes['mongoid'].value;
      Meteor.call('removeGiblet', id);
    }
  });
}

<|endoftext|>


<commit_before>(function () {

/******************************************************************************/
/* TestCaseResults                                                            */
/******************************************************************************/

TestCaseResults = function (test_case, onEvent, onException, stop_at_offset) {
  var self = this;
  self.test_case = test_case;
  self.onEvent = onEvent;
  self.expecting_failure = false;
  self.current_fail_count = 0;
  self.stop_at_offset = stop_at_offset;
  self.onException = onException;
  self.id = Meteor.uuid();
};

_.extend(TestCaseResults.prototype, {
  ok: function (doc) {
    var self = this;
    var ok = {type: "ok"};
    if (doc)
      ok.details = doc;
    if (self.expecting_failure) {
      ok.details = ok.details || {};
      ok.details["was_expecting_failure"] = true;
      self.expecting_failure = false;
    }
    self.onEvent(ok);
  },

  expect_fail: function () {
    var self = this;
    self.expecting_failure = true;
  },

  fail: function (doc) {
    var self = this;

    if (self.stop_at_offset === 0) {
      if (Meteor.isClient) {
        // Only supported on the browser for now..
        var now = (+new Date);
        debugger;
        if ((+new Date) - now < 100)
          alert("To use this feature, first enable your browser's debugger.");
      }
      self.stop_at_offset = null;
    }
    if (self.stop_at_offset)
      self.stop_at_offset--;

    self.onEvent({
        type: (self.expecting_failure ? "expected_fail" : "fail"),
        details: doc,
        cookie: {name: self.test_case.name, offset: self.current_fail_count,
                 groupPath: self.test_case.groupPath,
                 shortName: self.test_case.shortName}
    });
    self.expecting_failure = false;
    self.current_fail_count++;
  },

  // Call this to fail the test with an exception. Use this to record
  // exceptions that occur inside asynchronous callbacks in tests.
  //
  // It should only be used with asynchronous tests, and if you call
  // this function, you should make sure that (1) the test doesn't
  // call its callback (onComplete function); (2) the test function
  // doesn't directly raise an exception.
  exception: function (exception) {
    this.onException(exception);
  },

  // returns a unique ID for this test run, for convenience use by
  // your tests
  runId: function () {
    return this.id;
  },

  // === Following patterned after http://vowsjs.org/#reference ===

  // XXX eliminate 'message' and 'not' arguments
  equal: function (actual, expected, message, not) {
    /* If expected is a DOM node, do a literal '===' comparison with
     * actual. Otherwise do a deep comparison, as implemented by _.isEqual.
     */

    var matched;
    // XXX remove cruft specific to liverange
    if (typeof expected === "object" && expected && expected.nodeType) {
      matched = expected === actual;
      expected = "[Node]";
      actual = "[Unknown]";
    } else {
      matched = _.isEqual(expected, actual);
      expected = JSON.stringify(expected);
      actual = JSON.stringify(actual);
    }

    if (matched === !!not) {
      this.fail({type: "assert_equal", message: message,
                 expected: expected, actual: actual, not: !!not});
    } else
      this.ok();
  },

  notEqual: function (actual, expected, message) {
    this.equal(actual, expected, message, true);
  },

  instanceOf: function (obj, klass) {
    if (obj instanceof klass)
      this.ok();
    else
      this.fail({type: "instanceOf"}); // XXX what other data?
  },

  // XXX nodejs assert.throws can take an expected error, as a class,
  // regular expression, or predicate function..
  throws: function (f) {
    var actual;

    try {
      f();
    } catch (exception) {
      actual = exception;
    }

    if (actual)
      this.ok({message: actual.message});
    else
      this.fail({type: "throws"});
  },

  isTrue: function (v, msg) {
    if (v)
      this.ok();
    else
      this.fail({type: "true", message: msg});
  },

  isFalse: function (v, msg) {
    if (v)
      this.fail({type: "true", message: msg});
    else
      this.ok();
  },

  isNull: function (v, msg) {
    if (v === null)
      this.ok();
    else
      this.fail({type: "null", message: msg});
  },

  isNotNull: function (v, msg) {
    if (v === null)
      this.fail({type: "true", message: msg});
    else
      this.ok();
  },

  isUndefined: function (v, msg) {
    if (v === undefined)
      this.ok();
    else
      this.fail({type: "undefined", message: msg});
  },

  isNaN: function (v, msg) {
    if (isNaN(v))
      this.ok();
    else
      this.fail({type: "NaN", message: msg});
  },

  include: function (s, v) {
    var pass = false;
    if (s instanceof Array)
      pass = _.indexOf(s, v) !== -1;
    else if (typeof s === "object")
      pass = v in s;
    else if (typeof s === "string")
      for (var i = 0; i < s.length; i++)
        if (s.charAt(i) === v) {
          pass = true;
          break;
        }
    else
      /* fail -- not something that contains other things */;
    if (pass)
      this.ok();
    else
      this.fail({type: "include", sequence: s, should_contain_value: v});
  },

  // XXX should change to lengthOf to match vowsjs
  length: function (obj, expected_length) {
    if (obj.length === expected_length)
      this.ok();
    else
      this.fail({type: "length", expected: expected_length,
                 actual: obj.length});
  }

});

/******************************************************************************/
/* TestCase                                                                   */
/******************************************************************************/

TestCase = function (name, func, async) {
  var self = this;
  self.name = name;
  self.func = func;
  self.async = async || false;

  var nameParts = _.map(name.split(" - "), function(s) {
    return s.replace(/^\s*|\s*$/g, ""); // trim
  });
  self.shortName = nameParts.pop();
  nameParts.unshift("tinytest");
  self.groupPath = nameParts;
};

_.extend(TestCase.prototype, {
  // Run the test asynchronously, delivering results via onEvent;
  // then call onComplete() on success, or else onException(e) if the
  // test raised (or voluntarily reported) an exception.
  run: function (onEvent, onComplete, onException, stop_at_offset) {
    var self = this;

    var completed = false;
    var markComplete = function () {
      if (completed) {
        Meteor._debug("*** Test error -- test '" + self.name +
                      "' returned multiple times.");
        return false;
      }
      completed = true;
      return true;
    };

    var results = new TestCaseResults(self, onEvent,
                                      function (e) {
                                        if (markComplete())
                                          onException(e);
                                      }, stop_at_offset);

    Meteor.defer(function () {
      try {
        if (self.async) {
          self.func(results, function () {
            if (markComplete())
              onComplete();
          });
        } else {
          self.func(results);
          if (markComplete())
            onComplete();
        }
      } catch (e) {
        if (markComplete())
          onException(e);
      }
    });
  }
});

/******************************************************************************/
/* TestManager                                                                */
/******************************************************************************/

TestManager = function () {
  var self = this;
  self.tests = {};
  self.ordered_tests = [];
};

_.extend(TestManager.prototype, {
  addCase: function (test) {
    var self = this;
    if (test.name in self.tests)
      throw new Error("Every test needs a unique name, but there are two tests named '" + name + "'");
    self.tests[test.name] = test;
    self.ordered_tests.push(test);
  },

  createRun: function (onReport) {
    var self = this;
    return new TestRun(self, onReport);
  }
});

// singleton
TestManager = new TestManager;

/******************************************************************************/
/* TestRun                                                                    */
/******************************************************************************/

TestRun = function (manager, onReport) {
  var self = this;
  self.manager = manager;
  self.onReport = onReport;
  self.next_sequence_number = 0;

  _.each(self.manager.ordered_tests, function (test) {
    self._report(test);
  });
};

_.extend(TestRun.prototype, {
  _runOne: function (test, onComplete, stop_at_offset) {
    var self = this;

    var startTime = (+new Date);

    test.run(function (event) {
      /* onEvent */
      self._report(test, event);
    }, function () {
      /* onComplete */
      var totalTime = (+new Date) - startTime;
      self._report(test, {type: "finish", timeMs: totalTime});
      onComplete && onComplete();
    }, function (exception) {
      /* onException */

      // XXX you want the "name" and "message" fields on the
      // exception, to start with..
      self._report(test, {
        type: "exception",
        details: {
          message: exception.message, // XXX empty???
          stack: exception.stack // XXX portability
        }
      });

      onComplete && onComplete();
    }, stop_at_offset);
  },

  run: function (onComplete) {
    var self = this;
    // create array of arrays of tests; synchronous tests in
    // different groups are run in parallel on client, async tests or
    // tests in different groups are run in sequence, as are all
    // tests on server
    var testGroups = _.values(
      _.groupBy(self.manager.ordered_tests,
                function(t) {
                  if (Meteor.isServer)
                    return "SERVER";
                  if (t.async)
                    return "ASYNC";
                  return t.name.split(" - ")[0];
                }));

    if (! testGroups.length) {
      onComplete();
    } else {
      var groupsDone = 0;

      _.each(testGroups, function(tests) {
        var runNext = function () {
          if (tests.length) {
            self._runOne(tests.shift(), runNext);
          } else {
            groupsDone++;
            if (groupsDone >= testGroups.length)
              onComplete();
          }
        };

        runNext();
      });
    }
  },

  // An alternative to run(). Given the 'cookie' attribute of a
  // failure record, try to rerun that particular test up to that
  // failure, and then open the debugger.
  debug: function (cookie, onComplete) {
    var self = this;
    var test = self.manager.tests[cookie.name];
    if (!test)
      throw new Error("No such test '" + cookie.name + "'");
    self._runOne(test, onComplete, cookie.offset);
  },

  _report: function (test, event) {
    var self = this;
    if (event)
      var events = [_.extend({sequence: self.next_sequence_number++}, event)];
    else
      var events = [];
    self.onReport({
      groupPath: test.groupPath,
      test: test.shortName,
      events: events
    });
  }
});

/******************************************************************************/
/* Public API                                                                 */
/******************************************************************************/

var globals = this;
globals.Tinytest = {
  add: function (name, func) {
    TestManager.addCase(new TestCase(name, func));
  },

  addAsync: function (name, func) {
    TestManager.addCase(new TestCase(name, func, true));
  }
};

// Run every test, asynchronously. Runs the test in the current
// process only (if called on the server, runs the tests on the
// server, and likewise for the client.) Report results via
// onReport. Call onComplete when it's done.
Meteor._runTests = function (onReport, onComplete) {
  var testRun = TestManager.createRun(onReport);
  testRun.run(onComplete);
};

// Run just one test case, and stop the debugger at a particular
// error, all as indicated by 'cookie', which will have come from a
// failure event output by _runTests.
Meteor._debugTest = function (cookie, onReport, onComplete) {
  var testRun = TestManager.createRun(onReport);
  testRun.debug(cookie, onComplete);
};

})();
<commit_msg>fix tinytest error message bug<commit_after>(function () {

/******************************************************************************/
/* TestCaseResults                                                            */
/******************************************************************************/

TestCaseResults = function (test_case, onEvent, onException, stop_at_offset) {
  var self = this;
  self.test_case = test_case;
  self.onEvent = onEvent;
  self.expecting_failure = false;
  self.current_fail_count = 0;
  self.stop_at_offset = stop_at_offset;
  self.onException = onException;
  self.id = Meteor.uuid();
};

_.extend(TestCaseResults.prototype, {
  ok: function (doc) {
    var self = this;
    var ok = {type: "ok"};
    if (doc)
      ok.details = doc;
    if (self.expecting_failure) {
      ok.details = ok.details || {};
      ok.details["was_expecting_failure"] = true;
      self.expecting_failure = false;
    }
    self.onEvent(ok);
  },

  expect_fail: function () {
    var self = this;
    self.expecting_failure = true;
  },

  fail: function (doc) {
    var self = this;

    if (self.stop_at_offset === 0) {
      if (Meteor.isClient) {
        // Only supported on the browser for now..
        var now = (+new Date);
        debugger;
        if ((+new Date) - now < 100)
          alert("To use this feature, first enable your browser's debugger.");
      }
      self.stop_at_offset = null;
    }
    if (self.stop_at_offset)
      self.stop_at_offset--;

    self.onEvent({
        type: (self.expecting_failure ? "expected_fail" : "fail"),
        details: doc,
        cookie: {name: self.test_case.name, offset: self.current_fail_count,
                 groupPath: self.test_case.groupPath,
                 shortName: self.test_case.shortName}
    });
    self.expecting_failure = false;
    self.current_fail_count++;
  },

  // Call this to fail the test with an exception. Use this to record
  // exceptions that occur inside asynchronous callbacks in tests.
  //
  // It should only be used with asynchronous tests, and if you call
  // this function, you should make sure that (1) the test doesn't
  // call its callback (onComplete function); (2) the test function
  // doesn't directly raise an exception.
  exception: function (exception) {
    this.onException(exception);
  },

  // returns a unique ID for this test run, for convenience use by
  // your tests
  runId: function () {
    return this.id;
  },

  // === Following patterned after http://vowsjs.org/#reference ===

  // XXX eliminate 'message' and 'not' arguments
  equal: function (actual, expected, message, not) {
    /* If expected is a DOM node, do a literal '===' comparison with
     * actual. Otherwise do a deep comparison, as implemented by _.isEqual.
     */

    var matched;
    // XXX remove cruft specific to liverange
    if (typeof expected === "object" && expected && expected.nodeType) {
      matched = expected === actual;
      expected = "[Node]";
      actual = "[Unknown]";
    } else {
      matched = _.isEqual(expected, actual);
      expected = JSON.stringify(expected);
      actual = JSON.stringify(actual);
    }

    if (matched === !!not) {
      this.fail({type: "assert_equal", message: message,
                 expected: expected, actual: actual, not: !!not});
    } else
      this.ok();
  },

  notEqual: function (actual, expected, message) {
    this.equal(actual, expected, message, true);
  },

  instanceOf: function (obj, klass) {
    if (obj instanceof klass)
      this.ok();
    else
      this.fail({type: "instanceOf"}); // XXX what other data?
  },

  // XXX nodejs assert.throws can take an expected error, as a class,
  // regular expression, or predicate function..
  throws: function (f) {
    var actual;

    try {
      f();
    } catch (exception) {
      actual = exception;
    }

    if (actual)
      this.ok({message: actual.message});
    else
      this.fail({type: "throws"});
  },

  isTrue: function (v, msg) {
    if (v)
      this.ok();
    else
      this.fail({type: "true", message: msg});
  },

  isFalse: function (v, msg) {
    if (v)
      this.fail({type: "true", message: msg});
    else
      this.ok();
  },

  isNull: function (v, msg) {
    if (v === null)
      this.ok();
    else
      this.fail({type: "null", message: msg});
  },

  isNotNull: function (v, msg) {
    if (v === null)
      this.fail({type: "true", message: msg});
    else
      this.ok();
  },

  isUndefined: function (v, msg) {
    if (v === undefined)
      this.ok();
    else
      this.fail({type: "undefined", message: msg});
  },

  isNaN: function (v, msg) {
    if (isNaN(v))
      this.ok();
    else
      this.fail({type: "NaN", message: msg});
  },

  include: function (s, v) {
    var pass = false;
    if (s instanceof Array)
      pass = _.indexOf(s, v) !== -1;
    else if (typeof s === "object")
      pass = v in s;
    else if (typeof s === "string")
      for (var i = 0; i < s.length; i++)
        if (s.charAt(i) === v) {
          pass = true;
          break;
        }
    else
      /* fail -- not something that contains other things */;
    if (pass)
      this.ok();
    else
      this.fail({type: "include", sequence: s, should_contain_value: v});
  },

  // XXX should change to lengthOf to match vowsjs
  length: function (obj, expected_length) {
    if (obj.length === expected_length)
      this.ok();
    else
      this.fail({type: "length", expected: expected_length,
                 actual: obj.length});
  }

});

/******************************************************************************/
/* TestCase                                                                   */
/******************************************************************************/

TestCase = function (name, func, async) {
  var self = this;
  self.name = name;
  self.func = func;
  self.async = async || false;

  var nameParts = _.map(name.split(" - "), function(s) {
    return s.replace(/^\s*|\s*$/g, ""); // trim
  });
  self.shortName = nameParts.pop();
  nameParts.unshift("tinytest");
  self.groupPath = nameParts;
};

_.extend(TestCase.prototype, {
  // Run the test asynchronously, delivering results via onEvent;
  // then call onComplete() on success, or else onException(e) if the
  // test raised (or voluntarily reported) an exception.
  run: function (onEvent, onComplete, onException, stop_at_offset) {
    var self = this;

    var completed = false;
    var markComplete = function () {
      if (completed) {
        Meteor._debug("*** Test error -- test '" + self.name +
                      "' returned multiple times.");
        return false;
      }
      completed = true;
      return true;
    };

    var results = new TestCaseResults(self, onEvent,
                                      function (e) {
                                        if (markComplete())
                                          onException(e);
                                      }, stop_at_offset);

    Meteor.defer(function () {
      try {
        if (self.async) {
          self.func(results, function () {
            if (markComplete())
              onComplete();
          });
        } else {
          self.func(results);
          if (markComplete())
            onComplete();
        }
      } catch (e) {
        if (markComplete())
          onException(e);
      }
    });
  }
});

/******************************************************************************/
/* TestManager                                                                */
/******************************************************************************/

TestManager = function () {
  var self = this;
  self.tests = {};
  self.ordered_tests = [];
};

_.extend(TestManager.prototype, {
  addCase: function (test) {
    var self = this;
    if (test.name in self.tests)
      throw new Error(
        "Every test needs a unique name, but there are two tests named '" +
          test.name + "'");
    self.tests[test.name] = test;
    self.ordered_tests.push(test);
  },

  createRun: function (onReport) {
    var self = this;
    return new TestRun(self, onReport);
  }
});

// singleton
TestManager = new TestManager;

/******************************************************************************/
/* TestRun                                                                    */
/******************************************************************************/

TestRun = function (manager, onReport) {
  var self = this;
  self.manager = manager;
  self.onReport = onReport;
  self.next_sequence_number = 0;

  _.each(self.manager.ordered_tests, function (test) {
    self._report(test);
  });
};

_.extend(TestRun.prototype, {
  _runOne: function (test, onComplete, stop_at_offset) {
    var self = this;

    var startTime = (+new Date);

    test.run(function (event) {
      /* onEvent */
      self._report(test, event);
    }, function () {
      /* onComplete */
      var totalTime = (+new Date) - startTime;
      self._report(test, {type: "finish", timeMs: totalTime});
      onComplete && onComplete();
    }, function (exception) {
      /* onException */

      // XXX you want the "name" and "message" fields on the
      // exception, to start with..
      self._report(test, {
        type: "exception",
        details: {
          message: exception.message, // XXX empty???
          stack: exception.stack // XXX portability
        }
      });

      onComplete && onComplete();
    }, stop_at_offset);
  },

  run: function (onComplete) {
    var self = this;
    // create array of arrays of tests; synchronous tests in
    // different groups are run in parallel on client, async tests or
    // tests in different groups are run in sequence, as are all
    // tests on server
    var testGroups = _.values(
      _.groupBy(self.manager.ordered_tests,
                function(t) {
                  if (Meteor.isServer)
                    return "SERVER";
                  if (t.async)
                    return "ASYNC";
                  return t.name.split(" - ")[0];
                }));

    if (! testGroups.length) {
      onComplete();
    } else {
      var groupsDone = 0;

      _.each(testGroups, function(tests) {
        var runNext = function () {
          if (tests.length) {
            self._runOne(tests.shift(), runNext);
          } else {
            groupsDone++;
            if (groupsDone >= testGroups.length)
              onComplete();
          }
        };

        runNext();
      });
    }
  },

  // An alternative to run(). Given the 'cookie' attribute of a
  // failure record, try to rerun that particular test up to that
  // failure, and then open the debugger.
  debug: function (cookie, onComplete) {
    var self = this;
    var test = self.manager.tests[cookie.name];
    if (!test)
      throw new Error("No such test '" + cookie.name + "'");
    self._runOne(test, onComplete, cookie.offset);
  },

  _report: function (test, event) {
    var self = this;
    if (event)
      var events = [_.extend({sequence: self.next_sequence_number++}, event)];
    else
      var events = [];
    self.onReport({
      groupPath: test.groupPath,
      test: test.shortName,
      events: events
    });
  }
});

/******************************************************************************/
/* Public API                                                                 */
/******************************************************************************/

var globals = this;
globals.Tinytest = {
  add: function (name, func) {
    TestManager.addCase(new TestCase(name, func));
  },

  addAsync: function (name, func) {
    TestManager.addCase(new TestCase(name, func, true));
  }
};

// Run every test, asynchronously. Runs the test in the current
// process only (if called on the server, runs the tests on the
// server, and likewise for the client.) Report results via
// onReport. Call onComplete when it's done.
Meteor._runTests = function (onReport, onComplete) {
  var testRun = TestManager.createRun(onReport);
  testRun.run(onComplete);
};

// Run just one test case, and stop the debugger at a particular
// error, all as indicated by 'cookie', which will have come from a
// failure event output by _runTests.
Meteor._debugTest = function (cookie, onReport, onComplete) {
  var testRun = TestManager.createRun(onReport);
  testRun.debug(cookie, onComplete);
};

})();
<|endoftext|>