<commit_before>'use strict';

angular.module('schedulerApp.controller.task-edit', [
  'ngRoute',
  'ngResource',
  'schedulerApp.db',
])
.controller('TaskEdit', ['$scope', '$location', 'db', '$routeParams', function($scope, $location, db, $routeParams) {

	$scope.actionModels = {};
	$scope.isDescriptionFrozen = false
	$scope.repeatDaily = true

	$scope.duration = ''

	$scope.thingToModel = function(thing) {
		var result = {}
		result.id = thing.id
		result.description = thing.name
		var room = db.rooms[thing.location]
		if (!room) {
			return null
		}
		result.room = room.name
		return result
	}

	$scope.formatTime = function (date, ui) {
	    var h = date.getHours();
	    var m = date.getMinutes();
	    var s = date.getSeconds();
	    var pad = function(d) { return d <= 9 ? '0'+d : d }
	    return ''+pad(h)+":"+pad(m)+(s != 0 || !ui ?  ":"+pad(s) : "")
	}

	$scope.formatDate = function (date) {
	    var y = date.getFullYear();
	    var m = date.getMonth()+1;
	    var d = date.getDate();
	    var pad = function(d) { return d <= 9 ? '0'+d : d }
	    return ''+y+"-"+pad(m)+"-"+pad(d)
	}

	$scope.actionToModel = function(action) {
	    // {
        //    "action" : "turnOn",
        //    "thingID" : "94dc7d1a-aa67-11e4-803a-7c669d02a706",
        //     "type" : "thing-action"
        // }
        //
        // ==>
        //
	    // {
	    //	  "id": thing-id
        //    "description" : "thing description",
        //	  "room": "room description"
        //	  "on": true
        // }
        //

		var result = {}
		switch (action["action"]) {
		case "turnOn":
			result.on = true
			break
		case "turnOff":
			result.on = false
			break
		default:
			console.debug("bad action ", action)
			return null
		}

		if (action.type != "thing-action") {
			console.debug("bad type ", action)
			return null
		}

		if (!action.thingID) {
			console.debug("missing thing id", action)
			return null
		}

		result.id = action.thingID
		var thing = db.things[result.id]
		if (!thing) {
			console.debug("invalid thing id", result.id)
			return null
		}
		result.description = thing.name
		var room = db.rooms[thing.location]
		if (!room) {
			console.debug("invalid room id", thing.location)
			return null
		}
		result.room = room.name
		result.selected = true

		return result
	}

	$scope.task = function() {
		return db.tasks[$routeParams["id"]]
	}

	$scope.timestamp = function(ts) {
		var
			now = new Date(),
			ts = new Date($scope.formatDate(now).replace(/-/g, '/')+" "+(ts ? ts : $scope.timeOfDay))
			if (ts.getFullYear() && ts < now) {
				ts.setDate(ts.getDate()+1)
			}
			return ts
	}

	$scope.save = function() {
		$scope.message = ""
		var rule, param

		switch ($scope.timeOfDay) {
		case "dawn":
		case "dusk":
		case "sunrise":
		case "sunset":
			rule = $scope.timeOfDay
			param = ""
			break
		default:
			var
				ts = $scope.timestamp()

			if (!ts.getFullYear()) {
				$scope.message = "enter a time of the form hh:mm:dd"
				return
			}

			if ($scope.repeatDaily) {
				rule = "time-of-day"
				param = $scope.formatTime(ts, false)
			} else {
				rule = "timestamp"
				param = $scope.formatDate(ts) + " " + $scope.formatTime(ts, false)
			}
			break
		}

		var desc = (($scope.description == '') ? '@ '+$scope.timeOfDay : $scope.description )

		var actions = (function() {
			var
				open=[],
				close=[]
			angular.forEach($scope.actionModels, function(m) {
				if (m.selected) {
					var obj = {
							"type": "thing-action",
							"action": (m.on ? "turnOn" : "turnOff"),
							"thingID": m.id
						}
					open.push(obj)
					obj = angular.copy(obj)
					obj.action = (!m.on? "turnOn" : "turnOff")
					close.push(obj)
				}
			})
			return [open,close]
		}())

		var
			beforeParam

		if ($scope.duration == '') {
			beforeParam = "00:01:00"
			actions[1] = []
		} else {
			beforeParam = $scope.duration
		}

		var task = {
			"id": $routeParams["id"],
			"description": desc,
			"tags": ["simple-ui"],
			"open": actions[0],
			"close": actions[1],
			"window": {
				"after": {
					"rule": rule,
					"param": param
				},
				"before": {
					"rule": "delay",
					"param": beforeParam
				}
			}
		}

		db.save(task).then(
			function() {
				$location.path('/list')
			}, function(m) {
				if (m && m.data) {
					$scope.message = m.data
				} else {
					$scope.message = "An error occurred."
				}
				console.debug(m)
			})
	}

	$scope.delete = function() {
		db.delete($routeParams["id"]).then(
			function() {
				$location.path('/list')
			}, function(m) {
				if (m && m.data) {
					$scope.message = m.data
				} else {
					$scope.message = "An error occurred."
				}
				console.debug(m)
			})
	}

	$scope.cancel = function() {
		$location.path('/list')
	}

	$scope.freezeDescription = function() {
		$scope.isDescriptionFrozen = true
	}

	$scope.toggleSelect = function(model) {
		model.selected = !model.selected
	}

	$scope.toggleActionState = function(model) {
		model.on = ! model.on
	}

	$scope.setRepeatDaily = function(value) {
		$scope.repeatDaily = value;
	}

	$scope.$watch('task()', function(task) {
		angular.forEach(db.things, function(t) {
			var m = $scope.thingToModel(t)
			if (m) {
				$scope.actionModels[t["id"]] = m
			}
		})

		$scope.isDescriptionFrozen = false
		$scope.timeOfDay = $scope.formatTime(new Date(new Date().valueOf()+(60*1000))).substring(0,5)
		$scope.description = '@ '+$scope.timeOfDay
		$scope.repeatDaily = true

		if (!task) {
			// console.debug("new task", $scope)
			return
		}

		$scope.isDescriptionFrozen = true
		$scope.description = task.description

		angular.forEach(task.open, function(action) {
			var model = $scope.actionToModel(action)
			if (model && $scope.actionModels[model.id]) {
				$scope.actionModels[model.id] = model
			} else {
				console.debug("found an action for a thing that no longer exists: ", action)
			}
		})

		if (!task.window || !task.window.before || task.window.before.rule != 'delay') {
			console.debug("invalid before rule", task)
			return
		}

		console.debug(task)
		if (task.close && task.close.length != 0) {
			$scope.duration = task.window.before.param
		}

		switch (task.window.after.rule) {
		case "sunrise":
		case "sunset":
		case "dawn":
		case "dusk":
			$scope.timeOfDay = task.window.after.rule
			break;
		case "time-of-day":
			$scope.timeOfDay = $scope.formatTime($scope.timestamp(task.window.after.param), true)
			break;
		case "timestamp":
			var ts = new Date(task.window.after.param)
			if (ts.getFullYear()) {
				$scope.timeOfDay = $scope.formatTime(ts, true)
				$scope.repeatDaily = false
			}
			break;
		default:
			console.debug("can't edit rule of type: ", task.window.after.rule)
			return
		}

		// the description is frozen iff the generated description does not match the saved description
		$scope.isDescriptionFrozen = ($scope.description != '') && ($scope.description != $scope.generatedDescription())

		// console.debug("loaded task", $scope)

	})

	$scope.generatedDescription = function() {
		var
			base = '@ '+$scope.timeOfDay,
			ts = $scope.timestamp(),
			now = new Date()

		if (!$scope.repeatDaily && ts.getFullYear()) {
			if ($scope.formatDate(now) == $scope.formatDate($scope.timestamp())) {
				return base + ' today'
			} else {
				return base + ' tomorrow'
			}
		} else {
			return base
		}
	}

	$scope.$watch('timeOfDay+repeatDaily', function() {
		if (!$scope.isDescriptionFrozen) {
			$scope.description = $scope.generatedDescription()
		}
	});

}])

<commit_msg>Set the default action for new tasks to the opposite of the current state.<commit_after>'use strict';

angular.module('schedulerApp.controller.task-edit', [
  'ngRoute',
  'ngResource',
  'schedulerApp.db',
])
.controller('TaskEdit', ['$scope', '$location', 'db', '$routeParams', function($scope, $location, db, $routeParams) {

	$scope.actionModels = {};
	$scope.isDescriptionFrozen = false
	$scope.repeatDaily = true

	$scope.duration = ''

	$scope.thingToModel = function(thing) {
		var result = {}
		result.id = thing.id
		result.description = thing.name
		var room = db.rooms[thing.location]
		if (!room) {
			return null
		}
		result.room = room.name

		result.on = !(thing.onOffChannel
			&& thing.onOffChannel.lastState
			&& thing.onOffChannel.lastState.payload == true)
		return result
	}

	$scope.formatTime = function (date, ui) {
	    var h = date.getHours();
	    var m = date.getMinutes();
	    var s = date.getSeconds();
	    var pad = function(d) { return d <= 9 ? '0'+d : d }
	    return ''+pad(h)+":"+pad(m)+(s != 0 || !ui ?  ":"+pad(s) : "")
	}

	$scope.formatDate = function (date) {
	    var y = date.getFullYear();
	    var m = date.getMonth()+1;
	    var d = date.getDate();
	    var pad = function(d) { return d <= 9 ? '0'+d : d }
	    return ''+y+"-"+pad(m)+"-"+pad(d)
	}

	$scope.actionToModel = function(action) {
	    // {
        //    "action" : "turnOn",
        //    "thingID" : "94dc7d1a-aa67-11e4-803a-7c669d02a706",
        //     "type" : "thing-action"
        // }
        //
        // ==>
        //
	    // {
	    //	  "id": thing-id
        //    "description" : "thing description",
        //	  "room": "room description"
        //	  "on": true
        // }
        //

		var result = {}
		switch (action["action"]) {
		case "turnOn":
			result.on = true
			break
		case "turnOff":
			result.on = false
			break
		default:
			console.debug("bad action ", action)
			return null
		}

		if (action.type != "thing-action") {
			console.debug("bad type ", action)
			return null
		}

		if (!action.thingID) {
			console.debug("missing thing id", action)
			return null
		}

		result.id = action.thingID
		var thing = db.things[result.id]
		if (!thing) {
			console.debug("invalid thing id", result.id)
			return null
		}
		result.description = thing.name
		var room = db.rooms[thing.location]
		if (!room) {
			console.debug("invalid room id", thing.location)
			return null
		}
		result.room = room.name
		result.selected = true

		return result
	}

	$scope.task = function() {
		return db.tasks[$routeParams["id"]]
	}

	$scope.timestamp = function(ts) {
		var
			now = new Date(),
			ts = new Date($scope.formatDate(now).replace(/-/g, '/')+" "+(ts ? ts : $scope.timeOfDay))
			if (ts.getFullYear() && ts < now) {
				ts.setDate(ts.getDate()+1)
			}
			return ts
	}

	$scope.save = function() {
		$scope.message = ""
		var rule, param

		switch ($scope.timeOfDay) {
		case "dawn":
		case "dusk":
		case "sunrise":
		case "sunset":
			rule = $scope.timeOfDay
			param = ""
			break
		default:
			var
				ts = $scope.timestamp()

			if (!ts.getFullYear()) {
				$scope.message = "enter a time of the form hh:mm:dd"
				return
			}

			if ($scope.repeatDaily) {
				rule = "time-of-day"
				param = $scope.formatTime(ts, false)
			} else {
				rule = "timestamp"
				param = $scope.formatDate(ts) + " " + $scope.formatTime(ts, false)
			}
			break
		}

		var desc = (($scope.description == '') ? '@ '+$scope.timeOfDay : $scope.description )

		var actions = (function() {
			var
				open=[],
				close=[]
			angular.forEach($scope.actionModels, function(m) {
				if (m.selected) {
					var obj = {
							"type": "thing-action",
							"action": (m.on ? "turnOn" : "turnOff"),
							"thingID": m.id
						}
					open.push(obj)
					obj = angular.copy(obj)
					obj.action = (!m.on? "turnOn" : "turnOff")
					close.push(obj)
				}
			})
			return [open,close]
		}())

		var
			beforeParam

		if ($scope.duration == '') {
			beforeParam = "00:01:00"
			actions[1] = []
		} else {
			beforeParam = $scope.duration
		}

		var task = {
			"id": $routeParams["id"],
			"description": desc,
			"tags": ["simple-ui"],
			"open": actions[0],
			"close": actions[1],
			"window": {
				"after": {
					"rule": rule,
					"param": param
				},
				"before": {
					"rule": "delay",
					"param": beforeParam
				}
			}
		}

		db.save(task).then(
			function() {
				$location.path('/list')
			}, function(m) {
				if (m && m.data) {
					$scope.message = m.data
				} else {
					$scope.message = "An error occurred."
				}
				console.debug(m)
			})
	}

	$scope.delete = function() {
		db.delete($routeParams["id"]).then(
			function() {
				$location.path('/list')
			}, function(m) {
				if (m && m.data) {
					$scope.message = m.data
				} else {
					$scope.message = "An error occurred."
				}
				console.debug(m)
			})
	}

	$scope.cancel = function() {
		$location.path('/list')
	}

	$scope.freezeDescription = function() {
		$scope.isDescriptionFrozen = true
	}

	$scope.toggleSelect = function(model) {
		model.selected = !model.selected
	}

	$scope.toggleActionState = function(model) {
		model.on = ! model.on
	}

	$scope.setRepeatDaily = function(value) {
		$scope.repeatDaily = value;
	}

	$scope.$watch('task()', function(task) {
		angular.forEach(db.things, function(t) {
			var m = $scope.thingToModel(t)
			if (m) {
				$scope.actionModels[t["id"]] = m
			}
		})

		$scope.isDescriptionFrozen = false
		$scope.timeOfDay = $scope.formatTime(new Date(new Date().valueOf()+(60*1000))).substring(0,5)
		$scope.description = '@ '+$scope.timeOfDay
		$scope.repeatDaily = true

		if (!task) {
			// console.debug("new task", $scope)
			return
		}

		$scope.isDescriptionFrozen = true
		$scope.description = task.description

		angular.forEach(task.open, function(action) {
			var model = $scope.actionToModel(action)
			if (model && $scope.actionModels[model.id]) {
				$scope.actionModels[model.id] = model
			} else {
				console.debug("found an action for a thing that no longer exists: ", action)
			}
		})

		if (!task.window || !task.window.before || task.window.before.rule != 'delay') {
			console.debug("invalid before rule", task)
			return
		}

		console.debug(task)
		if (task.close && task.close.length != 0) {
			$scope.duration = task.window.before.param
		}

		switch (task.window.after.rule) {
		case "sunrise":
		case "sunset":
		case "dawn":
		case "dusk":
			$scope.timeOfDay = task.window.after.rule
			break;
		case "time-of-day":
			$scope.timeOfDay = $scope.formatTime($scope.timestamp(task.window.after.param), true)
			break;
		case "timestamp":
			var ts = new Date(task.window.after.param)
			if (ts.getFullYear()) {
				$scope.timeOfDay = $scope.formatTime(ts, true)
				$scope.repeatDaily = false
			}
			break;
		default:
			console.debug("can't edit rule of type: ", task.window.after.rule)
			return
		}

		// the description is frozen iff the generated description does not match the saved description
		$scope.isDescriptionFrozen = ($scope.description != '') && ($scope.description != $scope.generatedDescription())

		// console.debug("loaded task", $scope)

	})

	$scope.generatedDescription = function() {
		var
			base = '@ '+$scope.timeOfDay,
			ts = $scope.timestamp(),
			now = new Date()

		if (!$scope.repeatDaily && ts.getFullYear()) {
			if ($scope.formatDate(now) == $scope.formatDate($scope.timestamp())) {
				return base + ' today'
			} else {
				return base + ' tomorrow'
			}
		} else {
			return base
		}
	}

	$scope.$watch('timeOfDay+repeatDaily', function() {
		if (!$scope.isDescriptionFrozen) {
			$scope.description = $scope.generatedDescription()
		}
	});

}])

