From 73174e79b32cd72db008982b588476b0b0c744d5 Mon Sep 17 00:00:00 2001 From: Ariel Mashraki Date: Sat, 3 Dec 2016 13:57:12 +0200 Subject: [PATCH] build v0.5.12 --- dist/angular-filter.js | 4336 ++++++++++++++++++------------------ dist/angular-filter.min.js | 4 +- dist/angular-filter.zip | Bin 84124 -> 83983 bytes 3 files changed, 2180 insertions(+), 2160 deletions(-) diff --git a/dist/angular-filter.js b/dist/angular-filter.js index 915076e..9f34155 100644 --- a/dist/angular-filter.js +++ b/dist/angular-filter.js @@ -1,1655 +1,1675 @@ -/** - * Bunch of useful filters for angularJS(with no external dependencies!) - * @version v0.5.12 - 2016-12-02 * @link https://github.com/a8m/angular-filter - * @author Ariel Mashraki - * @license MIT License, http://www.opensource.org/licenses/MIT - */ -(function ( window, angular, undefined ) { -/*jshint globalstrict:true*/ -'use strict'; - -var isDefined = angular.isDefined, - isUndefined = angular.isUndefined, - isFunction = angular.isFunction, - isString = angular.isString, - isNumber = angular.isNumber, - isObject = angular.isObject, - isArray = angular.isArray, - forEach = angular.forEach, - extend = angular.extend, - copy = angular.copy, - equals = angular.equals; - - -/** - * @description - * get an object and return array of values - * @param object - * @returns {Array} - */ -function toArray(object) { - return isArray(object) - ? object - : Object.keys(object).map(function(key) { - return object[key]; - }); -} - -/** - * @param value - * @returns {boolean} - */ -function isNull(value) { - return value === null; -} - -/** - * @description - * return if object contains partial object - * @param partial{object} - * @param object{object} - * @returns {boolean} - */ -function objectContains(partial, object) { - var keys = Object.keys(partial); - - return keys.map(function(el) { - return (object[el] !== undefined) && (object[el] == partial[el]); - }).indexOf(false) == -1; - -} - -/** - * @description - * search for approximate pattern in string - * @param word - * @param pattern - * @returns {*} - */ -function hasApproxPattern(word, pattern) { - // cheaper version of indexOf; instead of creating each - // iteration new str. - function indexOf(word, p, c) { - var j = 0; - while ((p + j) <= word.length) { - if (word.charAt(p + j) == c) return j; - j++; - } - return -1; - } - var p = 0; - for (var i = 0; i <= pattern.length; i++) { - var index = indexOf(word, p, pattern.charAt(i)); - if (index == -1) return false; - p += index + 1; - } - return true -} - -/** - * @description - * return the first n element of an array, - * if expression provided, is returns as long the expression return truthy - * @param array - * @param n {number} - * @param expression {$parse} - * @return array or single object - */ -function getFirstMatches(array, n, expression) { - var count = 0; - - return array.filter(function(elm) { - var rest = isDefined(expression) ? (count < n && expression(elm)) : count < n; - count = rest ? count+1 : count; - - return rest; - }); -} -/** - * Polyfill to ECMA6 String.prototype.contains - */ -if (!String.prototype.contains) { - String.prototype.contains = function() { - return String.prototype.indexOf.apply(this, arguments) !== -1; - }; -} - -/** - * @param num {Number} - * @param decimal {Number} - * @param $math - * @returns {Number} - */ -function convertToDecimal(num, decimal, $math){ - return $math.round(num * $math.pow(10,decimal)) / ($math.pow(10,decimal)); -} - -/** - * @description - * Get an object, and return an array composed of it's properties names(nested too). - * @param obj {Object} - * @param stack {Array} - * @param parent {String} - * @returns {Array} - * @example - * parseKeys({ a:1, b: { c:2, d: { e: 3 } } }) ==> ["a", "b.c", "b.d.e"] - */ -function deepKeys(obj, stack, parent) { - stack = stack || []; - var keys = Object.keys(obj); - - keys.forEach(function(el) { - //if it's a nested object - if(isObject(obj[el]) && !isArray(obj[el])) { - //concatenate the new parent if exist - var p = parent ? parent + '.' + el : parent; - deepKeys(obj[el], stack, p || el); - } else { - //create and save the key - var key = parent ? parent + '.' + el : el; - stack.push(key) - } - }); - return stack -} - -/** - * @description - * Test if given object is a Scope instance - * @param obj - * @returns {Boolean} - */ -function isScope(obj) { - return obj && obj.$evalAsync && obj.$watch; -} - -/** - * @ngdoc filter - * @name a8m.angular - * @kind function - * - * @description - * reference to angular function - */ - -angular.module('a8m.angular', []) - - .filter('isUndefined', function () { - return function (input) { - return angular.isUndefined(input); - } - }) - .filter('isDefined', function() { - return function (input) { - return angular.isDefined(input); - } - }) - .filter('isFunction', function() { - return function (input) { - return angular.isFunction(input); - } - }) - .filter('isString', function() { - return function (input) { - return angular.isString(input) - } - }) - .filter('isNumber', function() { - return function (input) { - return angular.isNumber(input); - } - }) - .filter('isArray', function() { - return function (input) { - return angular.isArray(input); - } - }) - .filter('isObject', function() { - return function (input) { - return angular.isObject(input); - } - }) - .filter('isEqual', function() { - return function (o1, o2) { - return angular.equals(o1, o2); - } - }); - -/** - * @ngdoc filter - * @name a8m.conditions - * @kind function - * - * @description - * reference to math conditions - */ - angular.module('a8m.conditions', []) - - .filter({ - isGreaterThan : isGreaterThanFilter, - '>' : isGreaterThanFilter, - - isGreaterThanOrEqualTo : isGreaterThanOrEqualToFilter, - '>=' : isGreaterThanOrEqualToFilter, - - isLessThan : isLessThanFilter, - '<' : isLessThanFilter, - - isLessThanOrEqualTo : isLessThanOrEqualToFilter, - '<=' : isLessThanOrEqualToFilter, - - isEqualTo : isEqualToFilter, - '==' : isEqualToFilter, - - isNotEqualTo : isNotEqualToFilter, - '!=' : isNotEqualToFilter, - - isIdenticalTo : isIdenticalToFilter, - '===' : isIdenticalToFilter, - - isNotIdenticalTo : isNotIdenticalToFilter, - '!==' : isNotIdenticalToFilter - }); - - function isGreaterThanFilter() { - return function (input, check) { - return input > check; - }; - } - - function isGreaterThanOrEqualToFilter() { - return function (input, check) { - return input >= check; - }; - } - - function isLessThanFilter() { - return function (input, check) { - return input < check; - }; - } - - function isLessThanOrEqualToFilter() { - return function (input, check) { - return input <= check; - }; - } - - function isEqualToFilter() { - return function (input, check) { - return input == check; - }; - } - - function isNotEqualToFilter() { - return function (input, check) { - return input != check; - }; - } - - function isIdenticalToFilter() { - return function (input, check) { - return input === check; - }; - } - - function isNotIdenticalToFilter() { - return function (input, check) { - return input !== check; - }; - } -/** - * @ngdoc filter - * @name isNull - * @kind function - * - * @description - * checks if value is null or not - * @return Boolean - */ -angular.module('a8m.is-null', []) - .filter('isNull', function () { - return function(input) { - return isNull(input); - } - }); - -/** - * @ngdoc filter - * @name after-where - * @kind function - * - * @description - * get a collection and properties object, and returns all of the items - * in the collection after the first that found with the given properties. - * - */ -angular.module('a8m.after-where', []) - .filter('afterWhere', function() { - return function (collection, object) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(object)) return collection; - - var index = collection.map( function( elm ) { - return objectContains(object, elm); - }).indexOf( true ); - - return collection.slice((index === -1) ? 0 : index); - } - }); - -/** - * @ngdoc filter - * @name after - * @kind function - * - * @description - * get a collection and specified count, and returns all of the items - * in the collection after the specified count. - * - */ - -angular.module('a8m.after', []) - .filter('after', function() { - return function (collection, count) { - collection = isObject(collection) - ? toArray(collection) - : collection; - - return (isArray(collection)) - ? collection.slice(count) - : collection; - } - }); - -/** - * @ngdoc filter - * @name before-where - * @kind function - * - * @description - * get a collection and properties object, and returns all of the items - * in the collection before the first that found with the given properties. - */ -angular.module('a8m.before-where', []) - .filter('beforeWhere', function() { - return function (collection, object) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(object)) return collection; - - var index = collection.map( function( elm ) { - return objectContains(object, elm); - }).indexOf( true ); - - return collection.slice(0, (index === -1) ? collection.length : ++index); - } - }); - -/** - * @ngdoc filter - * @name before - * @kind function - * - * @description - * get a collection and specified count, and returns all of the items - * in the collection before the specified count. - */ -angular.module('a8m.before', []) - .filter('before', function() { - return function (collection, count) { - collection = isObject(collection) - ? toArray(collection) - : collection; - - return (isArray(collection)) - ? collection.slice(0, (!count) ? count : --count) - : collection; - } - }); - -/** - * @ngdoc filter - * @name chunkBy - * @kind function - * - * @description - * Collect data into fixed-length chunks or blocks - */ - -angular.module('a8m.chunk-by', ['a8m.filter-watcher']) - .filter('chunkBy', ['filterWatcher', function (filterWatcher) { - return function (array, n, fillVal) { - - return filterWatcher.isMemoized('chunkBy', arguments) || - filterWatcher.memoize('chunkBy', arguments, this, - _chunkBy(array, n, fillVal)); - /** - * @description - * Get array with size `n` in `val` inside it. - * @param n - * @param val - * @returns {Array} - */ - function fill(n, val) { - var ret = []; - while (n--) ret[n] = val; - return ret; - } - - function _chunkBy(array, n, fillVal) { - if (!isArray(array)) return array; - return array.map(function (el, i, self) { - i = i * n; - el = self.slice(i, i + n); - return !isUndefined(fillVal) && el.length < n - ? el.concat(fill(n - el.length, fillVal)) - : el; - }).slice(0, Math.ceil(array.length / n)); - } - } - }]); - -/** - * @ngdoc filter - * @name concat - * @kind function - * - * @description - * get (array/object, object/array) and return merged collection - */ -angular.module('a8m.concat', []) - .filter('concat', [function () { - return function (collection, joined) { - - if (isUndefined(joined)) return collection; - - if (isArray(collection)) { - return isObject(joined) - ? collection.concat(toArray(joined)) - : collection.concat(joined); - } - - if (isObject(collection)) { - var array = toArray(collection); - return (isObject(joined)) - ? array.concat(toArray(joined)) - : array.concat(joined); - } - return collection; - }; - } -]); - -/** - * @ngdoc filter - * @name contains - * @kind function - * - * @description - * Checks if given expression is present in one or more object in the collection - */ -angular.module('a8m.contains', []) - .filter({ - contains: ['$parse', containsFilter], - some: ['$parse', containsFilter] - }); - -function containsFilter($parse) { - return function (collection, expression) { - - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return false; - } - - return collection.some(function(elm) { - return ((isString(expression) && isObject(elm)) || isFunction(expression)) - ? $parse(expression)(elm) - : elm === expression; - }); - - } - } - -/** - * @ngdoc filter - * @name countBy - * @kind function - * - * @description - * Sorts a list into groups and returns a count for the number of objects in each group. - */ - -angular.module('a8m.count-by', []) - - .filter('countBy', [ '$parse', function ( $parse ) { - return function (collection, property) { - - var result = {}, - get = $parse(property), - prop; - - collection = (isObject(collection)) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(property)) { - return collection; - } - - collection.forEach( function( elm ) { - prop = get(elm); - - if(!result[prop]) { - result[prop] = 0; - } - - result[prop]++; - }); - - return result; - } - }]); - -/** - * @ngdoc filter - * @name defaults - * @kind function - * - * @description - * defaultsFilter allows to specify a default fallback value for properties that resolve to undefined. - */ -angular.module('a8m.defaults', []) - .filter('defaults', ['$parse', function( $parse ) { - return function(collection, defaults) { - - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || !isObject(defaults)) { - return collection; - } - - var keys = deepKeys(defaults); - - collection.forEach(function(elm) { - //loop through all the keys - keys.forEach(function(key) { - var getter = $parse(key); - var setter = getter.assign; - //if it's not exist - if(isUndefined(getter(elm))) { - //get from defaults, and set to the returned object - setter(elm, getter(defaults)) - } - }); - }); - - return collection; - } - }]); -/** - * @ngdoc filter - * @name every - * @kind function - * - * @description - * Checks if given expression is present in all members in the collection - * - */ -angular.module('a8m.every', []) - .filter('every', ['$parse', function($parse) { - return function (collection, expression) { - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return true; - } - - return collection.every( function(elm) { - return (isObject(elm) || isFunction(expression)) - ? $parse(expression)(elm) - : elm === expression; - }); - } - }]); - -/** - * @ngdoc filter - * @name filterBy - * @kind function - * - * @description - * filter by specific properties, avoid the rest - */ -angular.module('a8m.filter-by', []) - .filter('filterBy', ['$parse', function( $parse ) { - return function(collection, properties, search, strict) { - var comparator; - - search = (isString(search) || isNumber(search)) ? - String(search).toLowerCase() : undefined; - - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(search)) { - return collection; - } - - return collection.filter(function(elm) { - return properties.some(function(prop) { - - /** - * check if there is concatenate properties - * example: - * object: { first: 'foo', last:'bar' } - * filterBy: ['first + last'] => search by full name(i.e 'foo bar') - */ - if(!~prop.indexOf('+')) { - comparator = $parse(prop)(elm) - } else { - var propList = prop.replace(/\s+/g, '').split('+'); - comparator = propList - .map(function(prop) { return $parse(prop)(elm); }) - .join(' '); - } - - if (!isString(comparator) && !isNumber(comparator)) { - return false; - } - - comparator = String(comparator).toLowerCase(); - - return strict ? comparator === search : comparator.contains(search); - }); - }); - } - }]); - -/** - * @ngdoc filter - * @name first - * @kind function - * - * @description - * Gets the first element or first n elements of an array - * if callback is provided, is returns as long the callback return truthy - */ -angular.module('a8m.first', []) - .filter('first', ['$parse', function( $parse ) { - return function(collection) { - var n - , getter - , args; - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection)) { - return collection; - } - - args = Array.prototype.slice.call(arguments, 1); - n = (isNumber(args[0])) ? args[0] : 1; - getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; - - return (args.length) ? getFirstMatches(collection, n,(getter) ? $parse(getter) : getter) : - collection[0]; - } - }]); - -/** - * @ngdoc filter - * @name flatten - * @kind function - * - * @description - * Flattens a nested array (the nesting can be to any depth). - * If you pass shallow, the array will only be flattened a single level - */ -angular.module('a8m.flatten', []) - .filter('flatten', function () { - return function(collection, shallow) { - - shallow = shallow || false; - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection)) { - return collection; - } - - return !shallow - ? flatten(collection, 0) - : [].concat.apply([], collection); - } - }); - -/** - * flatten nested array (the nesting can be to any depth). - * @param array {Array} - * @param i {int} - * @returns {Array} - * @private - */ -function flatten(array, i) { - i = i || 0; - - if(i >= array.length) - return array; - - if(isArray(array[i])) { - return flatten(array.slice(0,i) - .concat(array[i], array.slice(i+1)), i); - } - return flatten(array, i+1); -} - -/** - * @ngdoc filter - * @name fuzzyByKey - * @kind function - * - * @description - * fuzzy string searching by key - */ -angular.module('a8m.fuzzy-by', []) - .filter('fuzzyBy', ['$parse', function ( $parse ) { - return function (collection, property, search, csensitive) { - - var sensitive = csensitive || false, - prop, getter; - - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(property) - || isUndefined(search)) { - return collection; - } - - getter = $parse(property); - - return collection.filter(function(elm) { - - prop = getter(elm); - if(!isString(prop)) { - return false; - } - - prop = (sensitive) ? prop : prop.toLowerCase(); - search = (sensitive) ? search : search.toLowerCase(); - - return hasApproxPattern(prop, search) !== false - }) - } - - }]); -/** - * @ngdoc filter - * @name fuzzy - * @kind function - * - * @description - * fuzzy string searching for array of strings, objects - */ -angular.module('a8m.fuzzy', []) - .filter('fuzzy', function () { - return function (collection, search, csensitive) { - var sensitive = csensitive || false; - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(search)) { - return collection; - } - - search = (sensitive) ? search : search.toLowerCase(); - - return collection.filter(function(elm) { - if(isString(elm)) { - elm = (sensitive) ? elm : elm.toLowerCase(); - return hasApproxPattern(elm, search) !== false - } - return (isObject(elm)) ? _hasApproximateKey(elm, search) : false; - }); - - /** - * checks if object has key{string} that match - * to fuzzy search pattern - * @param object - * @param search - * @returns {boolean} - * @private - */ - function _hasApproximateKey(object, search) { - var properties = Object.keys(object), - prop, flag; - return 0 < properties.filter(function (elm) { - prop = object[elm]; - - //avoid iteration if we found some key that equal[performance] - if(flag) return true; - - if (isString(prop)) { - prop = (sensitive) ? prop : prop.toLowerCase(); - return flag = (hasApproxPattern(prop, search) !== false); - } - - return false; - - }).length; - } - } - }); - -/** - * @ngdoc filter - * @name groupBy - * @kind function - * - * @description - * Create an object composed of keys generated from the result of running each element of a collection, - * each key is an array of the elements. - */ - -angular.module('a8m.group-by', [ 'a8m.filter-watcher' ]) - .filter('groupBy', [ '$parse', 'filterWatcher', function ( $parse, filterWatcher ) { - return function (collection, property) { - - if(!isObject(collection) || isUndefined(property)) { - return collection; - } - - return filterWatcher.isMemoized('groupBy', arguments) || - filterWatcher.memoize('groupBy', arguments, this, - _groupBy(collection, $parse(property))); - - /** - * groupBy function - * @param collection - * @param getter - * @returns {{}} - */ - function _groupBy(collection, getter) { - var result = {}; - var prop; - - forEach( collection, function( elm ) { - prop = getter(elm); - - if(!result[prop]) { - result[prop] = []; - } - result[prop].push(elm); - }); - return result; - } - } - }]); - -/** - * @ngdoc filter - * @name isEmpty - * @kind function - * - * @description - * get collection or string and return if it empty - */ -angular.module('a8m.is-empty', []) - .filter('isEmpty', function () { - return function(collection) { - return isObject(collection) - ? !toArray(collection).length - : !collection.length; - } - }); - -/** - * @ngdoc filter - * @name join - * @kind function - * - * @description - * join a collection by a provided delimiter (space by default) - */ -angular.module('a8m.join', []) - .filter('join', function () { - return function (input, delimiter) { - if (isUndefined(input) || !isArray(input)) { - return input; - } - if (isUndefined(delimiter)) delimiter = ' '; - - return input.join(delimiter); - }; - }) -; - -/** - * @ngdoc filter - * @name last - * @kind function - * - * @description - * Gets the last element or last n elements of an array - * if callback is provided, is returns as long the callback return truthy - */ -angular.module('a8m.last', []) - .filter('last', ['$parse', function( $parse ) { - return function(collection) { - var n - , getter - , args - //cuz reverse change our src collection - //and we don't want side effects - , reversed = copy(collection); - - reversed = isObject(reversed) - ? toArray(reversed) - : reversed; - - if(!isArray(reversed)) { - return reversed; - } - - args = Array.prototype.slice.call(arguments, 1); - n = (isNumber(args[0])) ? args[0] : 1; - getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; - - return (args.length) - //send reversed collection as arguments, and reverse it back as result - ? getFirstMatches(reversed.reverse(), n,(getter) ? $parse(getter) : getter).reverse() - //get the last element - : reversed[reversed.length-1]; - } - }]); - -/** - * @ngdoc filter - * @name map - * @kind function - * - * @description - * Returns a new collection of the results of each expression execution. - */ -angular.module('a8m.map', []) - .filter('map', ['$parse', function($parse) { - return function (collection, expression) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return collection; - } - - return collection.map(function (elm) { - return $parse(expression)(elm); - }); - } - }]); - -/** - * @ngdoc filter - * @name omit - * @kind function - * - * @description - * filter collection by expression - */ - -angular.module('a8m.omit', []) - - .filter('omit', ['$parse', function($parse) { - return function (collection, expression) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return collection; - } - - return collection.filter(function (elm) { - return !($parse(expression)(elm)); - }); - } - }]); - -/** - * @ngdoc filter - * @name pick - * @kind function - * - * @description - * filter collection by expression - */ - -angular.module('a8m.pick', []) - - .filter('pick', ['$parse', function($parse) { - return function (collection, expression) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return collection; - } - - return collection.filter(function (elm) { - return $parse(expression)(elm); - }); - } - }]); - -/** - * @ngdoc filter - * @name range - * @kind function - * - * @description - * rangeFilter provides some support for a for loop using numbers - */ -angular.module('a8m.range', []) - .filter('range', function () { - return function (input, total, start, increment, cb) { - start = start || 0; - increment = increment || 1; - for (var i = 0; i < parseInt(total); i++) { - var j = start + i * increment; - input.push(isFunction(cb) ? cb(j) : j); - } - return input; - }; - }); -/** - * @ngdoc filter - * @name removeWith - * @kind function - * - * @description - * get collection and properties object, and removed elements - * with this properties - */ - -angular.module('a8m.remove-with', []) - .filter('removeWith', function() { - return function (collection, object) { - - if(isUndefined(object)) { - return collection; - } - collection = isObject(collection) - ? toArray(collection) - : collection; - - return collection.filter(function (elm) { - return !objectContains(object, elm); - }); - } - }); - - -/** - * @ngdoc filter - * @name remove - * @kind function - * - * @description - * remove specific members from collection - */ - -angular.module('a8m.remove', []) - - .filter('remove', function () { - return function (collection) { - collection = isObject(collection) ? toArray(collection) : collection; - var args = Array.prototype.slice.call(arguments, 1); - - if(!isArray(collection)) { - return collection; - } - - return collection.filter( function( member ) { - return !args.some(function(nest) { - return equals(nest, member); - }) - }); - } - }); - -/** - * @ngdoc filter - * @name reverse - * @kind function - * - * @description - * Reverses a string or collection - */ -angular.module('a8m.reverse', []) - .filter('reverse',[ function () { - return function (input) { - input = isObject(input) ? toArray(input) : input; - - if(isString(input)) { - return input.split('').reverse().join(''); - } - - return isArray(input) - ? input.slice().reverse() - : input; - } - }]); - -/** - * @ngdoc filter - * @name searchField - * @kind function - * - * @description - * for each member, join several strings field and add them to - * new field called 'searchField' (use for search filtering) - */ -angular.module('a8m.search-field', []) - .filter('searchField', ['$parse', function ($parse) { - return function (collection) { - - var get, field; - - collection = isObject(collection) ? toArray(collection) : collection; - - var args = Array.prototype.slice.call(arguments, 1); - - if(!isArray(collection) || !args.length) { - return collection; - } - - return collection.map(function(member) { - - field = args.map(function(field) { - get = $parse(field); - return get(member); - }).join(' '); - - return extend(member, { searchField: field }); - }); - } - }]); - -/** - * @ngdoc filter - * @name toArray - * @kind function - * - * @description - * Convert objects into stable arrays. - * if addKey set to true,the filter also attaches a new property - * $key to the value containing the original key that was used in - * the object we are iterating over to reference the property - */ -angular.module('a8m.to-array', []) - .filter('toArray', function() { - return function (collection, addKey) { - - if(!isObject(collection)) { - return collection; - } - - return !addKey - ? toArray(collection) - : Object.keys(collection).map(function (key) { - return extend(collection[key], { $key: key }); - }); - } - }); - -/** - * @ngdoc filter - * @name unique/uniq - * @kind function - * - * @description - * get collection and filter duplicate members - * if uniqueFilter get a property(nested to) as argument it's - * filter by this property as unique identifier - */ - -angular.module('a8m.unique', []) - .filter({ - unique: ['$parse', uniqFilter], - uniq: ['$parse', uniqFilter] - }); - -function uniqFilter($parse) { - return function (collection, property) { - - collection = isObject(collection) ? toArray(collection) : collection; - - if (!isArray(collection)) { - return collection; - } - - //store all unique identifiers - var uniqueItems = [], - get = $parse(property); - - return (isUndefined(property)) - //if it's kind of primitive array - ? collection.filter(function (elm, pos, self) { - return self.indexOf(elm) === pos; - }) - //else compare with equals - : collection.filter(function (elm) { - var prop = get(elm); - if(some(uniqueItems, prop)) { - return false; - } - uniqueItems.push(prop); - return true; - }); - - //checked if the unique identifier is already exist - function some(array, member) { - if(isUndefined(member)) { - return false; - } - return array.some(function(el) { - return equals(el, member); - }); - } - } -} - -/** - * @ngdoc filter - * @name where - * @kind function - * - * @description - * of each element in a collection to the given properties object, - * returning an array of all elements that have equivalent property values. - * - */ -angular.module('a8m.where', []) - .filter('where', function() { - return function (collection, object) { - if(isUndefined(object)) return collection; - collection = isObject(collection) - ? toArray(collection) - : collection; - - return collection.filter(function (elm) { - return objectContains(object, elm); - }); - } - }); - -/** - * @ngdoc filter - * @name xor - * @kind function - * - * @description - * Exclusive or filter by expression - */ - -angular.module('a8m.xor', []) - - .filter('xor', ['$parse', function($parse) { - return function (col1, col2, expression) { - - expression = expression || false; - - col1 = isObject(col1) ? toArray(col1) : col1; - col2 = isObject(col2) ? toArray(col2) : col2; - - if(!isArray(col1) || !isArray(col2)) return col1; - - return col1.concat(col2) - .filter(function(elm) { - return !(some(elm, col1) && some(elm, col2)); - }); - - function some(el, col) { - var getter = $parse(expression); - return col.some(function(dElm) { - return expression - ? equals(getter(dElm), getter(el)) - : equals(dElm, el); - }); - } - } - }]); - -/** - * @ngdoc filter - * @name abs - * @kind function - * - * @description - * Will return the absolute value of a number - */ -angular.module('a8m.math.abs', []) - .filter('abs', function () { - return function (input) { - return Math.abs(input); - } - }); - -/** - * @ngdoc filter - * @name formatBytes - * @kind function - * - * @description - * Convert bytes into appropriate display - * 1024 bytes => 1 KB - */ -angular.module('a8m.math.byteFmt', ['a8m.math']) - .filter('byteFmt', ['$math', function ($math) { - return function (bytes, decimal) { - - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(bytes) && isFinite(bytes)) { - if(bytes < 1024) { // within 1 KB so B - return convertToDecimal(bytes, decimal, $math) + ' B'; - } else if(bytes < 1048576) { // within 1 MB so KB - return convertToDecimal((bytes / 1024), decimal, $math) + ' KB'; - } else if(bytes < 1073741824){ // within 1 GB so MB - return convertToDecimal((bytes / 1048576), decimal, $math) + ' MB'; - } else { // GB or more - return convertToDecimal((bytes / 1073741824), decimal, $math) + ' GB'; - } - - } - return "NaN"; - } - }]); -/** - * @ngdoc filter - * @name degrees - * @kind function - * - * @description - * Convert angle from radians to degrees - */ -angular.module('a8m.math.degrees', ['a8m.math']) - .filter('degrees', ['$math', function ($math) { - return function (radians, decimal) { - // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" - // if degrees is not a real number, we cannot do also. quit with error "NaN" - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(radians) && isFinite(radians)) { - var degrees = (radians * 180) / $math.PI; - return $math.round(degrees * $math.pow(10,decimal)) / ($math.pow(10,decimal)); - } else { - return "NaN"; - } - } - }]); - - - -/** - * @ngdoc filter - * @name formatBytes - * @kind function - * - * @description - * Convert bytes into appropriate display - * 1024 kilobytes => 1 MB - */ -angular.module('a8m.math.kbFmt', ['a8m.math']) - .filter('kbFmt', ['$math', function ($math) { - return function (bytes, decimal) { - - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(bytes) && isFinite(bytes)) { - if(bytes < 1024) { // within 1 MB so KB - return convertToDecimal(bytes, decimal, $math) + ' KB'; - } else if(bytes < 1048576) { // within 1 GB so MB - return convertToDecimal((bytes / 1024), decimal, $math) + ' MB'; - } else { - return convertToDecimal((bytes / 1048576), decimal, $math) + ' GB'; - } - } - return "NaN"; - } - }]); -/** - * @ngdoc module - * @name math - * @description - * reference to global Math object - */ -angular.module('a8m.math', []) - .factory('$math', ['$window', function ($window) { - return $window.Math; - }]); - -/** - * @ngdoc filter - * @name max - * @kind function - * - * @description - * Math.max will get an array and return the max value. if an expression - * is provided, will return max value by expression. - */ -angular.module('a8m.math.max', ['a8m.math']) - .filter('max', ['$math', '$parse', function ($math, $parse) { - return function (input, expression) { - - if(!isArray(input)) { - return input; - } - return isUndefined(expression) - ? $math.max.apply($math, input) - : input[indexByMax(input, expression)]; - }; - - /** - * @private - * @param array - * @param exp - * @returns {number|*|Number} - */ - function indexByMax(array, exp) { - var mappedArray = array.map(function(elm){ - return $parse(exp)(elm); - }); - return mappedArray.indexOf($math.max.apply($math, mappedArray)); - } - }]); -/** - * @ngdoc filter - * @name min - * @kind function - * - * @description - * Math.min will get an array and return the min value. if an expression - * is provided, will return min value by expression. - */ -angular.module('a8m.math.min', ['a8m.math']) - .filter('min', ['$math', '$parse', function ($math, $parse) { - return function (input, expression) { - - if(!isArray(input)) { - return input; - } - return isUndefined(expression) - ? $math.min.apply($math, input) - : input[indexByMin(input, expression)]; - }; - - /** - * @private - * @param array - * @param exp - * @returns {number|*|Number} - */ - function indexByMin(array, exp) { - var mappedArray = array.map(function(elm){ - return $parse(exp)(elm); - }); - return mappedArray.indexOf($math.min.apply($math, mappedArray)); - } - }]); -/** - * @ngdoc filter - * @name Percent - * @kind function - * - * @description - * percentage between two numbers - */ -angular.module('a8m.math.percent', ['a8m.math']) - .filter('percent', ['$math', '$window', function ($math, $window) { - return function (input, divided, round) { - - var divider = isString(input) ? $window.Number(input) : input; - divided = divided || 100; - round = round || false; - - if (!isNumber(divider) || $window.isNaN(divider)) return input; - - return round - ? $math.round((divider / divided) * 100) - : (divider / divided) * 100; - } - }]); - -/** - * @ngdoc filter - * @name toRadians - * @kind function - * - * @description - * Convert angle from degrees to radians - */ -angular.module('a8m.math.radians', ['a8m.math']) - .filter('radians', ['$math', function ($math) { - return function (degrees, decimal) { - // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" - // if degrees is not a real number, we cannot do also. quit with error "NaN" - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(degrees) && isFinite(degrees)) { - var radians = (degrees * 3.14159265359) / 180; - return $math.round(radians * $math.pow(10,decimal)) / ($math.pow(10,decimal)); - } - return "NaN"; - } - }]); - - - -/** - * @ngdoc filter - * @name Radix - * @kind function - * - * @description - * converting decimal numbers to different bases(radix) - */ -angular.module('a8m.math.radix', []) - .filter('radix', function () { - return function (input, radix) { - var RANGE = /^[2-9]$|^[1-2]\d$|^3[0-6]$/; - - if(!isNumber(input) || !RANGE.test(radix)) { - return input; - } - - return input.toString(radix).toUpperCase(); - } - }); - -/** - * @ngdoc filter - * @name formatBytes - * @kind function - * - * @description - * Convert number into abbreviations. - * i.e: K for one thousand, M for Million, B for billion - * e.g: number of users:235,221, decimal:1 => 235.2 K - */ -angular.module('a8m.math.shortFmt', ['a8m.math']) - .filter('shortFmt', ['$math', function ($math) { - return function (number, decimal) { - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(number) && isFinite(number)){ - if(number < 1e3) { - return number; - } else if(number < 1e6) { - return convertToDecimal((number / 1e3), decimal, $math) + ' K'; - } else if(number < 1e9){ - return convertToDecimal((number / 1e6), decimal, $math) + ' M'; - } else { - return convertToDecimal((number / 1e9), decimal, $math) + ' B'; - } - - } - return "NaN"; - } - }]); -/** - * @ngdoc filter - * @name sum - * @kind function - * - * @description - * Sum up all values within an array - */ -angular.module('a8m.math.sum', []) - .filter('sum', function () { - return function (input, initial) { - return !isArray(input) - ? input - : input.reduce(function(prev, curr) { - return prev + curr; - }, initial || 0); - } - }); - -/** - * @ngdoc filter - * @name endsWith - * @kind function - * - * @description - * checks whether string ends with the ends parameter. - */ -angular.module('a8m.ends-with', []) - - .filter('endsWith', function () { - return function (input, ends, csensitive) { - - var sensitive = csensitive || false, - position; - - if(!isString(input) || isUndefined(ends)) { - return input; - } - - input = (sensitive) ? input : input.toLowerCase(); - position = input.length - ends.length; - - return input.indexOf((sensitive) ? ends : ends.toLowerCase(), position) !== -1; - } - }); - +/** + * Bunch of useful filters for angularJS(with no external dependencies!) + * @version v0.5.12 - 2016-12-03 * @link https://github.com/a8m/angular-filter + * @author Ariel Mashraki + * @license MIT License, http://www.opensource.org/licenses/MIT + */ +(function ( window, angular, undefined ) { +/*jshint globalstrict:true*/ +'use strict'; + +var isDefined = angular.isDefined, + isUndefined = angular.isUndefined, + isFunction = angular.isFunction, + isString = angular.isString, + isNumber = angular.isNumber, + isObject = angular.isObject, + isArray = angular.isArray, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + equals = angular.equals; + + +/** + * @description + * get an object and return array of values + * @param object + * @returns {Array} + */ +function toArray(object) { + return isArray(object) + ? object + : Object.keys(object).map(function(key) { + return object[key]; + }); +} + +/** + * @param value + * @returns {boolean} + */ +function isNull(value) { + return value === null; +} + +/** + * @description + * return if object contains partial object + * @param partial{object} + * @param object{object} + * @returns {boolean} + */ +function objectContains(partial, object) { + var keys = Object.keys(partial); + + return keys.map(function(el) { + return (object[el] !== undefined) && (object[el] == partial[el]); + }).indexOf(false) == -1; + +} + +/** + * @description + * search for approximate pattern in string + * @param word + * @param pattern + * @returns {*} + */ +function hasApproxPattern(word, pattern) { + // cheaper version of indexOf; instead of creating each + // iteration new str. + function indexOf(word, p, c) { + var j = 0; + while ((p + j) <= word.length) { + if (word.charAt(p + j) == c) return j; + j++; + } + return -1; + } + var p = 0; + for (var i = 0; i <= pattern.length; i++) { + var index = indexOf(word, p, pattern.charAt(i)); + if (index == -1) return false; + p += index + 1; + } + return true +} + +/** + * @description + * return the first n element of an array, + * if expression provided, is returns as long the expression return truthy + * @param array + * @param n {number} + * @param expression {$parse} + * @return array or single object + */ +function getFirstMatches(array, n, expression) { + var count = 0; + + return array.filter(function(elm) { + var rest = isDefined(expression) ? (count < n && expression(elm)) : count < n; + count = rest ? count+1 : count; + + return rest; + }); +} +/** + * Polyfill to ECMA6 String.prototype.contains + */ +if (!String.prototype.contains) { + String.prototype.contains = function() { + return String.prototype.indexOf.apply(this, arguments) !== -1; + }; +} + +/** + * @param num {Number} + * @param decimal {Number} + * @param $math + * @returns {Number} + */ +function convertToDecimal(num, decimal, $math){ + return $math.round(num * $math.pow(10,decimal)) / ($math.pow(10,decimal)); +} + +/** + * @description + * Get an object, and return an array composed of it's properties names(nested too). + * @param obj {Object} + * @param stack {Array} + * @param parent {String} + * @returns {Array} + * @example + * parseKeys({ a:1, b: { c:2, d: { e: 3 } } }) ==> ["a", "b.c", "b.d.e"] + */ +function deepKeys(obj, stack, parent) { + stack = stack || []; + var keys = Object.keys(obj); + + keys.forEach(function(el) { + //if it's a nested object + if(isObject(obj[el]) && !isArray(obj[el])) { + //concatenate the new parent if exist + var p = parent ? parent + '.' + el : parent; + deepKeys(obj[el], stack, p || el); + } else { + //create and save the key + var key = parent ? parent + '.' + el : el; + stack.push(key) + } + }); + return stack +} + +/** + * @description + * Test if given object is a Scope instance + * @param obj + * @returns {Boolean} + */ +function isScope(obj) { + return obj && obj.$evalAsync && obj.$watch; +} + +/** + * @ngdoc filter + * @name a8m.angular + * @kind function + * + * @description + * reference to angular function + */ + +angular.module('a8m.angular', []) + + .filter('isUndefined', function () { + return function (input) { + return angular.isUndefined(input); + } + }) + .filter('isDefined', function() { + return function (input) { + return angular.isDefined(input); + } + }) + .filter('isFunction', function() { + return function (input) { + return angular.isFunction(input); + } + }) + .filter('isString', function() { + return function (input) { + return angular.isString(input) + } + }) + .filter('isNumber', function() { + return function (input) { + return angular.isNumber(input); + } + }) + .filter('isArray', function() { + return function (input) { + return angular.isArray(input); + } + }) + .filter('isObject', function() { + return function (input) { + return angular.isObject(input); + } + }) + .filter('isEqual', function() { + return function (o1, o2) { + return angular.equals(o1, o2); + } + }); + +/** + * @ngdoc filter + * @name a8m.conditions + * @kind function + * + * @description + * reference to math conditions + */ + angular.module('a8m.conditions', []) + + .filter({ + isGreaterThan : isGreaterThanFilter, + '>' : isGreaterThanFilter, + + isGreaterThanOrEqualTo : isGreaterThanOrEqualToFilter, + '>=' : isGreaterThanOrEqualToFilter, + + isLessThan : isLessThanFilter, + '<' : isLessThanFilter, + + isLessThanOrEqualTo : isLessThanOrEqualToFilter, + '<=' : isLessThanOrEqualToFilter, + + isEqualTo : isEqualToFilter, + '==' : isEqualToFilter, + + isNotEqualTo : isNotEqualToFilter, + '!=' : isNotEqualToFilter, + + isIdenticalTo : isIdenticalToFilter, + '===' : isIdenticalToFilter, + + isNotIdenticalTo : isNotIdenticalToFilter, + '!==' : isNotIdenticalToFilter + }); + + function isGreaterThanFilter() { + return function (input, check) { + return input > check; + }; + } + + function isGreaterThanOrEqualToFilter() { + return function (input, check) { + return input >= check; + }; + } + + function isLessThanFilter() { + return function (input, check) { + return input < check; + }; + } + + function isLessThanOrEqualToFilter() { + return function (input, check) { + return input <= check; + }; + } + + function isEqualToFilter() { + return function (input, check) { + return input == check; + }; + } + + function isNotEqualToFilter() { + return function (input, check) { + return input != check; + }; + } + + function isIdenticalToFilter() { + return function (input, check) { + return input === check; + }; + } + + function isNotIdenticalToFilter() { + return function (input, check) { + return input !== check; + }; + } +/** + * @ngdoc filter + * @name isNull + * @kind function + * + * @description + * checks if value is null or not + * @return Boolean + */ +angular.module('a8m.is-null', []) + .filter('isNull', function () { + return function(input) { + return isNull(input); + } + }); + +/** + * @ngdoc filter + * @name after-where + * @kind function + * + * @description + * get a collection and properties object, and returns all of the items + * in the collection after the first that found with the given properties. + * + */ +angular.module('a8m.after-where', []) + .filter('afterWhere', function() { + return function (collection, object) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(object)) return collection; + + var index = collection.map( function( elm ) { + return objectContains(object, elm); + }).indexOf( true ); + + return collection.slice((index === -1) ? 0 : index); + } + }); + +/** + * @ngdoc filter + * @name after + * @kind function + * + * @description + * get a collection and specified count, and returns all of the items + * in the collection after the specified count. + * + */ + +angular.module('a8m.after', []) + .filter('after', function() { + return function (collection, count) { + collection = isObject(collection) + ? toArray(collection) + : collection; + + return (isArray(collection)) + ? collection.slice(count) + : collection; + } + }); + +/** + * @ngdoc filter + * @name before-where + * @kind function + * + * @description + * get a collection and properties object, and returns all of the items + * in the collection before the first that found with the given properties. + */ +angular.module('a8m.before-where', []) + .filter('beforeWhere', function() { + return function (collection, object) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(object)) return collection; + + var index = collection.map( function( elm ) { + return objectContains(object, elm); + }).indexOf( true ); + + return collection.slice(0, (index === -1) ? collection.length : ++index); + } + }); + +/** + * @ngdoc filter + * @name before + * @kind function + * + * @description + * get a collection and specified count, and returns all of the items + * in the collection before the specified count. + */ +angular.module('a8m.before', []) + .filter('before', function() { + return function (collection, count) { + collection = isObject(collection) + ? toArray(collection) + : collection; + + return (isArray(collection)) + ? collection.slice(0, (!count) ? count : --count) + : collection; + } + }); + +/** + * @ngdoc filter + * @name chunkBy + * @kind function + * + * @description + * Collect data into fixed-length chunks or blocks + */ + +angular.module('a8m.chunk-by', ['a8m.filter-watcher']) + .filter('chunkBy', ['filterWatcher', function (filterWatcher) { + return function (array, n, fillVal) { + + return filterWatcher.isMemoized('chunkBy', arguments) || + filterWatcher.memoize('chunkBy', arguments, this, + _chunkBy(array, n, fillVal)); + /** + * @description + * Get array with size `n` in `val` inside it. + * @param n + * @param val + * @returns {Array} + */ + function fill(n, val) { + var ret = []; + while (n--) ret[n] = val; + return ret; + } + + function _chunkBy(array, n, fillVal) { + if (!isArray(array)) return array; + return array.map(function (el, i, self) { + i = i * n; + el = self.slice(i, i + n); + return !isUndefined(fillVal) && el.length < n + ? el.concat(fill(n - el.length, fillVal)) + : el; + }).slice(0, Math.ceil(array.length / n)); + } + } + }]); + +/** + * @ngdoc filter + * @name concat + * @kind function + * + * @description + * get (array/object, object/array) and return merged collection + */ +angular.module('a8m.concat', []) + .filter('concat', [function () { + return function (collection, joined) { + + if (isUndefined(joined)) return collection; + + if (isArray(collection)) { + return isObject(joined) + ? collection.concat(toArray(joined)) + : collection.concat(joined); + } + + if (isObject(collection)) { + var array = toArray(collection); + return (isObject(joined)) + ? array.concat(toArray(joined)) + : array.concat(joined); + } + return collection; + }; + } +]); + +/** + * @ngdoc filter + * @name contains + * @kind function + * + * @description + * Checks if given expression is present in one or more object in the collection + */ +angular.module('a8m.contains', []) + .filter({ + contains: ['$parse', containsFilter], + some: ['$parse', containsFilter] + }); + +function containsFilter($parse) { + return function (collection, expression) { + + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return false; + } + + return collection.some(function(elm) { + return ((isString(expression) && isObject(elm)) || isFunction(expression)) + ? $parse(expression)(elm) + : elm === expression; + }); + + } + } + +/** + * @ngdoc filter + * @name countBy + * @kind function + * + * @description + * Sorts a list into groups and returns a count for the number of objects in each group. + */ + +angular.module('a8m.count-by', []) + + .filter('countBy', [ '$parse', function ( $parse ) { + return function (collection, property) { + + var result = {}, + get = $parse(property), + prop; + + collection = (isObject(collection)) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(property)) { + return collection; + } + + collection.forEach( function( elm ) { + prop = get(elm); + + if(!result[prop]) { + result[prop] = 0; + } + + result[prop]++; + }); + + return result; + } + }]); + +/** + * @ngdoc filter + * @name defaults + * @kind function + * + * @description + * defaultsFilter allows to specify a default fallback value for properties that resolve to undefined. + */ +angular.module('a8m.defaults', []) + .filter('defaults', ['$parse', function( $parse ) { + return function(collection, defaults) { + + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || !isObject(defaults)) { + return collection; + } + + var keys = deepKeys(defaults); + + collection.forEach(function(elm) { + //loop through all the keys + keys.forEach(function(key) { + var getter = $parse(key); + var setter = getter.assign; + //if it's not exist + if(isUndefined(getter(elm))) { + //get from defaults, and set to the returned object + setter(elm, getter(defaults)) + } + }); + }); + + return collection; + } + }]); +/** + * @ngdoc filter + * @name every + * @kind function + * + * @description + * Checks if given expression is present in all members in the collection + * + */ +angular.module('a8m.every', []) + .filter('every', ['$parse', function($parse) { + return function (collection, expression) { + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return true; + } + + return collection.every( function(elm) { + return (isObject(elm) || isFunction(expression)) + ? $parse(expression)(elm) + : elm === expression; + }); + } + }]); + +/** + * @ngdoc filter + * @name filterBy + * @kind function + * + * @description + * filter by specific properties, avoid the rest + */ +angular.module('a8m.filter-by', []) + .filter('filterBy', ['$parse', function( $parse ) { + return function(collection, properties, search, strict) { + var comparator; + + search = (isString(search) || isNumber(search)) ? + String(search).toLowerCase() : undefined; + + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(search)) { + return collection; + } + + return collection.filter(function(elm) { + return properties.some(function(prop) { + + /** + * check if there is concatenate properties + * example: + * object: { first: 'foo', last:'bar' } + * filterBy: ['first + last'] => search by full name(i.e 'foo bar') + */ + if(!~prop.indexOf('+')) { + comparator = $parse(prop)(elm) + } else { + var propList = prop.replace(/\s+/g, '').split('+'); + comparator = propList + .map(function(prop) { return $parse(prop)(elm); }) + .join(' '); + } + + if (!isString(comparator) && !isNumber(comparator)) { + return false; + } + + comparator = String(comparator).toLowerCase(); + + return strict ? comparator === search : comparator.contains(search); + }); + }); + } + }]); + +/** + * @ngdoc filter + * @name first + * @kind function + * + * @description + * Gets the first element or first n elements of an array + * if callback is provided, is returns as long the callback return truthy + */ +angular.module('a8m.first', []) + .filter('first', ['$parse', function( $parse ) { + return function(collection) { + var n + , getter + , args; + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection)) { + return collection; + } + + args = Array.prototype.slice.call(arguments, 1); + n = (isNumber(args[0])) ? args[0] : 1; + getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; + + return (args.length) ? getFirstMatches(collection, n,(getter) ? $parse(getter) : getter) : + collection[0]; + } + }]); + +/** + * @ngdoc filter + * @name flatten + * @kind function + * + * @description + * Flattens a nested array (the nesting can be to any depth). + * If you pass shallow, the array will only be flattened a single level + */ +angular.module('a8m.flatten', []) + .filter('flatten', function () { + return function(collection, shallow) { + + shallow = shallow || false; + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection)) { + return collection; + } + + return !shallow + ? flatten(collection, 0) + : [].concat.apply([], collection); + } + }); + +/** + * flatten nested array (the nesting can be to any depth). + * @param array {Array} + * @param i {int} + * @returns {Array} + * @private + */ +function flatten(array, i) { + i = i || 0; + + if(i >= array.length) + return array; + + if(isArray(array[i])) { + return flatten(array.slice(0,i) + .concat(array[i], array.slice(i+1)), i); + } + return flatten(array, i+1); +} + +/** + * @ngdoc filter + * @name fuzzyByKey + * @kind function + * + * @description + * fuzzy string searching by key + */ +angular.module('a8m.fuzzy-by', []) + .filter('fuzzyBy', ['$parse', function ( $parse ) { + return function (collection, property, search, csensitive) { + + var sensitive = csensitive || false, + prop, getter; + + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(property) + || isUndefined(search)) { + return collection; + } + + getter = $parse(property); + + return collection.filter(function(elm) { + + prop = getter(elm); + if(!isString(prop)) { + return false; + } + + prop = (sensitive) ? prop : prop.toLowerCase(); + search = (sensitive) ? search : search.toLowerCase(); + + return hasApproxPattern(prop, search) !== false + }) + } + + }]); +/** + * @ngdoc filter + * @name fuzzy + * @kind function + * + * @description + * fuzzy string searching for array of strings, objects + */ +angular.module('a8m.fuzzy', []) + .filter('fuzzy', function () { + return function (collection, search, csensitive) { + var sensitive = csensitive || false; + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(search)) { + return collection; + } + + search = (sensitive) ? search : search.toLowerCase(); + + return collection.filter(function(elm) { + if(isString(elm)) { + elm = (sensitive) ? elm : elm.toLowerCase(); + return hasApproxPattern(elm, search) !== false + } + return (isObject(elm)) ? _hasApproximateKey(elm, search) : false; + }); + + /** + * checks if object has key{string} that match + * to fuzzy search pattern + * @param object + * @param search + * @returns {boolean} + * @private + */ + function _hasApproximateKey(object, search) { + var properties = Object.keys(object), + prop, flag; + return 0 < properties.filter(function (elm) { + prop = object[elm]; + + //avoid iteration if we found some key that equal[performance] + if(flag) return true; + + if (isString(prop)) { + prop = (sensitive) ? prop : prop.toLowerCase(); + return flag = (hasApproxPattern(prop, search) !== false); + } + + return false; + + }).length; + } + } + }); + +/** + * @ngdoc filter + * @name groupBy + * @kind function + * + * @description + * Create an object composed of keys generated from the result of running each element of a collection, + * each key is an array of the elements. + */ + +angular.module('a8m.group-by', [ 'a8m.filter-watcher' ]) + .filter('groupBy', [ '$parse', 'filterWatcher', function ( $parse, filterWatcher ) { + return function (collection, property) { + + if(!isObject(collection) || isUndefined(property)) { + return collection; + } + + return filterWatcher.isMemoized('groupBy', arguments) || + filterWatcher.memoize('groupBy', arguments, this, + _groupBy(collection, $parse(property))); + + /** + * groupBy function + * @param collection + * @param getter + * @returns {{}} + */ + function _groupBy(collection, getter) { + var result = {}; + var prop; + + forEach( collection, function( elm ) { + prop = getter(elm); + + if(!result[prop]) { + result[prop] = []; + } + result[prop].push(elm); + }); + return result; + } + } + }]); + +/** + * @ngdoc filter + * @name isEmpty + * @kind function + * + * @description + * get collection or string and return if it empty + */ +angular.module('a8m.is-empty', []) + .filter('isEmpty', function () { + return function(collection) { + return isObject(collection) + ? !toArray(collection).length + : !collection.length; + } + }); + +/** + * @ngdoc filter + * @name join + * @kind function + * + * @description + * join a collection by a provided delimiter (space by default) + */ +angular.module('a8m.join', []) + .filter('join', function () { + return function (input, delimiter) { + if (isUndefined(input) || !isArray(input)) { + return input; + } + if (isUndefined(delimiter)) delimiter = ' '; + + return input.join(delimiter); + }; + }) +; + +/** + * @ngdoc filter + * @name last + * @kind function + * + * @description + * Gets the last element or last n elements of an array + * if callback is provided, is returns as long the callback return truthy + */ +angular.module('a8m.last', []) + .filter('last', ['$parse', function( $parse ) { + return function(collection) { + var n + , getter + , args + //cuz reverse change our src collection + //and we don't want side effects + , reversed = copy(collection); + + reversed = isObject(reversed) + ? toArray(reversed) + : reversed; + + if(!isArray(reversed)) { + return reversed; + } + + args = Array.prototype.slice.call(arguments, 1); + n = (isNumber(args[0])) ? args[0] : 1; + getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; + + return (args.length) + //send reversed collection as arguments, and reverse it back as result + ? getFirstMatches(reversed.reverse(), n,(getter) ? $parse(getter) : getter).reverse() + //get the last element + : reversed[reversed.length-1]; + } + }]); + +/** + * @ngdoc filter + * @name map + * @kind function + * + * @description + * Returns a new collection of the results of each expression execution. + */ +angular.module('a8m.map', []) + .filter('map', ['$parse', function($parse) { + return function (collection, expression) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return collection; + } + + return collection.map(function (elm) { + return $parse(expression)(elm); + }); + } + }]); + +/** + * @ngdoc filter + * @name omit + * @kind function + * + * @description + * filter collection by expression + */ + +angular.module('a8m.omit', []) + + .filter('omit', ['$parse', function($parse) { + return function (collection, expression) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return collection; + } + + return collection.filter(function (elm) { + return !($parse(expression)(elm)); + }); + } + }]); + +/** + * @ngdoc filter + * @name pick + * @kind function + * + * @description + * filter collection by expression + */ + +angular.module('a8m.pick', []) + + .filter('pick', ['$parse', function($parse) { + return function (collection, expression) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return collection; + } + + return collection.filter(function (elm) { + return $parse(expression)(elm); + }); + } + }]); + +/** + * @ngdoc filter + * @name range + * @kind function + * + * @description + * rangeFilter provides some support for a for loop using numbers + */ +angular.module('a8m.range', []) + .filter('range', function () { + return function (input, total, start, increment, cb) { + start = start || 0; + increment = increment || 1; + for (var i = 0; i < parseInt(total); i++) { + var j = start + i * increment; + input.push(isFunction(cb) ? cb(j) : j); + } + return input; + }; + }); +/** + * @ngdoc filter + * @name removeWith + * @kind function + * + * @description + * get collection and properties object, and removed elements + * with this properties + */ + +angular.module('a8m.remove-with', []) + .filter('removeWith', function() { + return function (collection, object) { + + if(isUndefined(object)) { + return collection; + } + collection = isObject(collection) + ? toArray(collection) + : collection; + + return collection.filter(function (elm) { + return !objectContains(object, elm); + }); + } + }); + + +/** + * @ngdoc filter + * @name remove + * @kind function + * + * @description + * remove specific members from collection + */ + +angular.module('a8m.remove', []) + + .filter('remove', function () { + return function (collection) { + collection = isObject(collection) ? toArray(collection) : collection; + var args = Array.prototype.slice.call(arguments, 1); + + if(!isArray(collection)) { + return collection; + } + + return collection.filter( function( member ) { + return !args.some(function(nest) { + return equals(nest, member); + }) + }); + } + }); + +/** + * @ngdoc filter + * @name reverse + * @kind function + * + * @description + * Reverses a string or collection + */ +angular.module('a8m.reverse', []) + .filter('reverse',[ function () { + return function (input) { + input = isObject(input) ? toArray(input) : input; + + if(isString(input)) { + return input.split('').reverse().join(''); + } + + return isArray(input) + ? input.slice().reverse() + : input; + } + }]); + +/** + * @ngdoc filter + * @name searchField + * @kind function + * + * @description + * for each member, join several strings field and add them to + * new field called 'searchField' (use for search filtering) + */ +angular.module('a8m.search-field', []) + .filter('searchField', ['$parse', function ($parse) { + return function (collection) { + + var get, field; + + collection = isObject(collection) ? toArray(collection) : collection; + + var args = Array.prototype.slice.call(arguments, 1); + + if(!isArray(collection) || !args.length) { + return collection; + } + + return collection.map(function(member) { + + field = args.map(function(field) { + get = $parse(field); + return get(member); + }).join(' '); + + return extend(member, { searchField: field }); + }); + } + }]); + +/** + * @ngdoc filter + * @name toArray + * @kind function + * + * @description + * Convert objects into stable arrays. + * if addKey set to true,the filter also attaches a new property + * $key to the value containing the original key that was used in + * the object we are iterating over to reference the property + */ +angular.module('a8m.to-array', []) + .filter('toArray', function() { + return function (collection, addKey) { + + if(!isObject(collection)) { + return collection; + } + + return !addKey + ? toArray(collection) + : Object.keys(collection).map(function (key) { + return extend(collection[key], { $key: key }); + }); + } + }); + +/** + * @ngdoc filter + * @name unique/uniq + * @kind function + * + * @description + * get collection and filter duplicate members + * if uniqueFilter get a property(nested to) as argument it's + * filter by this property as unique identifier + */ + +angular.module('a8m.unique', []) + .filter({ + unique: ['$parse', uniqFilter], + uniq: ['$parse', uniqFilter] + }); + +function uniqFilter($parse) { + return function (collection, property) { + + collection = isObject(collection) ? toArray(collection) : collection; + + if (!isArray(collection)) { + return collection; + } + + //store all unique identifiers + var uniqueItems = [], + get = $parse(property); + + return (isUndefined(property)) + //if it's kind of primitive array + ? collection.filter(function (elm, pos, self) { + return self.indexOf(elm) === pos; + }) + //else compare with equals + : collection.filter(function (elm) { + var prop = get(elm); + if(some(uniqueItems, prop)) { + return false; + } + uniqueItems.push(prop); + return true; + }); + + //checked if the unique identifier is already exist + function some(array, member) { + if(isUndefined(member)) { + return false; + } + return array.some(function(el) { + return equals(el, member); + }); + } + } +} + +/** + * @ngdoc filter + * @name where + * @kind function + * + * @description + * of each element in a collection to the given properties object, + * returning an array of all elements that have equivalent property values. + * + */ +angular.module('a8m.where', []) + .filter('where', function() { + return function (collection, object) { + if(isUndefined(object)) return collection; + collection = isObject(collection) + ? toArray(collection) + : collection; + + return collection.filter(function (elm) { + return objectContains(object, elm); + }); + } + }); + +/** + * @ngdoc filter + * @name xor + * @kind function + * + * @description + * Exclusive or filter by expression + */ + +angular.module('a8m.xor', []) + + .filter('xor', ['$parse', function($parse) { + return function (col1, col2, expression) { + + expression = expression || false; + + col1 = isObject(col1) ? toArray(col1) : col1; + col2 = isObject(col2) ? toArray(col2) : col2; + + if(!isArray(col1) || !isArray(col2)) return col1; + + return col1.concat(col2) + .filter(function(elm) { + return !(some(elm, col1) && some(elm, col2)); + }); + + function some(el, col) { + var getter = $parse(expression); + return col.some(function(dElm) { + return expression + ? equals(getter(dElm), getter(el)) + : equals(dElm, el); + }); + } + } + }]); + +/** + * @ngdoc filter + * @name abs + * @kind function + * + * @description + * Will return the absolute value of a number + */ +angular.module('a8m.math.abs', []) + .filter('abs', function () { + return function (input) { + return Math.abs(input); + } + }); + +/** + * @ngdoc filter + * @name formatBytes + * @kind function + * + * @description + * Convert bytes into appropriate display + * 1024 bytes => 1 KB + */ +angular.module('a8m.math.byteFmt', ['a8m.math']) + .filter('byteFmt', ['$math', function ($math) { + return function (bytes, decimal) { + + if(isNumber(decimal) && isFinite(decimal) && decimal % 1 === 0 && decimal >= 0 && + isNumber(bytes) && isFinite(bytes)) { + if(bytes < 1024) { // within 1 KB so B + return convertToDecimal(bytes, decimal, $math) + ' B'; + } else if(bytes < 1048576) { // within 1 MB so KB + return convertToDecimal((bytes / 1024), decimal, $math) + ' KB'; + } else if(bytes < 1073741824) { // within 1 GB so MB + return convertToDecimal((bytes / 1048576), decimal, $math) + ' MB'; + } else if(bytes < 1099511627776 ) { // 1 TB so GB + return convertToDecimal((bytes / 1073741824), decimal, $math) + ' GB'; + } else if(bytes < 1125899906842624) { // 1 PB so TB + return convertToDecimal((bytes / 1099511627776), decimal, $math) + ' TB'; + } else if(bytes < 1152921504606846976) { // 1 EB so ZB + return convertToDecimal((bytes / 1125899906842624), decimal, $math) + ' PB'; + } else if(bytes < 1180591620717411303424) { // 1 ZB so EB + return convertToDecimal((bytes / 1152921504606846976), decimal, $math) + ' EB'; + } else if(bytes < 1208925819614629174706176) { // 1 YB so ZB + return convertToDecimal((bytes / 1180591620717411303424), decimal, $math) + ' ZB'; + } else { // 1 YB or more + return convertToDecimal((bytes / 1208925819614629174706176), decimal, $math) + ' YB'; + } + + } + return "NaN"; + } + }]); +/** + * @ngdoc filter + * @name degrees + * @kind function + * + * @description + * Convert angle from radians to degrees + */ +angular.module('a8m.math.degrees', ['a8m.math']) + .filter('degrees', ['$math', function ($math) { + return function (radians, decimal) { + // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" + // if degrees is not a real number, we cannot do also. quit with error "NaN" + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(radians) && isFinite(radians)) { + var degrees = (radians * 180) / $math.PI; + return $math.round(degrees * $math.pow(10,decimal)) / ($math.pow(10,decimal)); + } else { + return "NaN"; + } + } + }]); + + + +/** + * @ngdoc filter + * @name formatBytes + * @kind function + * + * @description + * Convert bytes into appropriate display + * 1024 kilobytes => 1 MB + */ +angular.module('a8m.math.kbFmt', ['a8m.math']) + .filter('kbFmt', ['$math', function ($math) { + return function (bytes, decimal) { + + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(bytes) && isFinite(bytes)) { + if(bytes < 1024) { // within 1 MB so KB + return convertToDecimal(bytes, decimal, $math) + ' KB'; + } else if(bytes < 1048576) { // within 1 GB so MB + return convertToDecimal((bytes / 1024), decimal, $math) + ' MB'; + } else if(bytes < 1073741824) { // within 1 TB so GB + return convertToDecimal((bytes / 1048576), decimal, $math) + ' GB'; + } else if(bytes < 1099511627776 ) { // 1 PB so TB + return convertToDecimal((bytes / 1073741824), decimal, $math) + ' TB'; + } else if(bytes < 1125899906842624) { // 1 EB so ZB + return convertToDecimal((bytes / 1099511627776), decimal, $math) + ' PB'; + } else if(bytes < 1152921504606846976) { // 1 ZB so EB + return convertToDecimal((bytes / 1125899906842624), decimal, $math) + ' EB'; + } else if(bytes < 1180591620717411303424) { // 1 YB so ZB + return convertToDecimal((bytes / 1152921504606846976), decimal, $math) + ' ZB'; + } else { // 1 YB or more + return convertToDecimal((bytes / 1180591620717411303424), decimal, $math) + ' YB'; + } + } + return "NaN"; + } + }]); +/** + * @ngdoc module + * @name math + * @description + * reference to global Math object + */ +angular.module('a8m.math', []) + .factory('$math', ['$window', function ($window) { + return $window.Math; + }]); + +/** + * @ngdoc filter + * @name max + * @kind function + * + * @description + * Math.max will get an array and return the max value. if an expression + * is provided, will return max value by expression. + */ +angular.module('a8m.math.max', ['a8m.math']) + .filter('max', ['$math', '$parse', function ($math, $parse) { + return function (input, expression) { + + if(!isArray(input)) { + return input; + } + return isUndefined(expression) + ? $math.max.apply($math, input) + : input[indexByMax(input, expression)]; + }; + + /** + * @private + * @param array + * @param exp + * @returns {number|*|Number} + */ + function indexByMax(array, exp) { + var mappedArray = array.map(function(elm){ + return $parse(exp)(elm); + }); + return mappedArray.indexOf($math.max.apply($math, mappedArray)); + } + }]); +/** + * @ngdoc filter + * @name min + * @kind function + * + * @description + * Math.min will get an array and return the min value. if an expression + * is provided, will return min value by expression. + */ +angular.module('a8m.math.min', ['a8m.math']) + .filter('min', ['$math', '$parse', function ($math, $parse) { + return function (input, expression) { + + if(!isArray(input)) { + return input; + } + return isUndefined(expression) + ? $math.min.apply($math, input) + : input[indexByMin(input, expression)]; + }; + + /** + * @private + * @param array + * @param exp + * @returns {number|*|Number} + */ + function indexByMin(array, exp) { + var mappedArray = array.map(function(elm){ + return $parse(exp)(elm); + }); + return mappedArray.indexOf($math.min.apply($math, mappedArray)); + } + }]); +/** + * @ngdoc filter + * @name Percent + * @kind function + * + * @description + * percentage between two numbers + */ +angular.module('a8m.math.percent', ['a8m.math']) + .filter('percent', ['$math', '$window', function ($math, $window) { + return function (input, divided, round) { + + var divider = isString(input) ? $window.Number(input) : input; + divided = divided || 100; + round = round || false; + + if (!isNumber(divider) || $window.isNaN(divider)) return input; + + return round + ? $math.round((divider / divided) * 100) + : (divider / divided) * 100; + } + }]); + +/** + * @ngdoc filter + * @name toRadians + * @kind function + * + * @description + * Convert angle from degrees to radians + */ +angular.module('a8m.math.radians', ['a8m.math']) + .filter('radians', ['$math', function ($math) { + return function (degrees, decimal) { + // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" + // if degrees is not a real number, we cannot do also. quit with error "NaN" + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(degrees) && isFinite(degrees)) { + var radians = (degrees * 3.14159265359) / 180; + return $math.round(radians * $math.pow(10,decimal)) / ($math.pow(10,decimal)); + } + return "NaN"; + } + }]); + + + +/** + * @ngdoc filter + * @name Radix + * @kind function + * + * @description + * converting decimal numbers to different bases(radix) + */ +angular.module('a8m.math.radix', []) + .filter('radix', function () { + return function (input, radix) { + var RANGE = /^[2-9]$|^[1-2]\d$|^3[0-6]$/; + + if(!isNumber(input) || !RANGE.test(radix)) { + return input; + } + + return input.toString(radix).toUpperCase(); + } + }); + +/** + * @ngdoc filter + * @name formatBytes + * @kind function + * + * @description + * Convert number into abbreviations. + * i.e: K for one thousand, M for Million, B for billion + * e.g: number of users:235,221, decimal:1 => 235.2 K + */ +angular.module('a8m.math.shortFmt', ['a8m.math']) + .filter('shortFmt', ['$math', function ($math) { + return function (number, decimal) { + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(number) && isFinite(number)){ + if(number < 1e3) { + return number; + } else if(number < 1e6) { + return convertToDecimal((number / 1e3), decimal, $math) + ' K'; + } else if(number < 1e9){ + return convertToDecimal((number / 1e6), decimal, $math) + ' M'; + } else { + return convertToDecimal((number / 1e9), decimal, $math) + ' B'; + } + + } + return "NaN"; + } + }]); +/** + * @ngdoc filter + * @name sum + * @kind function + * + * @description + * Sum up all values within an array + */ +angular.module('a8m.math.sum', []) + .filter('sum', function () { + return function (input, initial) { + return !isArray(input) + ? input + : input.reduce(function(prev, curr) { + return prev + curr; + }, initial || 0); + } + }); + +/** + * @ngdoc filter + * @name endsWith + * @kind function + * + * @description + * checks whether string ends with the ends parameter. + */ +angular.module('a8m.ends-with', []) + + .filter('endsWith', function () { + return function (input, ends, csensitive) { + + var sensitive = csensitive || false, + position; + + if(!isString(input) || isUndefined(ends)) { + return input; + } + + input = (sensitive) ? input : input.toLowerCase(); + position = input.length - ends.length; + + return input.indexOf((sensitive) ? ends : ends.toLowerCase(), position) !== -1; + } + }); + /** * @ngdoc filter * @name latinize @@ -1771,27 +1791,27 @@ angular.module('a8m.latinize', []) : input; } }]); - -/** - * @ngdoc filter - * @name ltrim - * @kind function - * - * @description - * Left trim. Similar to trimFilter, but only for left side. - */ -angular.module('a8m.ltrim', []) - .filter('ltrim', function () { - return function(input, chars) { - - var trim = chars || '\\s'; - - return isString(input) - ? input.replace(new RegExp('^' + trim + '+'), '') - : input; - } - }); - + +/** + * @ngdoc filter + * @name ltrim + * @kind function + * + * @description + * Left trim. Similar to trimFilter, but only for left side. + */ +angular.module('a8m.ltrim', []) + .filter('ltrim', function () { + return function(input, chars) { + + var trim = chars || '\\s'; + + return isString(input) + ? input.replace(new RegExp('^' + trim + '+'), '') + : input; + } + }); + /** * @ngdoc filter * @name match @@ -1811,145 +1831,145 @@ angular.module('a8m.match', []) : null; } }); - -/** - * @ngdoc filter - * @name repeat - * @kind function - * - * @description - * Repeats a string n times - */ -angular.module('a8m.repeat', []) - .filter('repeat',[ function () { - return function (input, n, separator) { - - var times = ~~n; - - if(!isString(input)) { - return input; - } - - return !times - ? input - : strRepeat(input, --n, separator || ''); - } - }]); - -/** - * Repeats a string n times with given separator - * @param str string to repeat - * @param n number of times - * @param sep separator - * @returns {*} - */ -function strRepeat(str, n, sep) { - if(!n) { - return str; - } - return str + sep + strRepeat(str, --n, sep); -} -/** -* @ngdoc filter -* @name rtrim -* @kind function -* -* @description -* Right trim. Similar to trimFilter, but only for right side. -*/ -angular.module('a8m.rtrim', []) - .filter('rtrim', function () { - return function(input, chars) { - - var trim = chars || '\\s'; - - return isString(input) - ? input.replace(new RegExp(trim + '+$'), '') - : input; - } - }); - -/** - * @ngdoc filter - * @name slugify - * @kind function - * - * @description - * remove spaces from string, replace with "-" or given argument - */ -angular.module('a8m.slugify', []) - .filter('slugify',[ function () { - return function (input, sub) { - - var replace = (isUndefined(sub)) ? '-' : sub; - - return isString(input) - ? input.toLowerCase().replace(/\s+/g, replace) - : input; - } - }]); - -/** - * @ngdoc filter - * @name startWith - * @kind function - * - * @description - * checks whether string starts with the starts parameter. - */ -angular.module('a8m.starts-with', []) - .filter('startsWith', function () { - return function (input, start, csensitive) { - - var sensitive = csensitive || false; - - if(!isString(input) || isUndefined(start)) { - return input; - } - - input = (sensitive) ? input : input.toLowerCase(); - - return !input.indexOf((sensitive) ? start : start.toLowerCase()); - } - }); - -/** - * @ngdoc filter - * @name stringular - * @kind function - * - * @description - * get string with {n} and replace match with enumeration values - */ -angular.module('a8m.stringular', []) - .filter('stringular', function () { - return function(input) { - - var args = Array.prototype.slice.call(arguments, 1); - - return input.replace(/{(\d+)}/g, function (match, number) { - return isUndefined(args[number]) ? match : args[number]; - }); - } - }); - -/** - * @ngdoc filter - * @name stripTags - * @kind function - * - * @description - * strip html tags from string - */ -angular.module('a8m.strip-tags', []) - .filter('stripTags', function () { - return function(input) { - return isString(input) - ? input.replace(/<\S[^><]*>/g, '') - : input; - } - }); - + +/** + * @ngdoc filter + * @name repeat + * @kind function + * + * @description + * Repeats a string n times + */ +angular.module('a8m.repeat', []) + .filter('repeat',[ function () { + return function (input, n, separator) { + + var times = ~~n; + + if(!isString(input)) { + return input; + } + + return !times + ? input + : strRepeat(input, --n, separator || ''); + } + }]); + +/** + * Repeats a string n times with given separator + * @param str string to repeat + * @param n number of times + * @param sep separator + * @returns {*} + */ +function strRepeat(str, n, sep) { + if(!n) { + return str; + } + return str + sep + strRepeat(str, --n, sep); +} +/** +* @ngdoc filter +* @name rtrim +* @kind function +* +* @description +* Right trim. Similar to trimFilter, but only for right side. +*/ +angular.module('a8m.rtrim', []) + .filter('rtrim', function () { + return function(input, chars) { + + var trim = chars || '\\s'; + + return isString(input) + ? input.replace(new RegExp(trim + '+$'), '') + : input; + } + }); + +/** + * @ngdoc filter + * @name slugify + * @kind function + * + * @description + * remove spaces from string, replace with "-" or given argument + */ +angular.module('a8m.slugify', []) + .filter('slugify',[ function () { + return function (input, sub) { + + var replace = (isUndefined(sub)) ? '-' : sub; + + return isString(input) + ? input.toLowerCase().replace(/\s+/g, replace) + : input; + } + }]); + +/** + * @ngdoc filter + * @name startWith + * @kind function + * + * @description + * checks whether string starts with the starts parameter. + */ +angular.module('a8m.starts-with', []) + .filter('startsWith', function () { + return function (input, start, csensitive) { + + var sensitive = csensitive || false; + + if(!isString(input) || isUndefined(start)) { + return input; + } + + input = (sensitive) ? input : input.toLowerCase(); + + return !input.indexOf((sensitive) ? start : start.toLowerCase()); + } + }); + +/** + * @ngdoc filter + * @name stringular + * @kind function + * + * @description + * get string with {n} and replace match with enumeration values + */ +angular.module('a8m.stringular', []) + .filter('stringular', function () { + return function(input) { + + var args = Array.prototype.slice.call(arguments, 1); + + return input.replace(/{(\d+)}/g, function (match, number) { + return isUndefined(args[number]) ? match : args[number]; + }); + } + }); + +/** + * @ngdoc filter + * @name stripTags + * @kind function + * + * @description + * strip html tags from string + */ +angular.module('a8m.strip-tags', []) + .filter('stripTags', function () { + return function(input) { + return isString(input) + ? input.replace(/<\S[^><]*>/g, '') + : input; + } + }); + /** * @ngdoc filter * @name test @@ -1969,349 +1989,349 @@ angular.module('a8m.test', []) : input; } }); - -/** - * @ngdoc filter - * @name trim - * @kind function - * - * @description - * Strip whitespace (or other characters) from the beginning and end of a string - */ -angular.module('a8m.trim', []) - .filter('trim', function () { - return function(input, chars) { - - var trim = chars || '\\s'; - - return isString(input) - ? input.replace(new RegExp('^' + trim + '+|' + trim + '+$', 'g'), '') - : input; - } - }); - -/** - * @ngdoc filter - * @name truncate - * @kind function - * - * @description - * truncates a string given a specified length, providing a custom string to denote an omission. - */ -angular.module('a8m.truncate', []) - .filter('truncate', function () { - return function(input, length, suffix, preserve) { - - length = isUndefined(length) ? input.length : length; - preserve = preserve || false; - suffix = suffix || ''; - - if(!isString(input) || (input.length <= length)) return input; - - return input.substring(0, (preserve) - ? ((input.indexOf(' ', length) === -1) ? input.length : input.indexOf(' ', length)) - : length) + suffix; - }; - }); - -/** - * @ngdoc filter - * @name ucfirst - * @kind function - * - * @description - * ucfirst - */ -angular.module('a8m.ucfirst', []) - .filter('ucfirst', [function() { - return function(input) { - return isString(input) - ? input - .split(' ') - .map(function (ch) { - return ch.charAt(0).toUpperCase() + ch.substring(1); - }) - .join(' ') - : input; - } - }]); - -/** - * @ngdoc filter - * @name uriComponentEncode - * @kind function - * - * @description - * get string as parameter and return encoded string - */ -angular.module('a8m.uri-component-encode', []) - .filter('uriComponentEncode',['$window', function ($window) { - return function (input) { - return isString(input) - ? $window.encodeURIComponent(input) - : input; - } - }]); - -/** - * @ngdoc filter - * @name uriEncode - * @kind function - * - * @description - * get string as parameter and return encoded string - */ -angular.module('a8m.uri-encode', []) - .filter('uriEncode',['$window', function ($window) { - return function (input) { - return isString(input) - ? $window.encodeURI(input) - : input; - } - }]); - -/** - * @ngdoc filter - * @name wrap - * @kind function - * - * @description - * Wrap a string with another string - */ -angular.module('a8m.wrap', []) - .filter('wrap', function () { - return function(input, wrap, ends) { - return isString(input) && isDefined(wrap) - ? [wrap, input, ends || wrap].join('') - : input; - } - }); - -/** - * @ngdoc provider - * @name filterWatcher - * @kind function - * - * @description - * store specific filters result in $$cache, based on scope life time(avoid memory leak). - * on scope.$destroy remove it's cache from $$cache container - */ - -angular.module('a8m.filter-watcher', []) - .provider('filterWatcher', function() { - - this.$get = ['$window', '$rootScope', function($window, $rootScope) { - - /** - * Cache storing - * @type {Object} - */ - var $$cache = {}; - - /** - * Scope listeners container - * scope.$destroy => remove all cache keys - * bind to current scope. - * @type {Object} - */ - var $$listeners = {}; - - /** - * $timeout without triggering the digest cycle - * @type {function} - */ - var $$timeout = $window.setTimeout; - - /** - * @description - * get `HashKey` string based on the given arguments. - * @param fName - * @param args - * @returns {string} - */ - function getHashKey(fName, args) { - function replacerFactory() { - var cache = []; - return function(key, val) { - if(isObject(val) && !isNull(val)) { - if (~cache.indexOf(val)) return '[Circular]'; - cache.push(val) - } - if($window == val) return '$WINDOW'; - if($window.document == val) return '$DOCUMENT'; - if(isScope(val)) return '$SCOPE'; - return val; - } - } - return [fName, JSON.stringify(args, replacerFactory())] - .join('#') - .replace(/"/g,''); - } - - /** - * @description - * fir on $scope.$destroy, - * remove cache based scope from `$$cache`, - * and remove itself from `$$listeners` - * @param event - */ - function removeCache(event) { - var id = event.targetScope.$id; - forEach($$listeners[id], function(key) { - delete $$cache[key]; - }); - delete $$listeners[id]; - } - - /** - * @description - * for angular version that greater than v.1.3.0 - * it clear cache when the digest cycle is end. - */ - function cleanStateless() { - $$timeout(function() { - if(!$rootScope.$$phase) - $$cache = {}; - }, 2000); - } - - /** - * @description - * Store hashKeys in $$listeners container - * on scope.$destroy, remove them all(bind an event). - * @param scope - * @param hashKey - * @returns {*} - */ - function addListener(scope, hashKey) { - var id = scope.$id; - if(isUndefined($$listeners[id])) { - scope.$on('$destroy', removeCache); - $$listeners[id] = []; - } - return $$listeners[id].push(hashKey); - } - - /** - * @description - * return the `cacheKey` or undefined. - * @param filterName - * @param args - * @returns {*} - */ - function $$isMemoized(filterName, args) { - var hashKey = getHashKey(filterName, args); - return $$cache[hashKey]; - } - - /** - * @description - * store `result` in `$$cache` container, based on the hashKey. - * add $destroy listener and return result - * @param filterName - * @param args - * @param scope - * @param result - * @returns {*} - */ - function $$memoize(filterName, args, scope, result) { - var hashKey = getHashKey(filterName, args); - //store result in `$$cache` container - $$cache[hashKey] = result; - // for angular versions that less than 1.3 - // add to `$destroy` listener, a cleaner callback - if(isScope(scope)) { - addListener(scope, hashKey); - } else { - cleanStateless(); - } - return result; - } - - return { - isMemoized: $$isMemoized, - memoize: $$memoize - } - }]; - }); - - -/** - * @ngdoc module - * @name angular.filters - * @description - * Bunch of useful filters for angularJS - */ - -angular.module('angular.filter', [ - - 'a8m.ucfirst', - 'a8m.uri-encode', - 'a8m.uri-component-encode', - 'a8m.slugify', - 'a8m.latinize', - 'a8m.strip-tags', - 'a8m.stringular', - 'a8m.truncate', - 'a8m.starts-with', - 'a8m.ends-with', - 'a8m.wrap', - 'a8m.trim', - 'a8m.ltrim', - 'a8m.rtrim', - 'a8m.repeat', - 'a8m.test', - 'a8m.match', - - 'a8m.to-array', - 'a8m.concat', - 'a8m.contains', - 'a8m.unique', - 'a8m.is-empty', - 'a8m.after', - 'a8m.after-where', - 'a8m.before', - 'a8m.before-where', - 'a8m.defaults', - 'a8m.where', - 'a8m.reverse', - 'a8m.remove', - 'a8m.remove-with', - 'a8m.group-by', - 'a8m.count-by', - 'a8m.chunk-by', - 'a8m.search-field', - 'a8m.fuzzy-by', - 'a8m.fuzzy', - 'a8m.omit', - 'a8m.pick', - 'a8m.every', - 'a8m.filter-by', - 'a8m.xor', - 'a8m.map', - 'a8m.first', - 'a8m.last', - 'a8m.flatten', - 'a8m.join', - 'a8m.range', - - 'a8m.math', - 'a8m.math.max', - 'a8m.math.min', - 'a8m.math.abs', - 'a8m.math.percent', - 'a8m.math.radix', - 'a8m.math.sum', - 'a8m.math.degrees', - 'a8m.math.radians', - 'a8m.math.byteFmt', - 'a8m.math.kbFmt', - 'a8m.math.shortFmt', - - 'a8m.angular', - 'a8m.conditions', - 'a8m.is-null', - - 'a8m.filter-watcher' -]); -})( window, window.angular ); + +/** + * @ngdoc filter + * @name trim + * @kind function + * + * @description + * Strip whitespace (or other characters) from the beginning and end of a string + */ +angular.module('a8m.trim', []) + .filter('trim', function () { + return function(input, chars) { + + var trim = chars || '\\s'; + + return isString(input) + ? input.replace(new RegExp('^' + trim + '+|' + trim + '+$', 'g'), '') + : input; + } + }); + +/** + * @ngdoc filter + * @name truncate + * @kind function + * + * @description + * truncates a string given a specified length, providing a custom string to denote an omission. + */ +angular.module('a8m.truncate', []) + .filter('truncate', function () { + return function(input, length, suffix, preserve) { + + length = isUndefined(length) ? input.length : length; + preserve = preserve || false; + suffix = suffix || ''; + + if(!isString(input) || (input.length <= length)) return input; + + return input.substring(0, (preserve) + ? ((input.indexOf(' ', length) === -1) ? input.length : input.indexOf(' ', length)) + : length) + suffix; + }; + }); + +/** + * @ngdoc filter + * @name ucfirst + * @kind function + * + * @description + * ucfirst + */ +angular.module('a8m.ucfirst', []) + .filter('ucfirst', [function() { + return function(input) { + return isString(input) + ? input + .split(' ') + .map(function (ch) { + return ch.charAt(0).toUpperCase() + ch.substring(1); + }) + .join(' ') + : input; + } + }]); + +/** + * @ngdoc filter + * @name uriComponentEncode + * @kind function + * + * @description + * get string as parameter and return encoded string + */ +angular.module('a8m.uri-component-encode', []) + .filter('uriComponentEncode',['$window', function ($window) { + return function (input) { + return isString(input) + ? $window.encodeURIComponent(input) + : input; + } + }]); + +/** + * @ngdoc filter + * @name uriEncode + * @kind function + * + * @description + * get string as parameter and return encoded string + */ +angular.module('a8m.uri-encode', []) + .filter('uriEncode',['$window', function ($window) { + return function (input) { + return isString(input) + ? $window.encodeURI(input) + : input; + } + }]); + +/** + * @ngdoc filter + * @name wrap + * @kind function + * + * @description + * Wrap a string with another string + */ +angular.module('a8m.wrap', []) + .filter('wrap', function () { + return function(input, wrap, ends) { + return isString(input) && isDefined(wrap) + ? [wrap, input, ends || wrap].join('') + : input; + } + }); + +/** + * @ngdoc provider + * @name filterWatcher + * @kind function + * + * @description + * store specific filters result in $$cache, based on scope life time(avoid memory leak). + * on scope.$destroy remove it's cache from $$cache container + */ + +angular.module('a8m.filter-watcher', []) + .provider('filterWatcher', function() { + + this.$get = ['$window', '$rootScope', function($window, $rootScope) { + + /** + * Cache storing + * @type {Object} + */ + var $$cache = {}; + + /** + * Scope listeners container + * scope.$destroy => remove all cache keys + * bind to current scope. + * @type {Object} + */ + var $$listeners = {}; + + /** + * $timeout without triggering the digest cycle + * @type {function} + */ + var $$timeout = $window.setTimeout; + + /** + * @description + * get `HashKey` string based on the given arguments. + * @param fName + * @param args + * @returns {string} + */ + function getHashKey(fName, args) { + function replacerFactory() { + var cache = []; + return function(key, val) { + if(isObject(val) && !isNull(val)) { + if (~cache.indexOf(val)) return '[Circular]'; + cache.push(val) + } + if($window == val) return '$WINDOW'; + if($window.document == val) return '$DOCUMENT'; + if(isScope(val)) return '$SCOPE'; + return val; + } + } + return [fName, JSON.stringify(args, replacerFactory())] + .join('#') + .replace(/"/g,''); + } + + /** + * @description + * fir on $scope.$destroy, + * remove cache based scope from `$$cache`, + * and remove itself from `$$listeners` + * @param event + */ + function removeCache(event) { + var id = event.targetScope.$id; + forEach($$listeners[id], function(key) { + delete $$cache[key]; + }); + delete $$listeners[id]; + } + + /** + * @description + * for angular version that greater than v.1.3.0 + * it clear cache when the digest cycle is end. + */ + function cleanStateless() { + $$timeout(function() { + if(!$rootScope.$$phase) + $$cache = {}; + }, 2000); + } + + /** + * @description + * Store hashKeys in $$listeners container + * on scope.$destroy, remove them all(bind an event). + * @param scope + * @param hashKey + * @returns {*} + */ + function addListener(scope, hashKey) { + var id = scope.$id; + if(isUndefined($$listeners[id])) { + scope.$on('$destroy', removeCache); + $$listeners[id] = []; + } + return $$listeners[id].push(hashKey); + } + + /** + * @description + * return the `cacheKey` or undefined. + * @param filterName + * @param args + * @returns {*} + */ + function $$isMemoized(filterName, args) { + var hashKey = getHashKey(filterName, args); + return $$cache[hashKey]; + } + + /** + * @description + * store `result` in `$$cache` container, based on the hashKey. + * add $destroy listener and return result + * @param filterName + * @param args + * @param scope + * @param result + * @returns {*} + */ + function $$memoize(filterName, args, scope, result) { + var hashKey = getHashKey(filterName, args); + //store result in `$$cache` container + $$cache[hashKey] = result; + // for angular versions that less than 1.3 + // add to `$destroy` listener, a cleaner callback + if(isScope(scope)) { + addListener(scope, hashKey); + } else { + cleanStateless(); + } + return result; + } + + return { + isMemoized: $$isMemoized, + memoize: $$memoize + } + }]; + }); + + +/** + * @ngdoc module + * @name angular.filters + * @description + * Bunch of useful filters for angularJS + */ + +angular.module('angular.filter', [ + + 'a8m.ucfirst', + 'a8m.uri-encode', + 'a8m.uri-component-encode', + 'a8m.slugify', + 'a8m.latinize', + 'a8m.strip-tags', + 'a8m.stringular', + 'a8m.truncate', + 'a8m.starts-with', + 'a8m.ends-with', + 'a8m.wrap', + 'a8m.trim', + 'a8m.ltrim', + 'a8m.rtrim', + 'a8m.repeat', + 'a8m.test', + 'a8m.match', + + 'a8m.to-array', + 'a8m.concat', + 'a8m.contains', + 'a8m.unique', + 'a8m.is-empty', + 'a8m.after', + 'a8m.after-where', + 'a8m.before', + 'a8m.before-where', + 'a8m.defaults', + 'a8m.where', + 'a8m.reverse', + 'a8m.remove', + 'a8m.remove-with', + 'a8m.group-by', + 'a8m.count-by', + 'a8m.chunk-by', + 'a8m.search-field', + 'a8m.fuzzy-by', + 'a8m.fuzzy', + 'a8m.omit', + 'a8m.pick', + 'a8m.every', + 'a8m.filter-by', + 'a8m.xor', + 'a8m.map', + 'a8m.first', + 'a8m.last', + 'a8m.flatten', + 'a8m.join', + 'a8m.range', + + 'a8m.math', + 'a8m.math.max', + 'a8m.math.min', + 'a8m.math.abs', + 'a8m.math.percent', + 'a8m.math.radix', + 'a8m.math.sum', + 'a8m.math.degrees', + 'a8m.math.radians', + 'a8m.math.byteFmt', + 'a8m.math.kbFmt', + 'a8m.math.shortFmt', + + 'a8m.angular', + 'a8m.conditions', + 'a8m.is-null', + + 'a8m.filter-watcher' +]); +})( window, window.angular ); \ No newline at end of file diff --git a/dist/angular-filter.min.js b/dist/angular-filter.min.js index 1ee3e12..8a31aaa 100644 --- a/dist/angular-filter.min.js +++ b/dist/angular-filter.min.js @@ -1,6 +1,6 @@ /** * Bunch of useful filters for angularJS(with no external dependencies!) - * @version v0.5.12 - 2016-12-02 * @link https://github.com/a8m/angular-filter + * @version v0.5.12 - 2016-12-03 * @link https://github.com/a8m/angular-filter * @author Ariel Mashraki * @license MIT License, http://www.opensource.org/licenses/MIT - */!function(a,b,c){"use strict";function d(a){return D(a)?a:Object.keys(a).map(function(b){return a[b]})}function e(a){return null===a}function f(a,b){var d=Object.keys(a);return d.map(function(d){return b[d]!==c&&b[d]==a[d]}).indexOf(!1)==-1}function g(a,b){function c(a,b,c){for(var d=0;b+d<=a.length;){if(a.charAt(b+d)==c)return d;d++}return-1}for(var d=0,e=0;e<=b.length;e++){var f=c(a,d,b.charAt(e));if(f==-1)return!1;d+=f+1}return!0}function h(a,b,c){var d=0;return a.filter(function(a){var e=x(c)?db}}function m(){return function(a,b){return a>=b}}function n(){return function(a,b){return a=a.length?a:D(a[b])?u(a.slice(0,b).concat(a[b],a.slice(b+1)),b):u(a,b+1)}function v(a){return function(b,c){function e(a,b){return!y(b)&&a.some(function(a){return H(a,b)})}if(b=C(b)?d(b):b,!D(b))return b;var f=[],g=a(c);return y(c)?b.filter(function(a,b,c){return c.indexOf(a)===b}):b.filter(function(a){var b=g(a);return!e(f,b)&&(f.push(b),!0)})}}function w(a,b,c){return b?a+c+w(a,--b,c):a}var x=b.isDefined,y=b.isUndefined,z=b.isFunction,A=b.isString,B=b.isNumber,C=b.isObject,D=b.isArray,E=b.forEach,F=b.extend,G=b.copy,H=b.equals;String.prototype.contains||(String.prototype.contains=function(){return String.prototype.indexOf.apply(this,arguments)!==-1}),b.module("a8m.angular",[]).filter("isUndefined",function(){return function(a){return b.isUndefined(a)}}).filter("isDefined",function(){return function(a){return b.isDefined(a)}}).filter("isFunction",function(){return function(a){return b.isFunction(a)}}).filter("isString",function(){return function(a){return b.isString(a)}}).filter("isNumber",function(){return function(a){return b.isNumber(a)}}).filter("isArray",function(){return function(a){return b.isArray(a)}}).filter("isObject",function(){return function(a){return b.isObject(a)}}).filter("isEqual",function(){return function(a,c){return b.equals(a,c)}}),b.module("a8m.conditions",[]).filter({isGreaterThan:l,">":l,isGreaterThanOrEqualTo:m,">=":m,isLessThan:n,"<":n,isLessThanOrEqualTo:o,"<=":o,isEqualTo:p,"==":p,isNotEqualTo:q,"!=":q,isIdenticalTo:r,"===":r,isNotIdenticalTo:s,"!==":s}),b.module("a8m.is-null",[]).filter("isNull",function(){return function(a){return e(a)}}),b.module("a8m.after-where",[]).filter("afterWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(c===-1?0:c)}}),b.module("a8m.after",[]).filter("after",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(b):a}}),b.module("a8m.before-where",[]).filter("beforeWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(0,c===-1?a.length:++c)}}),b.module("a8m.before",[]).filter("before",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(0,b?--b:b):a}}),b.module("a8m.chunk-by",["a8m.filter-watcher"]).filter("chunkBy",["filterWatcher",function(a){return function(b,c,d){function e(a,b){for(var c=[];a--;)c[a]=b;return c}function f(a,b,c){return D(a)?a.map(function(a,d,f){return d*=b,a=f.slice(d,d+b),!y(c)&&a.length=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" B":b<1048576?i(b/1024,c,a)+" KB":b<1073741824?i(b/1048576,c,a)+" MB":i(b/1073741824,c,a)+" GB":"NaN"}}]),b.module("a8m.math.degrees",["a8m.math"]).filter("degrees",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=180*b/a.PI;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.kbFmt",["a8m.math"]).filter("kbFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" KB":b<1048576?i(b/1024,c,a)+" MB":i(b/1048576,c,a)+" GB":"NaN"}}]),b.module("a8m.math",[]).factory("$math",["$window",function(a){return a.Math}]),b.module("a8m.math.max",["a8m.math"]).filter("max",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.max.apply(a,e))}return function(b,d){return D(b)?y(d)?a.max.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.min",["a8m.math"]).filter("min",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.min.apply(a,e))}return function(b,d){return D(b)?y(d)?a.min.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.percent",["a8m.math"]).filter("percent",["$math","$window",function(a,b){return function(c,d,e){var f=A(c)?b.Number(c):c;return d=d||100,e=e||!1,!B(f)||b.isNaN(f)?c:e?a.round(f/d*100):f/d*100}}]),b.module("a8m.math.radians",["a8m.math"]).filter("radians",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=3.14159265359*b/180;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.radix",[]).filter("radix",function(){return function(a,b){var c=/^[2-9]$|^[1-2]\d$|^3[0-6]$/;return B(a)&&c.test(b)?a.toString(b).toUpperCase():a}}),b.module("a8m.math.shortFmt",["a8m.math"]).filter("shortFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1e3?b:b<1e6?i(b/1e3,c,a)+" K":b<1e9?i(b/1e6,c,a)+" M":i(b/1e9,c,a)+" B":"NaN"}}]),b.module("a8m.math.sum",[]).filter("sum",function(){return function(a,b){return D(a)?a.reduce(function(a,b){return a+b},b||0):a}}),b.module("a8m.ends-with",[]).filter("endsWith",function(){return function(a,b,c){var d,e=c||!1;return!A(a)||y(b)?a:(a=e?a:a.toLowerCase(),d=a.length-b.length,a.indexOf(e?b:b.toLowerCase(),d)!==-1)}}),b.module("a8m.latinize",[]).filter("latinize",[function(){function a(a){return a.replace(/[^\u0000-\u007E]/g,function(a){return c[a]||a})}for(var b=[{base:"A",letters:"AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷBḂḄḆɃƂƁ"},{base:"C",letters:"CⒸCĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹDḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"DZDŽ"},{base:"Dz",letters:"DzDž"},{base:"E",letters:"EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻFḞƑꝻ"},{base:"G",letters:"GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿJĴɈ"},{base:"K",letters:"KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"LJ"},{base:"Lj",letters:"Lj"},{base:"M",letters:"MⓂMḾṀṂⱮƜ"},{base:"N",letters:"NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"NJ"},{base:"Nj",letters:"Nj"},{base:"O",letters:"OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"OE",letters:"ŒŒ"},{base:"oe",letters:"œœ"},{base:"P",letters:"PⓅPṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆQꝖꝘɊ"},{base:"R",letters:"RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋVṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌWẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍXẊẌ"},{base:"Y",letters:"YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑbḃḅḇƀƃɓ"},{base:"c",letters:"cⓒcćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓdḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"dzdž"},{base:"e",letters:"eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕfḟƒꝼ"},{base:"g",letters:"gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙjĵǰɉ"},{base:"k",letters:"kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"lj"},{base:"m",letters:"mⓜmḿṁṃɱɯ"},{base:"n",letters:"nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ"},{base:"nj",letters:"nj"},{base:"o",letters:"oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟpṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠqɋꝗꝙ"},{base:"r",letters:"rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢsßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥvṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦwẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧxẋẍ"},{base:"y",letters:"yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩzźẑżžẓẕƶȥɀⱬꝣ"}],c={},d=0;d<]*>/g,""):a}}),b.module("a8m.test",[]).filter("test",function(){return function(a,b,c){var d=new RegExp(b,c);return A(a)?d.test(a):a}}),b.module("a8m.trim",[]).filter("trim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp("^"+c+"+|"+c+"+$","g"),""):a}}),b.module("a8m.truncate",[]).filter("truncate",function(){return function(a,b,c,d){return b=y(b)?a.length:b,d=d||!1,c=c||"",!A(a)||a.length<=b?a:a.substring(0,d?a.indexOf(" ",b)===-1?a.length:a.indexOf(" ",b):b)+c}}),b.module("a8m.ucfirst",[]).filter("ucfirst",[function(){return function(a){return A(a)?a.split(" ").map(function(a){return a.charAt(0).toUpperCase()+a.substring(1)}).join(" "):a}}]),b.module("a8m.uri-component-encode",[]).filter("uriComponentEncode",["$window",function(a){return function(b){return A(b)?a.encodeURIComponent(b):b}}]),b.module("a8m.uri-encode",[]).filter("uriEncode",["$window",function(a){return function(b){return A(b)?a.encodeURI(b):b}}]),b.module("a8m.wrap",[]).filter("wrap",function(){return function(a,b,c){return A(a)&&x(b)?[b,a,c||b].join(""):a}}),b.module("a8m.filter-watcher",[]).provider("filterWatcher",function(){this.$get=["$window","$rootScope",function(a,b){function c(b,c){function d(){var b=[];return function(c,d){if(C(d)&&!e(d)){if(~b.indexOf(d))return"[Circular]";b.push(d)}return a==d?"$WINDOW":a.document==d?"$DOCUMENT":k(d)?"$SCOPE":d}}return[b,JSON.stringify(c,d())].join("#").replace(/"/g,"")}function d(a){var b=a.targetScope.$id;E(l[b],function(a){delete j[a]}),delete l[b]}function f(){m(function(){b.$$phase||(j={})},2e3)}function g(a,b){var c=a.$id;return y(l[c])&&(a.$on("$destroy",d),l[c]=[]),l[c].push(b)}function h(a,b){var d=c(a,b);return j[d]}function i(a,b,d,e){var h=c(a,b);return j[h]=e,k(d)?g(d,h):f(),e}var j={},l={},m=a.setTimeout;return{isMemoized:h,memoize:i}}]}),b.module("angular.filter",["a8m.ucfirst","a8m.uri-encode","a8m.uri-component-encode","a8m.slugify","a8m.latinize","a8m.strip-tags","a8m.stringular","a8m.truncate","a8m.starts-with","a8m.ends-with","a8m.wrap","a8m.trim","a8m.ltrim","a8m.rtrim","a8m.repeat","a8m.test","a8m.match","a8m.to-array","a8m.concat","a8m.contains","a8m.unique","a8m.is-empty","a8m.after","a8m.after-where","a8m.before","a8m.before-where","a8m.defaults","a8m.where","a8m.reverse","a8m.remove","a8m.remove-with","a8m.group-by","a8m.count-by","a8m.chunk-by","a8m.search-field","a8m.fuzzy-by","a8m.fuzzy","a8m.omit","a8m.pick","a8m.every","a8m.filter-by","a8m.xor","a8m.map","a8m.first","a8m.last","a8m.flatten","a8m.join","a8m.range","a8m.math","a8m.math.max","a8m.math.min","a8m.math.abs","a8m.math.percent","a8m.math.radix","a8m.math.sum","a8m.math.degrees","a8m.math.radians","a8m.math.byteFmt","a8m.math.kbFmt","a8m.math.shortFmt","a8m.math.shortFmt","a8m.angular","a8m.conditions","a8m.is-null","a8m.filter-watcher"])}(window,window.angular); \ No newline at end of file + */!function(a,b,c){"use strict";function d(a){return D(a)?a:Object.keys(a).map(function(b){return a[b]})}function e(a){return null===a}function f(a,b){var d=Object.keys(a);return d.map(function(d){return b[d]!==c&&b[d]==a[d]}).indexOf(!1)==-1}function g(a,b){function c(a,b,c){for(var d=0;b+d<=a.length;){if(a.charAt(b+d)==c)return d;d++}return-1}for(var d=0,e=0;e<=b.length;e++){var f=c(a,d,b.charAt(e));if(f==-1)return!1;d+=f+1}return!0}function h(a,b,c){var d=0;return a.filter(function(a){var e=x(c)?db}}function m(){return function(a,b){return a>=b}}function n(){return function(a,b){return a=a.length?a:D(a[b])?u(a.slice(0,b).concat(a[b],a.slice(b+1)),b):u(a,b+1)}function v(a){return function(b,c){function e(a,b){return!y(b)&&a.some(function(a){return H(a,b)})}if(b=C(b)?d(b):b,!D(b))return b;var f=[],g=a(c);return y(c)?b.filter(function(a,b,c){return c.indexOf(a)===b}):b.filter(function(a){var b=g(a);return!e(f,b)&&(f.push(b),!0)})}}function w(a,b,c){return b?a+c+w(a,--b,c):a}var x=b.isDefined,y=b.isUndefined,z=b.isFunction,A=b.isString,B=b.isNumber,C=b.isObject,D=b.isArray,E=b.forEach,F=b.extend,G=b.copy,H=b.equals;String.prototype.contains||(String.prototype.contains=function(){return String.prototype.indexOf.apply(this,arguments)!==-1}),b.module("a8m.angular",[]).filter("isUndefined",function(){return function(a){return b.isUndefined(a)}}).filter("isDefined",function(){return function(a){return b.isDefined(a)}}).filter("isFunction",function(){return function(a){return b.isFunction(a)}}).filter("isString",function(){return function(a){return b.isString(a)}}).filter("isNumber",function(){return function(a){return b.isNumber(a)}}).filter("isArray",function(){return function(a){return b.isArray(a)}}).filter("isObject",function(){return function(a){return b.isObject(a)}}).filter("isEqual",function(){return function(a,c){return b.equals(a,c)}}),b.module("a8m.conditions",[]).filter({isGreaterThan:l,">":l,isGreaterThanOrEqualTo:m,">=":m,isLessThan:n,"<":n,isLessThanOrEqualTo:o,"<=":o,isEqualTo:p,"==":p,isNotEqualTo:q,"!=":q,isIdenticalTo:r,"===":r,isNotIdenticalTo:s,"!==":s}),b.module("a8m.is-null",[]).filter("isNull",function(){return function(a){return e(a)}}),b.module("a8m.after-where",[]).filter("afterWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(c===-1?0:c)}}),b.module("a8m.after",[]).filter("after",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(b):a}}),b.module("a8m.before-where",[]).filter("beforeWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(0,c===-1?a.length:++c)}}),b.module("a8m.before",[]).filter("before",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(0,b?--b:b):a}}),b.module("a8m.chunk-by",["a8m.filter-watcher"]).filter("chunkBy",["filterWatcher",function(a){return function(b,c,d){function e(a,b){for(var c=[];a--;)c[a]=b;return c}function f(a,b,c){return D(a)?a.map(function(a,d,f){return d*=b,a=f.slice(d,d+b),!y(c)&&a.length=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" B":b<1048576?i(b/1024,c,a)+" KB":b<1073741824?i(b/1048576,c,a)+" MB":b<1099511627776?i(b/1073741824,c,a)+" GB":b<0x4000000000000?i(b/1099511627776,c,a)+" TB":b<0x1000000000000000?i(b/0x4000000000000,c,a)+" PB":b<0x400000000000000000?i(b/0x1000000000000000,c,a)+" EB":b<1.2089258196146292e24?i(b/0x400000000000000000,c,a)+" ZB":i(b/1.2089258196146292e24,c,a)+" YB":"NaN"}}]),b.module("a8m.math.degrees",["a8m.math"]).filter("degrees",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=180*b/a.PI;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.kbFmt",["a8m.math"]).filter("kbFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" KB":b<1048576?i(b/1024,c,a)+" MB":b<1073741824?i(b/1048576,c,a)+" GB":b<1099511627776?i(b/1073741824,c,a)+" TB":b<0x4000000000000?i(b/1099511627776,c,a)+" PB":b<0x1000000000000000?i(b/0x4000000000000,c,a)+" EB":b<0x400000000000000000?i(b/0x1000000000000000,c,a)+" ZB":i(b/0x400000000000000000,c,a)+" YB":"NaN"}}]),b.module("a8m.math",[]).factory("$math",["$window",function(a){return a.Math}]),b.module("a8m.math.max",["a8m.math"]).filter("max",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.max.apply(a,e))}return function(b,d){return D(b)?y(d)?a.max.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.min",["a8m.math"]).filter("min",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.min.apply(a,e))}return function(b,d){return D(b)?y(d)?a.min.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.percent",["a8m.math"]).filter("percent",["$math","$window",function(a,b){return function(c,d,e){var f=A(c)?b.Number(c):c;return d=d||100,e=e||!1,!B(f)||b.isNaN(f)?c:e?a.round(f/d*100):f/d*100}}]),b.module("a8m.math.radians",["a8m.math"]).filter("radians",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=3.14159265359*b/180;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.radix",[]).filter("radix",function(){return function(a,b){var c=/^[2-9]$|^[1-2]\d$|^3[0-6]$/;return B(a)&&c.test(b)?a.toString(b).toUpperCase():a}}),b.module("a8m.math.shortFmt",["a8m.math"]).filter("shortFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1e3?b:b<1e6?i(b/1e3,c,a)+" K":b<1e9?i(b/1e6,c,a)+" M":i(b/1e9,c,a)+" B":"NaN"}}]),b.module("a8m.math.sum",[]).filter("sum",function(){return function(a,b){return D(a)?a.reduce(function(a,b){return a+b},b||0):a}}),b.module("a8m.ends-with",[]).filter("endsWith",function(){return function(a,b,c){var d,e=c||!1;return!A(a)||y(b)?a:(a=e?a:a.toLowerCase(),d=a.length-b.length,a.indexOf(e?b:b.toLowerCase(),d)!==-1)}}),b.module("a8m.latinize",[]).filter("latinize",[function(){function a(a){return a.replace(/[^\u0000-\u007E]/g,function(a){return c[a]||a})}for(var b=[{base:"A",letters:"AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷBḂḄḆɃƂƁ"},{base:"C",letters:"CⒸCĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹDḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"DZDŽ"},{base:"Dz",letters:"DzDž"},{base:"E",letters:"EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻFḞƑꝻ"},{base:"G",letters:"GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿJĴɈ"},{base:"K",letters:"KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"LJ"},{base:"Lj",letters:"Lj"},{base:"M",letters:"MⓂMḾṀṂⱮƜ"},{base:"N",letters:"NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"NJ"},{base:"Nj",letters:"Nj"},{base:"O",letters:"OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"OE",letters:"ŒŒ"},{base:"oe",letters:"œœ"},{base:"P",letters:"PⓅPṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆQꝖꝘɊ"},{base:"R",letters:"RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋVṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌWẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍXẊẌ"},{base:"Y",letters:"YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑbḃḅḇƀƃɓ"},{base:"c",letters:"cⓒcćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓdḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"dzdž"},{base:"e",letters:"eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕfḟƒꝼ"},{base:"g",letters:"gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙjĵǰɉ"},{base:"k",letters:"kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"lj"},{base:"m",letters:"mⓜmḿṁṃɱɯ"},{base:"n",letters:"nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ"},{base:"nj",letters:"nj"},{base:"o",letters:"oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟpṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠqɋꝗꝙ"},{base:"r",letters:"rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢsßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥvṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦwẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧxẋẍ"},{base:"y",letters:"yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩzźẑżžẓẕƶȥɀⱬꝣ"}],c={},d=0;d<]*>/g,""):a}}),b.module("a8m.test",[]).filter("test",function(){return function(a,b,c){var d=new RegExp(b,c);return A(a)?d.test(a):a}}),b.module("a8m.trim",[]).filter("trim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp("^"+c+"+|"+c+"+$","g"),""):a}}),b.module("a8m.truncate",[]).filter("truncate",function(){return function(a,b,c,d){return b=y(b)?a.length:b,d=d||!1,c=c||"",!A(a)||a.length<=b?a:a.substring(0,d?a.indexOf(" ",b)===-1?a.length:a.indexOf(" ",b):b)+c}}),b.module("a8m.ucfirst",[]).filter("ucfirst",[function(){return function(a){return A(a)?a.split(" ").map(function(a){return a.charAt(0).toUpperCase()+a.substring(1)}).join(" "):a}}]),b.module("a8m.uri-component-encode",[]).filter("uriComponentEncode",["$window",function(a){return function(b){return A(b)?a.encodeURIComponent(b):b}}]),b.module("a8m.uri-encode",[]).filter("uriEncode",["$window",function(a){return function(b){return A(b)?a.encodeURI(b):b}}]),b.module("a8m.wrap",[]).filter("wrap",function(){return function(a,b,c){return A(a)&&x(b)?[b,a,c||b].join(""):a}}),b.module("a8m.filter-watcher",[]).provider("filterWatcher",function(){this.$get=["$window","$rootScope",function(a,b){function c(b,c){function d(){var b=[];return function(c,d){if(C(d)&&!e(d)){if(~b.indexOf(d))return"[Circular]";b.push(d)}return a==d?"$WINDOW":a.document==d?"$DOCUMENT":k(d)?"$SCOPE":d}}return[b,JSON.stringify(c,d())].join("#").replace(/"/g,"")}function d(a){var b=a.targetScope.$id;E(l[b],function(a){delete j[a]}),delete l[b]}function f(){m(function(){b.$$phase||(j={})},2e3)}function g(a,b){var c=a.$id;return y(l[c])&&(a.$on("$destroy",d),l[c]=[]),l[c].push(b)}function h(a,b){var d=c(a,b);return j[d]}function i(a,b,d,e){var h=c(a,b);return j[h]=e,k(d)?g(d,h):f(),e}var j={},l={},m=a.setTimeout;return{isMemoized:h,memoize:i}}]}),b.module("angular.filter",["a8m.ucfirst","a8m.uri-encode","a8m.uri-component-encode","a8m.slugify","a8m.latinize","a8m.strip-tags","a8m.stringular","a8m.truncate","a8m.starts-with","a8m.ends-with","a8m.wrap","a8m.trim","a8m.ltrim","a8m.rtrim","a8m.repeat","a8m.test","a8m.match","a8m.to-array","a8m.concat","a8m.contains","a8m.unique","a8m.is-empty","a8m.after","a8m.after-where","a8m.before","a8m.before-where","a8m.defaults","a8m.where","a8m.reverse","a8m.remove","a8m.remove-with","a8m.group-by","a8m.count-by","a8m.chunk-by","a8m.search-field","a8m.fuzzy-by","a8m.fuzzy","a8m.omit","a8m.pick","a8m.every","a8m.filter-by","a8m.xor","a8m.map","a8m.first","a8m.last","a8m.flatten","a8m.join","a8m.range","a8m.math","a8m.math.max","a8m.math.min","a8m.math.abs","a8m.math.percent","a8m.math.radix","a8m.math.sum","a8m.math.degrees","a8m.math.radians","a8m.math.byteFmt","a8m.math.kbFmt","a8m.math.shortFmt","a8m.angular","a8m.conditions","a8m.is-null","a8m.filter-watcher"])}(window,window.angular); \ No newline at end of file diff --git a/dist/angular-filter.zip b/dist/angular-filter.zip index 21fd54b47efed0b73d714f6c4969bd5038cfa804..22bcb01782a8c2e4427d73ac7214f706dbc4ec9d 100644 GIT binary patch delta 18932 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 F{s(rpSgHU3