<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

function onAvailabilityLoad() {
  const loginInfo = getLoginInfo();
  loginInfo.then(supplyLogoutLinkOrRedirectHome);
  loginInfo.then(getUserOrRedirectRegistration);
  loadAvailabilityTable(availabilityTableDiv(), browserTimezoneOffset());
}

// Toggles a tile from selected (green) to un-selected (white) and vice versa when clicked.
// Scheduled tiles (red) remain unaffected.
function toggleTile(tile) {
  let classList = tile.classList;
  if (classList.contains('selected-time-slot')) {
    classList.remove('table-success', 'selected-time-slot');
    return;
  }
  if (! classList.contains('scheduled-time-slot')) {
    classList.add('table-success', 'selected-time-slot');
  }
}

function loadAvailabilityTable(tableDiv, timezoneOffset) {
  fetch(`/availabilityTable.jsp?timeZoneOffset=${timezoneOffset}&page=${page}`)
    .then(response => response.text())
    .then(tableContents => {
      tableDiv.innerHTML = tableContents;
    });
}

function browserTimezoneOffset() {
  let date = new Date();
  return (-1) * date.getTimezoneOffset();
}

function availabilityTableDiv() {
  return document.getElementById('table-container');
}

function updateAvailability() {
  let selectedSlots = document.getElementsByClassName('selected-time-slot');
  let scheduledSlots = document.getElementsByClassName('scheduled-time-slot');
  let markedSlots = Array.from(selectedSlots).concat(Array.from(scheduledSlots));
  let firstSlot = document.getElementsByTagName('tbody').item(0)
    .firstElementChild.firstElementChild.getAttribute('data-utc');
  let lastSlot = document.getElementsByTagName('tbody').item(0)
    .lastElementChild.lastElementChild.getAttribute('data-utc');
  let requestObject = {
    firstSlot: firstSlot,
    lastSlot: lastSlot,
    markedSlots: markedSlots.map(s => s.getAttribute('data-utc')),
  };
  let requestBody = JSON.stringify(requestObject);
  let request = new Request('/availability', {method: 'PUT', body: requestBody});
  fetch(request).then(unused => {loadAvailabilityTable(availabilityTableDiv(), browserTimezoneOffset())});
}

let page = 0;
const maxWeeksAhead = 3;

function goBack() {
  if (page <= 0) {
    page = 0;
    return;
  }
  page -= 1;
  loadAvailabilityTable(availabilityTableDiv(), browserTimezoneOffset());
}

function goForward() {
  if (page >= maxWeeksAhead) {
    page = maxWeeksAhead;
    return;
  }
  page += 1;
  loadAvailabilityTable(availabilityTableDiv(), browserTimezoneOffset());
}
<commit_msg>Availability refresh (#134)<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

function onAvailabilityLoad() {
  const loginInfo = getLoginInfo();
  loginInfo.then(supplyLogoutLinkOrRedirectHome);
  loginInfo.then(getUserOrRedirectRegistration);
  loadAvailabilityTable(availabilityTableDiv(), browserTimezoneOffset());
}

// Toggles a tile from selected (green) to un-selected (white) and vice versa when clicked.
// Scheduled tiles (red) remain unaffected.
function toggleTile(tile) {
  let classList = tile.classList;
  if (classList.contains('selected-time-slot')) {
    classList.remove('table-success', 'selected-time-slot');
    return;
  }
  if (! classList.contains('scheduled-time-slot')) {
    classList.add('table-success', 'selected-time-slot');
  }
}

function loadAvailabilityTable(tableDiv, timezoneOffset) {
  fetch(`/availabilityTable.jsp?timeZoneOffset=${timezoneOffset}&page=${page}`)
    .then(response => response.text())
    .then(tableContents => {
      tableDiv.innerHTML = tableContents;
    });
}

function browserTimezoneOffset() {
  let date = new Date();
  return (-1) * date.getTimezoneOffset();
}

function availabilityTableDiv() {
  return document.getElementById('table-container');
}

function updateAvailability() {
  let selectedSlots = document.getElementsByClassName('selected-time-slot');
  let scheduledSlots = document.getElementsByClassName('scheduled-time-slot');
  let markedSlots = Array.from(selectedSlots).concat(Array.from(scheduledSlots));
  let firstSlot = document.getElementsByTagName('tbody').item(0)
    .firstElementChild.firstElementChild.getAttribute('data-utc');
  let lastSlot = document.getElementsByTagName('tbody').item(0)
    .lastElementChild.lastElementChild.getAttribute('data-utc');
  let requestObject = {
    firstSlot: firstSlot,
    lastSlot: lastSlot,
    markedSlots: markedSlots.map(s => s.getAttribute('data-utc')),
  };
  let requestBody = JSON.stringify(requestObject);
  let request = new Request('/availability', {method: 'PUT', body: requestBody});
  fetch(request).then(() => location.reload()).catch((error) => {
    alert('Error: ' + error + '\nThere was an error submitting your availability.' +
      ' Please try again.');
    });
}

let page = 0;
const maxWeeksAhead = 3;

function goBack() {
  if (page <= 0) {
    page = 0;
    return;
  }
  page -= 1;
  loadAvailabilityTable(availabilityTableDiv(), browserTimezoneOffset());
}

function goForward() {
  if (page >= maxWeeksAhead) {
    page = maxWeeksAhead;
    return;
  }
  page += 1;
  loadAvailabilityTable(availabilityTableDiv(), browserTimezoneOffset());
}
<|endoftext|>


<commit_before>/*
 *	Copyright 2016 Victorien Elvinger
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 * 	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import test from "ava"
import {Identifier, INT_32_MIN_VALUE, INT_32_MAX_VALUE} from "../lib/identifier.js"

test("from-plain-factory", (t) => {
    const plain = {
        base: [-1, 1, 8],
        last: 0,
    }
    const id = Identifier.fromPlain(plain)

    t.deepEqual(id, plain)
})

test("compare-to-last", (t) => {
    const id1 = new Identifier([], 4)
    const id1Twin = new Identifier([], 4)
    const id2 = new Identifier([], 1)
    const id3 = new Identifier([], 9)

    t.is(id1.compareTo(id1Twin), 0)
    t.not(id1.compareTo(id2), -1)
    t.not(id1.compareTo(id3), 1)
})

test("compare-to-base", (t) => {
    const last = 0
    const id1 = new Identifier([1, 2], last)
    const id1Twin = new Identifier([1, 2], last)
    const id2 = new Identifier([1, 2, 3], last)
    const id3 = new Identifier([1], last)
    const id4 = new Identifier([1, 3], last)
    const id5 = new Identifier([1, 1], last)

    t.is(id1.compareTo(id1Twin), 0)
    t.not(id1.compareTo(id2), 1)
    t.not(id1.compareTo(id3), -1)
    t.not(id1.compareTo(id4), 1)
    t.not(id1.compareTo(id5), -1)
})

test("hasPlaceAfter-same-base", (t) => {
    const id1 = new Identifier([], 0)
    const id2 = new Identifier([], 1)

    t.true(id1.hasPlaceAfter(id2, 1))
    t.false(id1.hasPlaceAfter(id2, 2))

    t.false(id1.hasPlaceAfter(id1, 1))
    t.false(id2.hasPlaceAfter(id1, 1))
})

test("hasPlaceAfter-max-last", (t) => {
    const id1 = new Identifier([0, 0, 0], INT_32_MAX_VALUE - 1)
    const id2 = new Identifier([1, 1, 0], 0)

    t.true(id1.hasPlaceAfter(id2, 1))
    t.false(id1.hasPlaceAfter(id2, 2))
})

test("hasPlaceBefore-same-base", (t) => {
    const id1 = new Identifier([], 0)
    const id2 = new Identifier([], 1)
    const id3 = new Identifier([], 2)

    t.true(id3.hasPlaceBefore(id1, 1))
    t.false(id3.hasPlaceBefore(id1, 2))

    t.false(id1.hasPlaceBefore(id1, 1))
    t.false(id2.hasPlaceBefore(id1, 1))
})

test("hasPlaceBefore-min-last", (t) => {
    const id1 = new Identifier([0, 0, 0], 0)
    const id2 = new Identifier([1, 0, 0], INT_32_MIN_VALUE + 2)

    t.true(id2.hasPlaceBefore(id1, 1))
    t.false(id2.hasPlaceBefore(id1, 2))
})
<commit_msg>test(identifier): Add tests for maxOffsetBeforeNext() and minOffsetAfterPrev()<commit_after>/*
 *	Copyright 2016 Victorien Elvinger
 *
 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 * 	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import test from "ava"
import {Identifier, INT_32_MIN_VALUE, INT_32_MAX_VALUE} from "../lib/identifier.js"

test("from-plain-factory", (t) => {
    const plain = {
        base: [-1, 1, 8],
        last: 0,
    }
    const id = Identifier.fromPlain(plain)

    t.deepEqual(id, plain)
})

test("compare-to-last", (t) => {
    const id1 = new Identifier([], 4)
    const id1Twin = new Identifier([], 4)
    const id2 = new Identifier([], 1)
    const id3 = new Identifier([], 9)

    t.is(id1.compareTo(id1Twin), 0)
    t.not(id1.compareTo(id2), -1)
    t.not(id1.compareTo(id3), 1)
})

test("compare-to-base", (t) => {
    const last = 0
    const id1 = new Identifier([1, 2], last)
    const id1Twin = new Identifier([1, 2], last)
    const id2 = new Identifier([1, 2, 3], last)
    const id3 = new Identifier([1], last)
    const id4 = new Identifier([1, 3], last)
    const id5 = new Identifier([1, 1], last)

    t.is(id1.compareTo(id1Twin), 0)
    t.not(id1.compareTo(id2), 1)
    t.not(id1.compareTo(id3), -1)
    t.not(id1.compareTo(id4), 1)
    t.not(id1.compareTo(id5), -1)
})

test("hasPlaceAfter-same-base", (t) => {
    const id1 = new Identifier([], 0)
    const id2 = new Identifier([], 1)

    t.true(id1.hasPlaceAfter(id2, 1))
    t.false(id1.hasPlaceAfter(id2, 2))

    t.false(id1.hasPlaceAfter(id1, 1))
    t.false(id2.hasPlaceAfter(id1, 1))
})

test("hasPlaceAfter-max-last", (t) => {
    const id1 = new Identifier([0, 0, 0], INT_32_MAX_VALUE - 1)
    const id2 = new Identifier([1, 1, 0], 0)

    t.true(id1.hasPlaceAfter(id2, 1))
    t.false(id1.hasPlaceAfter(id2, 2))
})

test("hasPlaceBefore-same-base", (t) => {
    const id1 = new Identifier([], 0)
    const id2 = new Identifier([], 1)
    const id3 = new Identifier([], 2)

    t.true(id3.hasPlaceBefore(id1, 1))
    t.false(id3.hasPlaceBefore(id1, 2))

    t.false(id1.hasPlaceBefore(id1, 1))
    t.false(id2.hasPlaceBefore(id1, 1))
})

test("hasPlaceBefore-min-last", (t) => {
    const id1 = new Identifier([0, 0, 0], 0)
    const id2 = new Identifier([1, 0, 0], INT_32_MIN_VALUE + 2)

    t.true(id2.hasPlaceBefore(id1, 1))
    t.false(id2.hasPlaceBefore(id1, 2))
})

test.failing("maxOffsetBeforeNext-same-base", (t) => {
  const id1 = new Identifier([0, 0, 0], 3)
  const id2 = new Identifier([0, 0, 0], 5)

  const expected = 4
  const actual = id1.maxOffsetBeforeNext(id2, 10)

  t.is(actual, expected)
})

test("maxOffsetBeforeNext-base-is-prefix", (t) => {
  const id1 = new Identifier([0, 0, 0], 3)
  const id2 = new Identifier([0, 0, 0, 5, 0, 0, 1], 0)

  const expected = 5
  const actual = id1.maxOffsetBeforeNext(id2, 10)

  t.is(actual, expected)
})

test("minOffsetAfterPrev-same-base", (t) => {
  const id1 = new Identifier([0, 0, 0], 3)
  const id2 = new Identifier([0, 0, 0], 5)

  const expected = 4
  const actual = id2.minOffsetAfterPrev(id1, 0)

  t.is(actual, expected)
})

test("minOffsetAfterPrev-base-is-prefix", (t) => {
  const id1 = new Identifier([0, 0, 0, 3, 0, 0, 1], 0)
  const id2 = new Identifier([0, 0, 0], 5)

  const expected = 4
  const actual = id2.minOffsetAfterPrev(id1, 0)

  t.is(actual, expected)
})
<|endoftext|>


<commit_before>'use strict'

/* global describe */
/* global it */

var assert = require('chai').assert
var createIndex = require('../src/mindex')

var testdb = [
  { age: 46,
  id: 'Lamar Goodwin',
  phone: '817-529-2557',
  title: 'Global Usability Coordinator' },
  { age: 13,
  id: 'Elmo Hansen',
  phone: '1-632-301-4062 x8351',
  title: 'Customer Paradigm Assistant' },
  { age: 43,
  id: 'Ashton Oberbrunner',
  phone: '1-292-015-9298 x19171',
  title: 'Legacy Security Planner' },
  { age: 32,
  id: 'Ms. Kiera Hodkiewicz',
  phone: '1-742-905-8677',
  title: 'Legacy Security Planner' },
  { age: 10,
  id: 'Hilda O\'Kon',
  phone: '118.357.9132 x76245',
  title: 'Dynamic Communications Agent' },
  { age: 38,
  id: 'Leland Bahringer',
  phone: '114.754.5482 x7853',
  title: 'Internal Program Officer' },
  { age: 21,
  id: 'Axel Block',
  phone: '816-557-8326 x083',
  title: 'Forward Interactions Liason' },
  { age: 14,
  id: 'Wendy Dare',
  phone: '966.968.5997 x42838',
  title: 'Infrastructure Associate' },
  { age: 23,
  id: 'Miss Fermin Bartell',
  phone: '(003) 694-6712',
  title: 'Product Applications Designer' },
  { age: 68,
  id: 'Marquise Weimann',
  phone: '400.157.6206',
  title: 'Corporate Response Orchestrator' },
  { age: 24,
  id: 'Kaley Jones',
  phone: '1-426-266-8041',
  title: 'District Brand Producer' },
  { age: 51,
  id: 'Dr. Jess Stokes',
  phone: '1-754-630-5989 x8753',
  title: 'Chief Tactics Supervisor' },
  { age: 24,
  id: 'Durward Runolfsson',
  phone: '726.255.5565',
  title: 'Regional Configuration Planner' },
  { age: 64,
  id: 'Clemens Howell Dr.',
  phone: '1-926-168-6208',
  title: 'Global Communications Orchestrator' },
  { age: 22,
  id: 'Catherine Predovic',
  phone: '206-479-6915 x835',
  title: 'Dynamic Accountability Architect' },
  { age: 36,
  id: 'Odie Reichel',
  phone: '(695) 562-6049 x68079',
  title: 'Forward Configuration Representative' },
  { age: 32,
  id: 'Wilfredo Strosin',
  phone: '071.478.7926',
  title: 'Dynamic Web Consultant' },
  { age: 8,
  id: 'Makayla McLaughlin',
  phone: '1-667-221-6294 x87922',
  title: 'Implementation Facilitator' },
  { age: 41,
  id: 'Ardella O\'Conner',
  phone: '1-927-933-8004',
  title: 'Product Operations Supervisor' },
  { age: 8,
  id: 'Magdalen Zulauf Mr.',
  phone: '992-726-6046 x72367',
  title: 'Central Accountability Manager' },
  { age: 62,
  id: 'Santino Kuvalis',
  phone: '(297) 534-9135',
  title: 'Direct Accounts Analyst' },
  { age: 48,
  id: 'Elva Graham',
  phone: '229.798.4078 x4705',
  title: 'International Mobility Facilitator' },
  { age: 16,
  id: 'Lesley Howe',
  phone: '(829) 112-7415 x2891',
  title: 'Internal Response Agent' },
  { age: 49,
  id: 'Antonio Monahan Mr.',
  phone: '(682) 162-2301',
  title: 'Integration Technician' },
  { age: 3,
  id: 'Shana Lubowitz',
  phone: '849-809-2691 x787',
  title: 'Senior Detector Agent' },
  { age: 1,
  id: 'Serena Bruen',
  phone: '1-070-021-2968',
  title: 'Senior Detector Agent' },
  { age: undefined,
  id: 'Clemmie Powlowski',
  phone: '1-796-310-8197 x253',
  title: 'Senior Detector Agent' },
  { age: 2,
  id: 'Albertha Simonis Ms.',
  phone: '1-421-993-2782 x073',
  title: 'Senior Fax Administrator' },
  { age: 4,
  id: 'Kavon Hammes',
  phone: '(913) 113-1961 x68847',
  title: 'Senior Identity Engineer' },
  { age: 5,
  id: 'Kyle MacGyver',
  phone: '333-464-6778 x7218',
  title: 'Senior Klingon Consultant' }
]

describe('mindex', function () {
  describe('simple keys', function () {
    let index

    before(function () {
      index = createIndex(['age'])
      testdb.forEach(function (record) {
        index.insertRecord(record)
      })
    })

    it('insertRecord does not error', function (done) {
      testdb.forEach(function (record) {
        index.insertRecord(record)
      })
      done()
    })

    it('get returns correct result', function (done) {
      let result = index.get(1)
      assert.deepEqual(result, ['Serena Bruen'])
      done()
    })

    it('between returns correct result', function (done) {
      let result = index.between([1], [4])
      assert.deepEqual(result, ['Serena Bruen', 'Albertha Simonis Ms.', 'Shana Lubowitz'])
      done()
    })

    it('clear removes all data', function (done) {
      index.clear()
      let result = index.getAll()
      assert.deepEqual(result, [])
      done()
    })
  })

  describe('compound keys', function () {
    let index = createIndex(['title', 'age'])

    it('insertRecord does not error', (done) => {
      testdb.forEach(function (record) {
        index.insertRecord(record)
      })
      done()
    })

    it('get returns correct result one level deep', (done) => {
      let result = index.get('Senior Detector Agent')
      assert.deepEqual(result, ['Clemmie Powlowski', 'Serena Bruen', 'Shana Lubowitz'])
      done()
    })

    it('get returns correct result two levels deep', (done) => {
      let result = index.get(['Senior Detector Agent', 1])
      assert.deepEqual(result, ['Serena Bruen'])
      done()
    })

    it('between returns correct result for one level deep', (done) => {
      let result = index.between(['Internal Response Agent'], ['Senior Detector Agent'], {rightInclusive: true})
      assert.deepEqual(result, ['Lesley Howe', 'Elva Graham', 'Ms. Kiera Hodkiewicz', 'Ashton Oberbrunner', 'Miss Fermin Bartell', 'Ardella O\'Conner', 'Durward Runolfsson', 'Clemmie Powlowski', 'Serena Bruen', 'Shana Lubowitz'])
      done()
    })

    it('between returns correct result for two levels deep', (done) => {
      let result = index.between(['Internal Response Agent', 20], ['Senior Detector Agent', 3], {rightInclusive: true, offset: 1})
      assert.deepEqual(result, ['Ms. Kiera Hodkiewicz', 'Ashton Oberbrunner', 'Miss Fermin Bartell', 'Ardella O\'Conner', 'Durward Runolfsson', 'Clemmie Powlowski', 'Serena Bruen', 'Shana Lubowitz'])
      done()
    })

    it("Query {'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', 3], offset: 2, limit: 5}", (done) => {
      let result = index.query({'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', 3], offset: 2, limit: 5})
      assert.deepEqual(result, ['Ashton Oberbrunner', 'Miss Fermin Bartell', 'Ardella O\'Conner', 'Durward Runolfsson', 'Clemmie Powlowski'])
      done()
    })

    it("Query {'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', 3], offset: 1}", (done) => {
      let result = index.query({'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', undefined], offset: 1})
      assert.deepEqual(result, ['Ms. Kiera Hodkiewicz', 'Ashton Oberbrunner', 'Miss Fermin Bartell', 'Ardella O\'Conner', 'Durward Runolfsson', 'Clemmie Powlowski', 'Serena Bruen', 'Shana Lubowitz'])
      done()
    })
  })
})
<commit_msg>add more tests<commit_after>'use strict'

/* global describe */
/* global it */
/* global before */

var assert = require('chai').assert
var createIndex = require('../src/mindex')

var testdb = [
  { age: 46,
  id: 'Lamar Goodwin',
  phone: '817-529-2557',
  title: 'Global Usability Coordinator' },
  { age: 13,
  id: 'Elmo Hansen',
  phone: '1-632-301-4062 x8351',
  title: 'Customer Paradigm Assistant' },
  { age: 43,
  id: 'Ashton Oberbrunner',
  phone: '1-292-015-9298 x19171',
  title: 'Legacy Security Planner' },
  { age: 32,
  id: 'Ms. Kiera Hodkiewicz',
  phone: '1-742-905-8677',
  title: 'Legacy Security Planner' },
  { age: 10,
  id: 'Hilda O\'Kon',
  phone: '118.357.9132 x76245',
  title: 'Dynamic Communications Agent' },
  { age: 38,
  id: 'Leland Bahringer',
  phone: '114.754.5482 x7853',
  title: 'Internal Program Officer' },
  { age: 21,
  id: 'Axel Block',
  phone: '816-557-8326 x083',
  title: 'Forward Interactions Liason' },
  { age: 14,
  id: 'Wendy Dare',
  phone: '966.968.5997 x42838',
  title: 'Infrastructure Associate' },
  { age: 23,
  id: 'Miss Fermin Bartell',
  phone: '(003) 694-6712',
  title: 'Product Applications Designer' },
  { age: 68,
  id: 'Marquise Weimann',
  phone: '400.157.6206',
  title: 'Corporate Response Orchestrator' },
  { age: 24,
  id: 'Kaley Jones',
  phone: '1-426-266-8041',
  title: 'District Brand Producer' },
  { age: 51,
  id: 'Dr. Jess Stokes',
  phone: '1-754-630-5989 x8753',
  title: 'Chief Tactics Supervisor' },
  { age: 24,
  id: 'Durward Runolfsson',
  phone: '726.255.5565',
  title: 'Regional Configuration Planner' },
  { age: 64,
  id: 'Clemens Howell Dr.',
  phone: '1-926-168-6208',
  title: 'Global Communications Orchestrator' },
  { age: 22,
  id: 'Catherine Predovic',
  phone: '206-479-6915 x835',
  title: 'Dynamic Accountability Architect' },
  { age: 36,
  id: 'Odie Reichel',
  phone: '(695) 562-6049 x68079',
  title: 'Forward Configuration Representative' },
  { age: 32,
  id: 'Wilfredo Strosin',
  phone: '071.478.7926',
  title: 'Dynamic Web Consultant' },
  { age: 8,
  id: 'Makayla McLaughlin',
  phone: '1-667-221-6294 x87922',
  title: 'Implementation Facilitator' },
  { age: 41,
  id: 'Ardella O\'Conner',
  phone: '1-927-933-8004',
  title: 'Product Operations Supervisor' },
  { age: 8,
  id: 'Magdalen Zulauf Mr.',
  phone: '992-726-6046 x72367',
  title: 'Central Accountability Manager' },
  { age: 62,
  id: 'Santino Kuvalis',
  phone: '(297) 534-9135',
  title: 'Direct Accounts Analyst' },
  { age: 48,
  id: 'Elva Graham',
  phone: '229.798.4078 x4705',
  title: 'International Mobility Facilitator' },
  { age: 16,
  id: 'Lesley Howe',
  phone: '(829) 112-7415 x2891',
  title: 'Internal Response Agent' },
  { age: 49,
  id: 'Antonio Monahan Mr.',
  phone: '(682) 162-2301',
  title: 'Integration Technician' },
  { age: 3,
  id: 'Shana Lubowitz',
  phone: '849-809-2691 x787',
  title: 'Senior Detector Agent' },
  { age: 1,
  id: 'Serena Bruen',
  phone: '1-070-021-2968',
  title: 'Senior Detector Agent' },
  { age: undefined,
  id: 'Clemmie Powlowski',
  phone: '1-796-310-8197 x253',
  title: 'Senior Detector Agent' },
  { age: 2,
  id: 'Albertha Simonis Ms.',
  phone: '1-421-993-2782 x073',
  title: 'Senior Fax Administrator' },
  { age: 4,
  id: 'Kavon Hammes',
  phone: '(913) 113-1961 x68847',
  title: 'Senior Identity Engineer' },
  { age: 5,
  id: 'Kyle MacGyver',
  phone: '333-464-6778 x7218',
  title: 'Senior Klingon Consultant' }
]

describe('mindex', function () {
  describe('simple keys', function () {
    let index

    before(function () {
      index = createIndex(['age'])
      testdb.forEach(function (record) {
        index.insertRecord(record)
      })
    })

    it('insertRecord does not error', function (done) {
      testdb.forEach(function (record) {
        index.insertRecord(record)
      })
      done()
    })

    it('get returns correct result', function (done) {
      let result = index.get(1)
      assert.deepEqual(result, ['Serena Bruen'])
      done()
    })

    it('between returns correct result', function (done) {
      let result = index.between([1], [4])
      assert.deepEqual(result, ['Serena Bruen', 'Albertha Simonis Ms.', 'Shana Lubowitz'])
      done()
    })

    it('clear removes all data', function (done) {
      index.clear()
      let result = index.getAll()
      assert.deepEqual(result, [])
      done()
    })
  })

  describe('compound keys', function () {
    let index = createIndex(['title', 'age'])

    it('insertRecord does not error', (done) => {
      testdb.forEach(function (record) {
        index.insertRecord(record)
      })
      done()
    })

    it('get returns correct result one level deep', (done) => {
      let result = index.get('Senior Detector Agent')
      assert.deepEqual(result, ['Clemmie Powlowski', 'Serena Bruen', 'Shana Lubowitz'])
      done()
    })

    it('get returns correct result two levels deep', (done) => {
      let result = index.get(['Senior Detector Agent', 1])
      assert.deepEqual(result, ['Serena Bruen'])
      done()
    })

    it('getAll returns correct result', (done) => {
      let result = index.getAll()
      assert.deepEqual(result, [
        'Magdalen Zulauf Mr.',
        'Dr. Jess Stokes',
        'Marquise Weimann',
        'Elmo Hansen',
        'Santino Kuvalis',
        'Kaley Jones',
        'Catherine Predovic',
        'Hilda O\'Kon',
        'Wilfredo Strosin',
        'Odie Reichel',
        'Axel Block',
        'Clemens Howell Dr.',
        'Lamar Goodwin',
        'Makayla McLaughlin',
        'Wendy Dare',
        'Antonio Monahan Mr.',
        'Leland Bahringer',
        'Lesley Howe',
        'Elva Graham',
        'Ms. Kiera Hodkiewicz',
        'Ashton Oberbrunner',
        'Miss Fermin Bartell',
        'Ardella O\'Conner',
        'Durward Runolfsson',
        'Clemmie Powlowski',
        'Serena Bruen',
        'Shana Lubowitz',
        'Albertha Simonis Ms.',
        'Kavon Hammes',
        'Kyle MacGyver'
      ])
      done()
    })

    it('getAll returns correct result in descending order', (done) => {
      let result = index.getAll({order: 'desc'})
      assert.deepEqual(result, [
        'Kyle MacGyver',
        'Kavon Hammes',
        'Albertha Simonis Ms.',
        'Shana Lubowitz',
        'Serena Bruen',
        'Clemmie Powlowski',
        'Durward Runolfsson',
        'Ardella O\'Conner',
        'Miss Fermin Bartell',
        'Ashton Oberbrunner',
        'Ms. Kiera Hodkiewicz',
        'Elva Graham',
        'Lesley Howe',
        'Leland Bahringer',
        'Antonio Monahan Mr.',
        'Wendy Dare',
        'Makayla McLaughlin',
        'Lamar Goodwin',
        'Clemens Howell Dr.',
        'Axel Block',
        'Odie Reichel',
        'Wilfredo Strosin',
        'Hilda O\'Kon',
        'Catherine Predovic',
        'Kaley Jones',
        'Santino Kuvalis',
        'Elmo Hansen',
        'Marquise Weimann',
        'Dr. Jess Stokes',
        'Magdalen Zulauf Mr.'
      ])
      done()
    })

    it('between returns correct result for one level deep', (done) => {
      let result = index.between(['Internal Response Agent'], ['Senior Detector Agent'], {rightInclusive: true})
      assert.deepEqual(result, ['Lesley Howe', 'Elva Graham', 'Ms. Kiera Hodkiewicz', 'Ashton Oberbrunner', 'Miss Fermin Bartell', 'Ardella O\'Conner', 'Durward Runolfsson', 'Clemmie Powlowski', 'Serena Bruen', 'Shana Lubowitz'])
      done()
    })

    it('between returns correct result for two levels deep', (done) => {
      let result = index.between(['Internal Response Agent', 20], ['Senior Detector Agent', 3], {rightInclusive: true, offset: 1})
      assert.deepEqual(result, ['Ms. Kiera Hodkiewicz', 'Ashton Oberbrunner', 'Miss Fermin Bartell', 'Ardella O\'Conner', 'Durward Runolfsson', 'Clemmie Powlowski', 'Serena Bruen', 'Shana Lubowitz'])
      done()
    })

    it("Query {'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', 3], offset: 2, limit: 5}", (done) => {
      let result = index.query({'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', 3], offset: 2, limit: 5})
      assert.deepEqual(result, ['Ashton Oberbrunner', 'Miss Fermin Bartell', 'Ardella O\'Conner', 'Durward Runolfsson', 'Clemmie Powlowski'])
      done()
    })

    it("Query {'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', undefined], offset: 1}", (done) => {
      let result = index.query({'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', undefined], offset: 1})
      assert.deepEqual(result, ['Ms. Kiera Hodkiewicz', 'Ashton Oberbrunner', 'Miss Fermin Bartell', 'Ardella O\'Conner', 'Durward Runolfsson', 'Clemmie Powlowski', 'Serena Bruen', 'Shana Lubowitz'])
      done()
    })

    it("Query {'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', undefined], offset: 1, order: 'desc'}", (done) => {
      let result = index.query({'>': ['Internal Response Agent', 20], '<=': ['Senior Detector Agent', undefined], offset: 1, order: 'desc'})
      assert.deepEqual(result, ['Serena Bruen', 'Clemmie Powlowski', 'Durward Runolfsson', 'Ardella O\'Conner', 'Miss Fermin Bartell', 'Ashton Oberbrunner', 'Ms. Kiera Hodkiewicz', 'Elva Graham'])
      done()
    })

    it("Query {limit: 1, order: 'desc'}", (done) => {
      let result = index.query({limit: 1, order: 'desc'})
      assert.deepEqual(result, ['Kyle MacGyver'])
      done()
    })

    it("Query {limit: 1, order: 'asc'}", (done) => {
      let result = index.query({limit: 1, order: 'asc'})
      assert.deepEqual(result, ['Magdalen Zulauf Mr.'])
      done()
    })
  })
})
<|endoftext|>


<commit_before>/*	--------------------------------------------------
	:: Handlebars Helpers
	--------------------------------------------------

	Atomic Handlebars Helpers
	http://atomicinfotech.com/atomic-handlebars-helpers
	http://github.com/atomicinfotech/atomic-handlebars-helpers

	---------------------------------------------------

	The MIT License (MIT)

	Copyright (c) 2013 Atomic Infotech

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in
	the Software without restriction, including without limitation the rights to
	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
	the Software, and to permit persons to whom the Software is furnished to do so,
	subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
	FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
	COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
	IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
	CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
	*/

$(document).ready(function() {

	// For debugging values inside of a template
	Handlebars.registerHelper('log', function(context) {
		if(console && (typeof console.log === 'function')){
			console.log(context);
		}
		return '';
	});

	Handlebars.registerHelper('uriEncode',function(string) {
		return encodeURIComponent(string);
	});

	// HELPER: #key_value
	//
	// Usage: {{#key_value obj}} Key: {{key}} // Value: {{value}} {{/key_value}}
	//
	// Iterate over an object, setting 'key' and 'value' for each property in
	// the object.
	Handlebars.registerHelper("key_value", function(obj, options) {
		var buffer = "",
			key;

		for (key in obj) {
			if (obj.hasOwnProperty(key)) {
				buffer += options.fn({key: key, value: obj[key]});
			}
		}

		return buffer;
	});

	// HELPER: #each_with_key
	//
	// Usage: {{#each_with_key container key="myKey"}}...{{/each_with_key}}
	//
	// Iterate over an object containing other objects. Each
	// inner object will be used in turn, with an added key ("myKey")
	// set to the value of the inner object's key in the container.
	Handlebars.registerHelper("each_with_key", function(obj, options) {
		var context,
			buffer = "",
			key,
			keyName = options.hash.key;
		for (key in obj) {
			if (obj.hasOwnProperty(key)) {
				context = obj[key];
				if (keyName) {
					context[keyName] = key;
				}
				buffer += options.fn(context);
			}
		}

		return buffer;
	});

	// Indexed version of each (only works with arrays)
	Handlebars.registerHelper('each_i', function(context, options) {
		var ret = '';
		if (context) {
			for(var i=0, j=context.length; i<j; i++) {
				context[i]._index = i;
				ret += options.fn(context[i]);
			}
		}
		return ret;
	});

	// Keyed version on each (works with arrays and other objects)
	Handlebars.registerHelper('each_k', function(/* obj,key,ctx */) {
		var obj,key,ctx,ret;

		arguments = Array.prototype.slice.call(arguments);

		ctx = arguments.pop();

		obj = arguments[0] || {};
		key = arguments[1] || '_k';
		ret = '';

		for (var x in obj) {
			obj[x][key] = x;
			ret += ctx.fn(obj[x]);
		}

		return ret;
	});

	Handlebars.registerHelper("apply", function(templateName, data) {
		var t = HandleBarsTemplates[templateName];
		if(typeof t == 'undefined')
			return new Handlebars.SafeString('');

		return new Handlebars.SafeString(t(data));
	});

	Handlebars.registerHelper("eachWeekday", function(indexed_obj, options) {

		var names = [
			{ full: "Sunday", short: "Sun", caps: "SUN", small: "sun" },
			{ full: "Monday", short: "Mon", caps: "MON", small: "mon" },
			{ full: "Tuesday", short: "Tue", caps: "TUE", small: "tue" },
			{ full: "Wednesday", short: "Wed", caps: "WED", small: "wed" },
			{ full: "Thursday", short: "Thu", caps: "THU", small: "thu" },
			{ full: "Friday", short: "Fri", caps: "FRI", small: "fri" },
			{ full: "Saturday", short: "Sat", caps: "SAT", small: "sat" },
		];

		var buffer = "";

		for(var i = 0; i <= 6; i++) {
			buffer += options.fn({
				dayNames: names[i],
				dayData: indexed_obj[i],
				index: i,
				indexOB: i+1,
			});
		}

		return buffer;
	});

	Handlebars.registerHelper("int", function(obj) {
		var n = parseInt(obj, 10);
		return typeof n == "number" && !isNaN(n) ? n : obj;
	});

	Handlebars.registerHelper("YesNo", function(x) {
		return x == true ? 'Yes' : 'No';
	});

	Handlebars.registerHelper("yesno", function(x) {
		return x == true ? 'yes' : 'no';
	});

	// not the prettiest, but it's good enough
	Handlebars.registerHelper("phone", function(obj) {
		if(typeof obj != 'string') return obj;
		var l = obj.length;
		if(l <= 7) return obj.replace(/(.*)(....)/, '$1-$2');
		if(l <= 10) return obj.replace(/(.*)(...)(....)/, '$1-$2-$3');
		if(l > 10) return obj.replace(/(.*)(...)(...)(....)/, '$1-$2-$3-$4');
	});

	Handlebars.registerHelper("%", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(1) : obj;
	});

	Handlebars.registerHelper("%%", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? (n.toMoney(1) + '%') : obj;
	});

	Handlebars.registerHelper("$", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(0) : obj;
	});

	Handlebars.registerHelper("cents", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(2) : obj;
	});

	Handlebars.registerHelper("$$", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(0, '.', ',', '$') : obj;
	});

	Handlebars.registerHelper("$cents", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(2, '.', ',', '$') : obj;
	});

	// "plus/minus"
	Handlebars.registerHelper("pm", function(obj) {
		return typeof parseFloat(obj) == "number" ? (obj >= 0 ? 'good' : 'bad') : '';
	});

	// "plus/minus (inverted)"
	Handlebars.registerHelper("pmi", function(obj) {
		return typeof parseFloat(obj) == "number" ? (obj >= 0 ? 'bad' : 'good') : '';
	});

	Handlebars.registerHelper("ago", function(context,formatString) {
		var isTimestamp,when;

		if (typeof context === 'number') {
			when = moment.unix(context);
		} else {
			isTimestamp = context.match(/^\d+\.?\d+$/);

			if (isTimestamp && isTimestamp[0].length === context.length) {
				when = moment.unix(context);
			} else {
				if (arguments.length < 3) formatString = '';
				when = moment(context,formatString);
			}
		}

		if(when.isValid())
			return new Handlebars.SafeString(when.fromNow());
		return '';
	});

	Handlebars.registerHelper("formatDate", function(/* d,fmtStr */) {
		var dt,fs,ctx;

		arguments = Array.prototype.slice.call(arguments);

		ctx = arguments.pop();

		dt = arguments[0];
		fs = arguments[1] || "YYYY-MM-DD";

		if(typeof dt == 'undefined' || dt == null) {
			return 'Never';
		}

		return moment(dt).format(fs);
	});

	Handlebars.registerHelper("hourFormat", function(d) {
		return d == 12 ? "NOON" : (d % 12) + (d / 12 < 1 ? "AM" : "PM")
	});

	Handlebars.registerHelper("delta", function(diff, pct, decimals) {

		if(typeof decimals == undefined) decimals = 0;

		var pm = typeof parseFloat(diff) == "number" ? (diff >= 0 ? 'good' : 'bad') : '';

		var difff = parseFloat(diff);
		diff = typeof difff == "number" && !isNaN(difff) ? difff.toMoney(decimals) : diff;

		var pctf = parseFloat(pct);
		pct = typeof pctf == "number" && !isNaN(pctf) ? (pctf.toMoney(1) + '%') : pct;

		var s = '' +
			diff + ' ' + pct + '';

 		return new Handlebars.SafeString(s);
 	});

	Handlebars.registerHelper("progress", function(percent, yellow, red, extraclasses) {

		extraclasses = typeof extraclasses == 'string' || '';

		var pctf = parseFloat(percent);
		pct = (typeof pctf == "number" && !isNaN(pctf)) ? pctf : 0;

		var rf = parseFloat(red);
		var yf = parseFloat(yellow);
		if(typeof rf == 'number' && rf <= pctf)
			barcolor = 'progress-bar-danger';
		else if (typeof yf == 'number' && yf <= pctf)
			barcolor = 'progress-bar-warning';
		else
			barcolor = 'progress-bar-success';

		// clamp the width number
		var w = pct > 100 ? 100 : (pct < 0 ? 0 : pct);
		w = w.toMoney(1);

		var s = '
' +
				'
' + (pct|0) + '%

';

 		return new Handlebars.SafeString(s);
 	});

	Handlebars.registerHelper('cmp',function(v1,op,v2,context){
		var ops = {
				'===': function(l,r){ return l===r; },
				'!==': function(l,r){ return l!==r; },
				'==' : function(l,r){ return l==r; },
				'!=' : function(l,r){ return l!=r; },
				'<=' : function(l,r){ return l<=r; },
				'>=' : function(l,r){ return l>=r; },
				'<'  : function(l,r){ return l<r; },
				'>'  : function(l,r){ return l>r; }
			};
		return (ops[op] && ops[op](v1,v2)) ? context.fn(this) : context.inverse(this);
	});

	Handlebars.registerHelper('default',function(val,def){
		return new Handlebars.SafeString(val ? val : def);
	});

	Handlebars.registerHelper('logic',function(v1,op,v2,context){
		var ops = {
				'and' : function(l,r){ return l && r; },
				'or'  : function(l,r){ return l || r; },
				'xor' : function(l,r){ return (l || r) && ! (l && r); },
				'not' : function(l,r){ return ! l; },
				'nand': function(l,r){ return ! (l && r); },
				'nor' : function(l,r){ return ! (l || r); }
			};
		return (ops[op] && ops[op](bool(v1),bool(v2))) ? context.fn(this) : context.inverse(this);
	});

	Handlebars.registerHelper('math', function(l,fn,r,opt) {
		l = parseFloat(l);
		r = parseFloat(r);

		var ops = {
				'+': l + r,
				'-': l - r,
				'*': l * r,
				'/': l / r,
				'%': l % r
			};

		return (opt === 'int') ? parseInt(ops[fn],10) : ops[fn];
	});

	Handlebars.registerHelper('selectOptions',function(options, selectedValue, defaultTitle, defaultValue){
		var dV = typeof defaultValue == 'undefined' || typeof defaultValue == 'object' ? '' : defaultValue;

		var def = typeof defaultTitle != 'object' && typeof defaultTitle != 'undefined' ? '' +
					'<option value="'+dV +
					'"' + (selectedValue == dV ? ' selected="selected"' : '') +
					'>'+defaultTitle+'</option>' : '<option selected="selected"></option>';

		return new Handlebars.SafeString(def + _(options).reduce(function(m,v){
			if (typeof v.value != 'undefined') {
				return m +
					'<option value="'+v.value + '"' +
					 (selectedValue == v.value ? ' selected="selected"' : '') +
					 (v.disabled == true ? ' disabled="disabled"' : '') +
					'>'+v.title+'</option>';
			} else return m+'<option>'+v.title+'</option>';
		},''));
	});

	Handlebars.registerHelper('makeEnum',function(valueList){
		if (!(valueList instanceof Array)) {
			valueList = valueList.split(',');
		}

		valueList = _(valueList).map(function(v){
			return v.split('|');
		});

		return new Handlebars.SafeString(_(valueList).reduce(function(m,v){
			if (v.length === 2) {
				return m+'<option value="'+v[0]+'">'+v[1]+'</option>';
			} else return m+'<option>'+v[0]+'</option>';
		},''));
	});

	// google places convert number to word description
	Handlebars.registerHelper("googleplacesrating", function(rating) {
		rating = parseFloat(rating);

		if (rating == 0) {
			return 'Poor';
		} else if (rating == 1) {
			return 'Good';
		} else if (rating == 2) {
			return 'Very Good';
		} else if (rating == 3) {
			return 'Excellent';
		} else {
			return '';
		}

	});

	// formats numbers to desired decial places
	Handlebars.registerHelper("numFormat", function(num, decimals) {
		var num = parseFloat(num);
		return num.toFixed(decimals);
	});

	Handlebars.registerHelper('select', function( value, options ){
        var $el = $('<select />').html( options.fn(this) );
        $el.find('[value=' + value + ']').attr({'selected':'selected'});
        return $el.html();
    });
});<commit_msg>remove deprecated looping helpers, cleanup spaces<commit_after>/*	--------------------------------------------------
	:: Handlebars Helpers
	--------------------------------------------------

	Atomic Handlebars Helpers
	http://atomicinfotech.com/atomic-handlebars-helpers
	http://github.com/atomicinfotech/atomic-handlebars-helpers

	---------------------------------------------------

	The MIT License (MIT)

	Copyright (c) 2013 - 2016  Atomic Infotech

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in
	the Software without restriction, including without limitation the rights to
	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
	the Software, and to permit persons to whom the Software is furnished to do so,
	subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
	FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
	COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
	IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
	CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
	*/

$(document).ready(function() {

	// For debugging values inside of a template
	Handlebars.registerHelper('log', function(context) {
		if(console && (typeof console.log === 'function')){
			console.log(context);
		}
		return '';
	});

	Handlebars.registerHelper('uriEncode',function(string) {
		return encodeURIComponent(string);
	});

	Handlebars.registerHelper("apply", function(templateName, data) {
		var t = HandleBarsTemplates[templateName];
		if(typeof t == 'undefined')
			return new Handlebars.SafeString('');

		return new Handlebars.SafeString(t(data));
	});

	Handlebars.registerHelper("eachWeekday", function(indexed_obj, options) {

		var names = [
			{ full: "Sunday", short: "Sun", caps: "SUN", small: "sun" },
			{ full: "Monday", short: "Mon", caps: "MON", small: "mon" },
			{ full: "Tuesday", short: "Tue", caps: "TUE", small: "tue" },
			{ full: "Wednesday", short: "Wed", caps: "WED", small: "wed" },
			{ full: "Thursday", short: "Thu", caps: "THU", small: "thu" },
			{ full: "Friday", short: "Fri", caps: "FRI", small: "fri" },
			{ full: "Saturday", short: "Sat", caps: "SAT", small: "sat" },
		];

		var buffer = "";

		for(var i = 0; i <= 6; i++) {
			buffer += options.fn({
				dayNames: names[i],
				dayData: indexed_obj[i],
				index: i,
				indexOB: i+1,
			});
		}

		return buffer;
	});

	Handlebars.registerHelper("int", function(obj) {
		var n = parseInt(obj, 10);
		return typeof n == "number" && !isNaN(n) ? n : obj;
	});

	Handlebars.registerHelper("YesNo", function(x) {
		return x === true ? 'Yes' : 'No';
	});

	Handlebars.registerHelper("yesno", function(x) {
		return x === true ? 'yes' : 'no';
	});

	// not the prettiest, but it's good enough
	Handlebars.registerHelper("phone", function(obj) {
		if(typeof obj != 'string') return obj;
		var l = obj.length;
		if(l <= 7) return obj.replace(/(.*)(....)/, '$1-$2');
		if(l <= 10) return obj.replace(/(.*)(...)(....)/, '$1-$2-$3');
		if(l > 10) return obj.replace(/(.*)(...)(...)(....)/, '$1-$2-$3-$4');
	});

	Handlebars.registerHelper("%", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(1) : obj;
	});

	Handlebars.registerHelper("%%", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? (n.toMoney(1) + '%') : obj;
	});

	Handlebars.registerHelper("$", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(0) : obj;
	});

	Handlebars.registerHelper("cents", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(2) : obj;
	});

	Handlebars.registerHelper("$$", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(0, '.', ',', '$') : obj;
	});

	Handlebars.registerHelper("$cents", function(obj) {
		var n = parseFloat(obj);
		return typeof n == "number" && !isNaN(n) ? n.toMoney(2, '.', ',', '$') : obj;
	});

	// "plus/minus"
	Handlebars.registerHelper("pm", function(obj) {
		return typeof parseFloat(obj) == "number" ? (obj >= 0 ? 'good' : 'bad') : '';
	});

	// "plus/minus (inverted)"
	Handlebars.registerHelper("pmi", function(obj) {
		return typeof parseFloat(obj) == "number" ? (obj >= 0 ? 'bad' : 'good') : '';
	});

	Handlebars.registerHelper("ago", function(context,formatString) {
		var isTimestamp,when;

		if (typeof context === 'number') {
			when = moment.unix(context);
		} else {
			isTimestamp = context.match(/^\d+\.?\d+$/);

			if (isTimestamp && isTimestamp[0].length === context.length) {
				when = moment.unix(context);
			} else {
				if (arguments.length < 3) formatString = '';
				when = moment(context,formatString);
			}
		}

		if(when.isValid())
			return new Handlebars.SafeString(when.fromNow());
		return '';
	});

	Handlebars.registerHelper("formatDate", function(/* d,fmtStr */) {
		var args,dt,fs,ctx;

		args = Array.prototype.slice.call(arguments);

		ctx = args.pop();

		dt = args[0];
		fs = args[1] || "YYYY-MM-DD";

		if(typeof dt === 'undefined' || dt === null || dt === '') {
			return 'Never';
		}

		return moment(dt).format(fs);
	});

	Handlebars.registerHelper("hourFormat", function(d) {
		return d == 12 ? "NOON" : (d % 12) + (d / 12 < 1 ? "AM" : "PM");
	});

	Handlebars.registerHelper("delta", function(diff, pct, decimals) {

		if(typeof decimals === 'undefined') decimals = 0;

		var pm = typeof parseFloat(diff) == "number" ? (diff >= 0 ? 'good' : 'bad') : '';

		var difff = parseFloat(diff);
		diff = typeof difff == "number" && !isNaN(difff) ? difff.toMoney(decimals) : diff;

		var pctf = parseFloat(pct);
		pct = typeof pctf == "number" && !isNaN(pctf) ? (pctf.toMoney(1) + '%') : pct;

		var s = '' +
			diff + ' ' + pct + '';

		return new Handlebars.SafeString(s);
	});

	Handlebars.registerHelper("progress", function(percent, yellow, red, extraclasses) {

		extraclasses = typeof extraclasses == 'string' ? extraclasses : '';

		var pctf = parseFloat(percent);
		pct = (typeof pctf == "number" && !isNaN(pctf)) ? pctf : 0;

		var rf = parseFloat(red);
		var yf = parseFloat(yellow);
		if(typeof rf == 'number' && rf <= pctf) {
			barcolor = 'progress-bar-danger';
		}
		else if (typeof yf == 'number' && yf <= pctf) {
			barcolor = 'progress-bar-warning';
		}
		else {
			barcolor = 'progress-bar-success';
		}

		// clamp the width number
		var w = pct > 100 ? 100 : (pct < 0 ? 0 : pct);
		w = w.toMoney(1);

		var s = '
' +
				'
' + (pct|0) + '%

';

 		return new Handlebars.SafeString(s);
 	});

	Handlebars.registerHelper('cmp',function(v1,op,v2,context){
		var ops = {
				'===': function(l,r){ return l===r; },
				'!==': function(l,r){ return l!==r; },
				'==' : function(l,r){ return l==r; },
				'!=' : function(l,r){ return l!=r; },
				'<=' : function(l,r){ return l<=r; },
				'>=' : function(l,r){ return l>=r; },
				'<'  : function(l,r){ return l<r; },
				'>'  : function(l,r){ return l>r; }
			};
		return (ops[op] && ops[op](v1,v2)) ? context.fn(this) : context.inverse(this);
	});

	Handlebars.registerHelper('default',function(val,def){
		return new Handlebars.SafeString(val ? val : def);
	});

	Handlebars.registerHelper('logic',function(v1,op,v2,context){
		var ops = {
				'and' : function(l,r){ return l && r; },
				'or'  : function(l,r){ return l || r; },
				'xor' : function(l,r){ return (l || r) && ! (l && r); },
				'not' : function(l,r){ return ! l; },
				'nand': function(l,r){ return ! (l && r); },
				'nor' : function(l,r){ return ! (l || r); }
			};
		return (ops[op] && ops[op](bool(v1),bool(v2))) ? context.fn(this) : context.inverse(this);
	});

	Handlebars.registerHelper('math', function(l,fn,r,opt) {
		l = parseFloat(l);
		r = parseFloat(r);

		var ops = {
				'+': l + r,
				'-': l - r,
				'*': l * r,
				'/': l / r,
				'%': l % r
			};

		return (opt === 'int') ? parseInt(ops[fn],10) : ops[fn];
	});

	Handlebars.registerHelper('selectOptions',function(options, selectedValue, defaultTitle, defaultValue){
		var dV = typeof defaultValue == 'undefined' || typeof defaultValue == 'object' ? '' : defaultValue;

		var def = typeof defaultTitle != 'object' && typeof defaultTitle != 'undefined' ? '' +
					'<option value="'+dV +
					'"' + (selectedValue == dV ? ' selected="selected"' : '') +
					'>'+defaultTitle+'</option>' : '<option selected="selected"></option>';

		return new Handlebars.SafeString(def + _(options).reduce(function(m,v){
			if (typeof v.value != 'undefined') {
				return m +
					'<option value="'+v.value + '"' +
					 (selectedValue == v.value ? ' selected="selected"' : '') +
					 (v.disabled == true ? ' disabled="disabled"' : '') +
					'>'+v.title+'</option>';
			} else return m+'<option>'+v.title+'</option>';
		},''));
	});

	Handlebars.registerHelper('makeEnum',function(valueList){
		if (!(valueList instanceof Array)) {
			valueList = valueList.split(',');
		}

		valueList = _(valueList).map(function(v){
			return v.split('|');
		});

		return new Handlebars.SafeString(_(valueList).reduce(function(m,v){
			if (v.length === 2) {
				return m+'<option value="'+v[0]+'">'+v[1]+'</option>';
			} else return m+'<option>'+v[0]+'</option>';
		},''));
	});

	// google places convert number to word description
	Handlebars.registerHelper("googleplacesrating", function(rating) {
		rating = Math.floor(float(rating));

		return ['Poor','Good','Very Good','Excellent'][rating] || '';
	});

	// formats numbers to desired decial places
	Handlebars.registerHelper("numFormat", function(num, decimals) {
		return fixed(num,decimals);
	});

	Handlebars.registerHelper('select', function( value, options ){
		var $el = $('<select />').html( options.fn(this) );
		$el.find('[value=' + value + ']').attr({'selected':'selected'});
		return $el.html();
	});
});<|endoftext|>


<commit_before>/* global key, console */

//Configure KeyMaster to respond to all shortcuts,
//even inside of
//input, textarea, and select.
key.filter = function () {
    return true;
};

key.setScope('default');
/**
 * Only routes can implement shortcuts.
 * If you need to trigger actions on the controller,
 * simply call them with `this.get('controller').send('action')`.
 *
 * To implement shortcuts, add this mixin to your `extend()`,
 * and implement a `shortcuts` hash.
 * In this hash, keys are shortcut combinations and values are route action names.
 *  (see [keymaster docs](https://github.com/madrobby/keymaster/blob/master/README.markdown)),
 *
 * ```javascript
 * shortcuts: {
 *     'ctrl+s, command+s': 'save',
 *     'ctrl+alt+z': 'toggleZenMode'
 * }
 * ```
 * For more complex actions, shortcuts can instead have their value
 * be an object like {action, options}
 * ```javascript
 * shortcuts: {
 *      'ctrl+k': {action: 'markdownShortcut', options: 'createLink'}
 * }
 * ```
 * You can set the scope of your shortcut by passing a scope property.
 * ```javascript
 * shortcuts : {
 *   'enter': {action : 'confirmModal', scope: 'modal'}
 * }
 * ```
 * If you don't specify a scope, we use a default scope called "default".
 * To have all your shortcut work in all scopes, give it the scope "all".
 * Find out more at the keymaster docs
 */
var ShortcutsRoute = Ember.Mixin.create({
    registerShortcuts: function () {
        var self = this,
            shortcuts = this.get('shortcuts');

        Ember.keys(shortcuts).forEach(function (shortcut) {
            var scope = shortcuts[shortcut].scope || 'default',
                action = shortcuts[shortcut],
                options;

            if (Ember.typeOf(action) !== 'string') {
                options = action.options;
                action = action.action;
            }

            key(shortcut, scope, function (event) {
                //stop things like ctrl+s from actually opening a save dialogue
                event.preventDefault();
                self.send(action, options);
            });
        });
    },
    removeShortcuts: function () {
        var shortcuts = this.get('shortcuts');

        Ember.keys(shortcuts).forEach(function (shortcut) {
            key.unbind(shortcut);
        });
    },
    activate: function () {
        this._super();
        if (!this.shortcuts) {
            console.error('Shortcuts not found on route');
            return;
        }
        this.registerShortcuts();
    },
    deactivate: function () {
        this._super();
        this.removeShortcuts();
    }
});

export default ShortcutsRoute;
<commit_msg>Remove console.error from shortcuts-route mixing<commit_after>/* global key */

//Configure KeyMaster to respond to all shortcuts,
//even inside of
//input, textarea, and select.
key.filter = function () {
    return true;
};

key.setScope('default');
/**
 * Only routes can implement shortcuts.
 * If you need to trigger actions on the controller,
 * simply call them with `this.get('controller').send('action')`.
 *
 * To implement shortcuts, add this mixin to your `extend()`,
 * and implement a `shortcuts` hash.
 * In this hash, keys are shortcut combinations and values are route action names.
 *  (see [keymaster docs](https://github.com/madrobby/keymaster/blob/master/README.markdown)),
 *
 * ```javascript
 * shortcuts: {
 *     'ctrl+s, command+s': 'save',
 *     'ctrl+alt+z': 'toggleZenMode'
 * }
 * ```
 * For more complex actions, shortcuts can instead have their value
 * be an object like {action, options}
 * ```javascript
 * shortcuts: {
 *      'ctrl+k': {action: 'markdownShortcut', options: 'createLink'}
 * }
 * ```
 * You can set the scope of your shortcut by passing a scope property.
 * ```javascript
 * shortcuts : {
 *   'enter': {action : 'confirmModal', scope: 'modal'}
 * }
 * ```
 * If you don't specify a scope, we use a default scope called "default".
 * To have all your shortcut work in all scopes, give it the scope "all".
 * Find out more at the keymaster docs
 */
var ShortcutsRoute = Ember.Mixin.create({
    registerShortcuts: function () {
        var self = this,
            shortcuts = this.get('shortcuts');

        Ember.keys(shortcuts).forEach(function (shortcut) {
            var scope = shortcuts[shortcut].scope || 'default',
                action = shortcuts[shortcut],
                options;

            if (Ember.typeOf(action) !== 'string') {
                options = action.options;
                action = action.action;
            }

            key(shortcut, scope, function (event) {
                //stop things like ctrl+s from actually opening a save dialogue
                event.preventDefault();
                self.send(action, options);
            });
        });
    },
    removeShortcuts: function () {
        var shortcuts = this.get('shortcuts');

        Ember.keys(shortcuts).forEach(function (shortcut) {
            key.unbind(shortcut);
        });
    },
    activate: function () {
        this._super();
        if (!this.shortcuts) {
            return;
        }
        this.registerShortcuts();
    },
    deactivate: function () {
        this._super();
        this.removeShortcuts();
    }
});

export default ShortcutsRoute;
<|endoftext|>


<commit_before>import stravaLib from 'strava-v3';
import util from 'util';
import Promise from 'bluebird';
import _ from 'lodash';

// I haven't figured this out, but the load order sometimes isn't
// guaranteed and dotenv doesn't always load up.
if (process.env.NODE_ENV !== 'production') {
  require('dotenv').config();
}

export default class StravaService {
  activityZipped(activityId, accessToken, strava = stravaLib) {
    return this.activityStream(activityId, accessToken, strava)
    .then((data) => {
      console.log(data);
      const timeData = _.find(data, { type: 'time' }).data;
      const latlngData = _.find(data, { type: 'latlng' }).data;
      const distanceData = _.find(data, { type: 'distance' }).data;
      const velocityData = _.find(data, { type: 'velocity_smooth' }).data;
      console.log(`activityZipped with activity ${activityId}`)
      return _.zipWith(timeData,
                       latlngData,
                       distanceData,
                       velocityData,
                       (time, latlng, dist, vel) => {
                         return {
                           time: time,
                           latlng: latlng,
                           distance: dist,
                           velocity: vel
                         };
                       });
    });
  }

  activities(accessToken, strava = stravaLib) {
    console.log(`[StravaService] making listActivities call with accessToken: ${accessToken}`);
    return new Promise((resolve, reject) => {
      strava.athlete.listActivities({
        per_page: 200,
        access_token: accessToken,
      }, (err, data) => {
        if (!err) { resolve(data); }
        else { reject(err); }
      });
    })
  }

  activityStream(activityId, accessToken, strava = stravaLib) {
    console.log(`Looking up Strava activity stream for ${activityId} for user ${accessToken}`);
    return new Promise((resolve, reject) => {
      const stream = strava.streams.activity({
        id: activityId,
        types: 'latlng,time,velocity_smooth',
        resolution: 'high',
        access_token: accessToken,
      }, (err, data) => {
        if (!err) { resolve(data); }
        else {
          console.error('Error', err)
          reject(err)
        }
      });
    });
  }
};
<commit_msg>Remove stray debugger.<commit_after>import stravaLib from 'strava-v3';
import util from 'util';
import Promise from 'bluebird';
import _ from 'lodash';

// I haven't figured this out, but the load order sometimes isn't
// guaranteed and dotenv doesn't always load up.
if (process.env.NODE_ENV !== 'production') {
  require('dotenv').config();
}

export default class StravaService {
  activityZipped(activityId, accessToken, strava = stravaLib) {
    return this.activityStream(activityId, accessToken, strava)
    .then((data) => {
      const timeData = _.find(data, { type: 'time' }).data;
      const latlngData = _.find(data, { type: 'latlng' }).data;
      const distanceData = _.find(data, { type: 'distance' }).data;
      const velocityData = _.find(data, { type: 'velocity_smooth' }).data;
      console.log(`activityZipped with activity ${activityId}`)
      return _.zipWith(timeData,
                       latlngData,
                       distanceData,
                       velocityData,
                       (time, latlng, dist, vel) => {
                         return {
                           time: time,
                           latlng: latlng,
                           distance: dist,
                           velocity: vel
                         };
                       });
    });
  }

  activities(accessToken, strava = stravaLib) {
    console.log(`[StravaService] making listActivities call with accessToken: ${accessToken}`);
    return new Promise((resolve, reject) => {
      strava.athlete.listActivities({
        per_page: 200,
        access_token: accessToken,
      }, (err, data) => {
        if (!err) { resolve(data); }
        else { reject(err); }
      });
    })
  }

  activityStream(activityId, accessToken, strava = stravaLib) {
    console.log(`Looking up Strava activity stream for ${activityId} for user ${accessToken}`);
    return new Promise((resolve, reject) => {
      const stream = strava.streams.activity({
        id: activityId,
        types: 'latlng,time,velocity_smooth',
        resolution: 'high',
        access_token: accessToken,
      }, (err, data) => {
        if (!err) { resolve(data); }
        else {
          console.error('Error', err)
          reject(err)
        }
      });
    });
  }
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Radium from 'radium';
import {getFontSize} from '../../styles/typography';
import {pageShape} from '../../CatalogPropTypes';
import Link from '../Link/Link';

// The vertical and horizontal padding inside the left/right nav
// link element.
const verticalPadding = 28;
const horizontalPadding = 21;

function getStyles(theme) {
  return {
    navbar: {
      width: '100%',
      backgroundColor: theme.navBarBackground
    },
    navlink: {
      boxSizing: 'border-box',
      display: 'inline-block',
      verticalAlign: 'top',
      width: '50%',
      transition: '.2s opacity',
      ':hover': {
        opacity: 0.65;
      }
    },
    leftNavLink: {
      padding: `${verticalPadding}px 0 ${verticalPadding}px ${horizontalPadding}px`,
      textAlign: 'left',
      '@media (min-width: 1000px)': {
        padding: `${verticalPadding}px 0 ${verticalPadding}px ${horizontalPadding * 2}px`
      }
    },
    rightNavLink: {
      padding: `${verticalPadding}px ${horizontalPadding}px ${verticalPadding}px 0`,
      textAlign: 'right',
      borderLeft: `1px solid ${theme.background}`,
      '@media (min-width: 1000px)': {
        padding: `${verticalPadding}px ${horizontalPadding * 2}px ${verticalPadding}px 0`
      }
    },
    link: {
      color: theme.navBarTextColor,
      display: 'block',
      fontFamily: theme.fontFamily,
      textDecoration: 'none'
    },
    leftLinkIcon: {
      display: 'none',
      margin: '0 24px 0 0',
      verticalAlign: 'middle',
      '@media (min-width: 1000px)': {
        display: 'inline'
      }
    },
    rightLinkIcon: {
      display: 'none',
      margin: '0 0 0 24px',
      verticalAlign: 'middle',
      '@media (min-width: 1000px)': {
        display: 'inline'
      }
    },
    linkIconPath: {
      stroke: 'none',
      fill: theme.navBarTextColor
    },
    linklabels: {
      display: 'block',
      verticalAlign: 'middle',
      '@media (min-width: 1000px)': {
        display: 'inline-block'
      }
    },
    linkSuperTitle: {
      fontSize: getFontSize(theme, 0),
      margin: 0,
      fontWeight: 400
    },
    linkTitle: {
      fontSize: getFontSize(theme, 1),
      margin: 0,
      fontWeight: 400
    }
  };
}

class NavigationBar extends React.Component {

  render() {
    const {nextPage, previousPage, theme} = this.props;

    const styles = getStyles(theme);

    const leftIcon = (
      <svg style={styles.leftLinkIcon} width='37px' height='26px' viewBox='0 0 37 26'>
        <path style={styles.linkIconPath} d='M12.2925,0.2925 C12.6845,-0.0975 13.3165,-0.0975 13.7085,0.2925 C14.0985,0.6845 14.0985,1.3165 13.7085,1.7085 L3.4145,12.0005 L36.0005,12.0005 C36.5525,12.0005 37.0005,12.4485 37.0005,13.0005 C37.0005,13.5525 36.5525,14.0005 36.0005,14.0005 L3.4145,14.0005 L13.7085,24.2925 C14.0985,24.6845 14.0985,25.3165 13.7085,25.7085 C13.5125,25.9025 13.2565,26.0005 13.0005,26.0005 C12.7445,26.0005 12.4885,25.9025 12.2925,25.7085 L0.2925,13.7085 C-0.0975,13.3165 -0.0975,12.6845 0.2925,12.2925 L12.2925,0.2925 Z'></path>
      </svg>
    );
    const rightIcon = (
      <svg style={styles.rightLinkIcon} width='37px' height='26px' viewBox='0 0 37 26'>
        <path style={styles.linkIconPath} d='M24.708,0.2925 C24.316,-0.0975 23.684,-0.0975 23.292,0.2925 C22.902,0.6845 22.902,1.3165 23.292,1.7085 L33.586,12.0005 L1,12.0005 C0.448,12.0005 0,12.4485 0,13.0005 C0,13.5525 0.448,14.0005 1,14.0005 L33.586,14.0005 L23.292,24.2925 C22.902,24.6845 22.902,25.3165 23.292,25.7085 C23.488,25.9025 23.744,26.0005 24,26.0005 C24.256,26.0005 24.512,25.9025 24.708,25.7085 L36.708,13.7085 C37.098,13.3165 37.098,12.6845 36.708,12.2925 L24.708,0.2925 Z'></path>
      </svg>
    );

    return (

{
          previousPage &&
          <Link to={previousPage.path} style={{...styles.link, ...styles.leftNavLink}}>
            { leftIcon }

{ previousPage.superTitle }

{ previousPage.title }

          </Link>
        }

{
          nextPage &&
          <Link to={nextPage.path} style={{...styles.link, ...styles.rightNavLink}}>

{ nextPage.superTitle }

{ nextPage.title }

            { rightIcon }
          </Link>
        }

    );
  }
}

NavigationBar.propTypes = {
  theme: PropTypes.object.isRequired,
  nextPage: pageShape,
  previousPage: pageShape
};

export default Radium(NavigationBar);
<commit_msg>remove semicolon<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Radium from 'radium';
import {getFontSize} from '../../styles/typography';
import {pageShape} from '../../CatalogPropTypes';
import Link from '../Link/Link';

// The vertical and horizontal padding inside the left/right nav
// link element.
const verticalPadding = 28;
const horizontalPadding = 21;

function getStyles(theme) {
  return {
    navbar: {
      width: '100%',
      backgroundColor: theme.navBarBackground
    },
    navlink: {
      boxSizing: 'border-box',
      display: 'inline-block',
      verticalAlign: 'top',
      width: '50%',
      transition: '.2s opacity',
      ':hover': {
        opacity: 0.65
      }
    },
    leftNavLink: {
      padding: `${verticalPadding}px 0 ${verticalPadding}px ${horizontalPadding}px`,
      textAlign: 'left',
      '@media (min-width: 1000px)': {
        padding: `${verticalPadding}px 0 ${verticalPadding}px ${horizontalPadding * 2}px`
      }
    },
    rightNavLink: {
      padding: `${verticalPadding}px ${horizontalPadding}px ${verticalPadding}px 0`,
      textAlign: 'right',
      borderLeft: `1px solid ${theme.background}`,
      '@media (min-width: 1000px)': {
        padding: `${verticalPadding}px ${horizontalPadding * 2}px ${verticalPadding}px 0`
      }
    },
    link: {
      color: theme.navBarTextColor,
      display: 'block',
      fontFamily: theme.fontFamily,
      textDecoration: 'none'
    },
    leftLinkIcon: {
      display: 'none',
      margin: '0 24px 0 0',
      verticalAlign: 'middle',
      '@media (min-width: 1000px)': {
        display: 'inline'
      }
    },
    rightLinkIcon: {
      display: 'none',
      margin: '0 0 0 24px',
      verticalAlign: 'middle',
      '@media (min-width: 1000px)': {
        display: 'inline'
      }
    },
    linkIconPath: {
      stroke: 'none',
      fill: theme.navBarTextColor
    },
    linklabels: {
      display: 'block',
      verticalAlign: 'middle',
      '@media (min-width: 1000px)': {
        display: 'inline-block'
      }
    },
    linkSuperTitle: {
      fontSize: getFontSize(theme, 0),
      margin: 0,
      fontWeight: 400
    },
    linkTitle: {
      fontSize: getFontSize(theme, 1),
      margin: 0,
      fontWeight: 400
    }
  };
}

class NavigationBar extends React.Component {

  render() {
    const {nextPage, previousPage, theme} = this.props;

    const styles = getStyles(theme);

    const leftIcon = (
      <svg style={styles.leftLinkIcon} width='37px' height='26px' viewBox='0 0 37 26'>
        <path style={styles.linkIconPath} d='M12.2925,0.2925 C12.6845,-0.0975 13.3165,-0.0975 13.7085,0.2925 C14.0985,0.6845 14.0985,1.3165 13.7085,1.7085 L3.4145,12.0005 L36.0005,12.0005 C36.5525,12.0005 37.0005,12.4485 37.0005,13.0005 C37.0005,13.5525 36.5525,14.0005 36.0005,14.0005 L3.4145,14.0005 L13.7085,24.2925 C14.0985,24.6845 14.0985,25.3165 13.7085,25.7085 C13.5125,25.9025 13.2565,26.0005 13.0005,26.0005 C12.7445,26.0005 12.4885,25.9025 12.2925,25.7085 L0.2925,13.7085 C-0.0975,13.3165 -0.0975,12.6845 0.2925,12.2925 L12.2925,0.2925 Z'></path>
      </svg>
    );
    const rightIcon = (
      <svg style={styles.rightLinkIcon} width='37px' height='26px' viewBox='0 0 37 26'>
        <path style={styles.linkIconPath} d='M24.708,0.2925 C24.316,-0.0975 23.684,-0.0975 23.292,0.2925 C22.902,0.6845 22.902,1.3165 23.292,1.7085 L33.586,12.0005 L1,12.0005 C0.448,12.0005 0,12.4485 0,13.0005 C0,13.5525 0.448,14.0005 1,14.0005 L33.586,14.0005 L23.292,24.2925 C22.902,24.6845 22.902,25.3165 23.292,25.7085 C23.488,25.9025 23.744,26.0005 24,26.0005 C24.256,26.0005 24.512,25.9025 24.708,25.7085 L36.708,13.7085 C37.098,13.3165 37.098,12.6845 36.708,12.2925 L24.708,0.2925 Z'></path>
      </svg>
    );

    return (

{
          previousPage &&
          <Link to={previousPage.path} style={{...styles.link, ...styles.leftNavLink}}>
            { leftIcon }

{ previousPage.superTitle }

{ previousPage.title }

          </Link>
        }

{
          nextPage &&
          <Link to={nextPage.path} style={{...styles.link, ...styles.rightNavLink}}>

{ nextPage.superTitle }

{ nextPage.title }

            { rightIcon }
          </Link>
        }

    );
  }
}

NavigationBar.propTypes = {
  theme: PropTypes.object.isRequired,
  nextPage: pageShape,
  previousPage: pageShape
};

export default Radium(NavigationBar);
<|endoftext|>


<commit_before>/**
 * @file
 */

/**
 * Extract the hit part of the ES result, if it exist
 *
 * @param esRes
 * @param pos
 * @param fld
 * @returns {*}
 */
export function getEsField(esRes, pos, fld) {
  if (esRes && Array.isArray(esRes.hits) && esRes.hits[pos] && esRes.hits[pos]._source && Array.isArray(esRes.hits[pos]._source[fld])) {
    return esRes.hits[pos]._source[fld];
  }
  return [];
}

/**
 * return first occurrence of a given list of fields
 *
 * @param esRes
 * @param pos
 * @param fldList
 * @returns {*}
 */
export function getFirstField(esRes, pos, fldList) {
  let field = '';
  fldList.forEach(function (tag) {
    const fld = getEsField(esRes, pos, tag);
    if (!field && fld[0]) {
      field = fld[0];
    }
  });
  return field;
}

/**
 * Merge default and function given parameters, and translate to ES names
 *
 * @param pars
 * @param defaultParameters
 * @param esParMap
 * @returns {{}}
 */
export function setAndMap(pars, defaultParameters, esParMap) {
  let ret = {};
  Object.keys(defaultParameters).forEach(function (key) {
    ret[esParMap[key]] = pars[key] ? pars[key] : defaultParameters[key];
  });
  return ret;
}

/**
 * sort on ascending distance
 *
 * @param buf
 * @param elements
 * @returns {*|Blob|string|ArrayBuffer}
 */
export function sortDistanceAndSlice(buf, elements) {
  return buf.sort(function (a, b) {
    return (parseInt(a.distance, 10) - parseInt(b.distance, 10));
  }).slice(0, elements);
}

/**
 * sort an array on the title field
 *
 * @param arr
 * @returns {*}
 */
export function titleSort(arr) {
  return arr.sort(function (a, b) {
    return (a.title > b.title ? 1 : -1);
  });
}

/**
 * parse a register record and return a consolidated structure
 *
 * @param esRes
 * @param pos
 * @param dk5Tab
 * @returns {*}
 */
export function parseRegisterRecord(esRes, pos, dk5Tab) {
  const ret = {};
  ret.title = getFirstField(esRes, pos, ['630a', '633a', '640a', '600a', '610a']);
  ret.titleDetails = getFirstField(esRes, pos, ['630e', '633e', '640e', '600f', '610e']);
  ret.titleFull = ret.title + (ret.titleDetails ? ' - ' + ret.titleDetails : '');
  ret.index = getFirstField(esRes, pos, ['652m']);
  ret.id = getFirstField(esRes, pos, ['001a']);
  ret.parent = Object.assign({}, dk5Tab[ret.index]);
  const aspectDk5 = getEsField(esRes, pos, 'b52m');
  if (aspectDk5.length === 0) {
    return ret;
  }

  const aspectTitle = getEsField(esRes, pos, 'b52y');
  const items = [];
  for (let i = 0; i < aspectDk5.length; i++) {
    items.push({index: aspectDk5[i], title: aspectTitle[i], parent: dk5Tab[aspectDk5[i]]});
  }
  return Object.assign({}, ret, {items: items});
}

/**
 * Parse fields and build a html like note
 *
 * @param syst
 * @param pos
 * @returns {string}
 */
export function createTaggedNote(syst, pos) {
  const noteText = getEsField(syst, pos, 'a40a');
  let note = '';
  if (Array.isArray(noteText)) {
    const noteSyst = getEsField(syst, pos, 'a40b');
    for (let i = 0; i < noteText.length; i++) {
      if (note && !/[a-zæåø,.()]/.exec(noteText[i].substr(0, 1))) {
        note += '
';
      }
      note = note.trim() + noteText[i];
      if (noteSyst[i]) {
        note += noteSyst[i].indexOf('-') !== -1 ? ' ' + noteSyst[i] + ' ' : ' <dk>' + noteSyst[i] + '</dk> ';
      }
    }
  }
  return note.trim();
}

/**
 * Parse register records and extract unique words
 *
 * @param regRecs
 * @param wordFields
 * @returns {Array}
 */
export function parseRegisterForUniqueWords(regRecs, wordFields) {
  let uniqueWords = {};
  for (let hitPos = 0; hitPos < regRecs.hits.length; hitPos++) {
    wordFields.forEach((fld) => {
      const wordArr = getEsField(regRecs, hitPos, fld);
      for (let i = 0; i < wordArr.length; i++) {
        wordArr[i].split(/[\s,]+/).forEach((word) => {
          word = word.replace(/^[:\-\.#]+|[:\-\.#]+$|[\"]+/g, '');
          let num = word.replace(/[\.:-]/g, '');
          if (num.length > 2 && parseInt(num) != num) {
            uniqueWords[word.toLowerCase()] = true;
          }
        });
      }
    });
  }
  return Object.keys(uniqueWords);
}

<commit_msg>#28: lint<commit_after>/**
 * @file
 */

/**
 * Extract the hit part of the ES result, if it exist
 *
 * @param esRes
 * @param pos
 * @param fld
 * @returns {*}
 */
export function getEsField(esRes, pos, fld) {
  if (esRes && Array.isArray(esRes.hits) && esRes.hits[pos] && esRes.hits[pos]._source && Array.isArray(esRes.hits[pos]._source[fld])) {
    return esRes.hits[pos]._source[fld];
  }
  return [];
}

/**
 * return first occurrence of a given list of fields
 *
 * @param esRes
 * @param pos
 * @param fldList
 * @returns {*}
 */
export function getFirstField(esRes, pos, fldList) {
  let field = '';
  fldList.forEach(function (tag) {
    const fld = getEsField(esRes, pos, tag);
    if (!field && fld[0]) {
      field = fld[0];
    }
  });
  return field;
}

/**
 * Merge default and function given parameters, and translate to ES names
 *
 * @param pars
 * @param defaultParameters
 * @param esParMap
 * @returns {{}}
 */
export function setAndMap(pars, defaultParameters, esParMap) {
  let ret = {};
  Object.keys(defaultParameters).forEach(function (key) {
    ret[esParMap[key]] = pars[key] ? pars[key] : defaultParameters[key];
  });
  return ret;
}

/**
 * sort on ascending distance
 *
 * @param buf
 * @param elements
 * @returns {*|Blob|string|ArrayBuffer}
 */
export function sortDistanceAndSlice(buf, elements) {
  return buf.sort(function (a, b) {
    return (parseInt(a.distance, 10) - parseInt(b.distance, 10));
  }).slice(0, elements);
}

/**
 * sort an array on the title field
 *
 * @param arr
 * @returns {*}
 */
export function titleSort(arr) {
  return arr.sort(function (a, b) {
    return (a.title > b.title ? 1 : -1);
  });
}

/**
 * parse a register record and return a consolidated structure
 *
 * @param esRes
 * @param pos
 * @param dk5Tab
 * @returns {*}
 */
export function parseRegisterRecord(esRes, pos, dk5Tab) {
  const ret = {};
  ret.title = getFirstField(esRes, pos, ['630a', '633a', '640a', '600a', '610a']);
  ret.titleDetails = getFirstField(esRes, pos, ['630e', '633e', '640e', '600f', '610e']);
  ret.titleFull = ret.title + (ret.titleDetails ? ' - ' + ret.titleDetails : '');
  ret.index = getFirstField(esRes, pos, ['652m']);
  ret.id = getFirstField(esRes, pos, ['001a']);
  ret.parent = Object.assign({}, dk5Tab[ret.index]);
  const aspectDk5 = getEsField(esRes, pos, 'b52m');
  if (aspectDk5.length === 0) {
    return ret;
  }

  const aspectTitle = getEsField(esRes, pos, 'b52y');
  const items = [];
  for (let i = 0; i < aspectDk5.length; i++) {
    items.push({index: aspectDk5[i], title: aspectTitle[i], parent: dk5Tab[aspectDk5[i]]});
  }
  return Object.assign({}, ret, {items: items});
}

/**
 * Parse fields and build a html like note
 *
 * @param syst
 * @param pos
 * @returns {string}
 */
export function createTaggedNote(syst, pos) {
  const noteText = getEsField(syst, pos, 'a40a');
  let note = '';
  if (Array.isArray(noteText)) {
    const noteSyst = getEsField(syst, pos, 'a40b');
    for (let i = 0; i < noteText.length; i++) {
      if (note && !/[a-zæåø,.()]/.exec(noteText[i].substr(0, 1))) {
        note += '
';
      }
      note = note.trim() + noteText[i];
      if (noteSyst[i]) {
        note += noteSyst[i].indexOf('-') !== -1 ? ' ' + noteSyst[i] + ' ' : ' <dk>' + noteSyst[i] + '</dk> ';
      }
    }
  }
  return note.trim();
}

/**
 * Parse register records and extract unique words
 *
 * @param regRecs
 * @param wordFields
 * @returns {Array}
 */
export function parseRegisterForUniqueWords(regRecs, wordFields) {
  let uniqueWords = {};
  for (let hitPos = 0; hitPos < regRecs.hits.length; hitPos++) {
    wordFields.forEach((fld) => {
      const wordArr = getEsField(regRecs, hitPos, fld);
      for (let i = 0; i < wordArr.length; i++) {
        wordArr[i].split(/[\s,]+/).forEach((word) => {
          word = word.replace(/^[:\-\.#]+|[:\-\.#]+$|[\"]+/g, '');
          let num = word.replace(/[\.:-]/g, '');
          if (num.length > 2 && parseInt(num, 10) !== num) {
            uniqueWords[word.toLowerCase()] = true;
          }
        });
      }
    });
  }
  return Object.keys(uniqueWords);
}

<|endoftext|>


<commit_before>// create table view data object
var data = [];
var rssTable = Titanium.UI.createTableView({data:data});
detailWindow.add(rssTable);

//Load an RSS feed when we get the message to do so
Ti.App.addEventListener("loadFeed", function(e) {
  //set new title
  detailWindow.title = e.title;

  //Indicate we're loading a new feed
  data = [{title:"Loading News Feed..."}];
  rssTable.setData(data);

  var xhr = Ti.Network.createHTTPClient();
  xhr.onload = function() {
  	try {
  		var doc = this.responseXML.documentElement;
  		var items = doc.getElementsByTagName("item");
  		var x = 0;
  		var doctitle = doc.evaluate("//channel/title/text()").item(0).nodeValue;
  		for (var c=0;c<items.length;c++) {
  			var item = items.item(c);
  			var thumbnails = item.getElementsByTagName("media:thumbnail");
  			if (thumbnails && thumbnails.length > 0) {
  				var media = thumbnails.item(0).getAttribute("url");
  				var title = item.getElementsByTagName("title").item(0).text;
  				var row = Ti.UI.createTableViewRow({height:80});
  				var label = Ti.UI.createLabel({
  					text:title,
  					left:72,
  					top:5,
  					bottom:5,
  					right:5
  				});
  				row.add(label);
  				var img = Ti.UI.createImageView({
  					url:media,
  					left:5,
  					height:60,
  					width:60
  				});
  				row.add(img);
  				data[x++] = row;
  				row.url = item.getElementsByTagName("link").item(0).text;
  			}
  		}
  		rssTable.setData(data);
  		rssTable.addEventListener('click',function(e) {
  		  var newsPage = Ti.UI.createWindow({title:doctitle});
      	var wb = Ti.UI.createWebView({url:e.row.url});
      	newsPage.add(wb);
      	detailNavigationGroup.open(newsPage,{animated:true});
      });
  	}
  	catch(E) {
  		alert(E);
  	}
  };
  xhr.open("GET","http://v2.0.news.tmg.s3.amazonaws.com/feeds/news.xml");
  xhr.send();
});
<commit_msg>Fixing window open behavior<commit_after>// create table view data object
var data = [];
var rssTable = Titanium.UI.createTableView({data:data});
detailWindow.add(rssTable);
var onClickFunction = false;

//Load an RSS feed when we get the message to do so
Ti.App.addEventListener("loadFeed", function(e) {
  //set new title
  detailWindow.title = e.title;

  //Indicate we're loading a new feed
  data = [{title:"Loading News Feed..."}];
  rssTable.setData(data);

  var xhr = Ti.Network.createHTTPClient();
  xhr.onload = function() {
    try {
      if (onClickFunction) {
        rssTable.removeEventListener('click', onClickFunction);
      }

      var doc = this.responseXML.documentElement;
      var items = doc.getElementsByTagName("item");
      var x = 0;
      var doctitle = doc.evaluate("//channel/title/text()").item(0).nodeValue;
      for (var c=0;c<items.length;c++) {
        var item = items.item(c);
        var thumbnails = item.getElementsByTagName("media:thumbnail");
        if (thumbnails && thumbnails.length > 0) {
          var media = thumbnails.item(0).getAttribute("url");
          var title = item.getElementsByTagName("title").item(0).text;
          var row = Ti.UI.createTableViewRow({height:80});
          var label = Ti.UI.createLabel({
            text:title,
            left:72,
            top:5,
            bottom:5,
            right:5
          });
          row.add(label);
          var img = Ti.UI.createImageView({
            url:media,
            left:5,
            height:60,
            width:60
          });
          row.add(img);
          data[x++] = row;
          row.url = item.getElementsByTagName("link").item(0).text;
        }
      }
      onClickFunction = function(e) {
        var newsPage = Ti.UI.createWindow({title:doctitle});
        var wb = Ti.UI.createWebView({url:e.row.url});
        newsPage.add(wb);
        detailNavigationGroup.open(newsPage,{animated:true});
      };
      rssTable.addEventListener('click', onClickFunction);
      rssTable.setData(data);
    }
    catch(E) {
      alert(E);
    }
  };
  xhr.open("GET","http://v2.0.news.tmg.s3.amazonaws.com/feeds/news.xml");
  xhr.send();
});<|endoftext|>