diff --git a/index.html b/index.html index 7fea7595..174ef82f 100644 --- a/index.html +++ b/index.html @@ -4,8 +4,9 @@ OxJS - A JavaScript Library for Web Applications - + + - \ No newline at end of file + diff --git a/source/Ox.compat.js b/source/Ox.compat.js new file mode 100644 index 00000000..7e809841 --- /dev/null +++ b/source/Ox.compat.js @@ -0,0 +1,18 @@ +const init = [], initLoad = []; +window.Ox = function(value) { + console.log("delay stuff until we are done") + init.push(value) +}; +window.Ox.load = function() { + initLoad.push(arguments) +}; + +(async () => { + const module = await import('./Ox/index.js'); + console.log("Ox was loaded", init); + init.forEach(value => Ox(value)) + delete init + initLoad.forEach(arguments => Ox.load.apply(null, arguments)) + delete initLoad +})(); + diff --git a/source/Ox/index.js b/source/Ox/index.js new file mode 100644 index 00000000..58e220be --- /dev/null +++ b/source/Ox/index.js @@ -0,0 +1,102 @@ +'use strict'; + +/*@ +Ox The `Ox` object + See `Ox.wrap` for details. + (value) -> wrapped value + value <*> Any value +@*/ + +import * as OxCore from './js/Core.js'; +import * as OxFunction from './js/Function.js'; +import * as OxPolyfill from './js/Polyfill.js'; +import * as OxArray from './js/Array.js'; +import * as OxString from './js/String.js'; +import * as OxCollection from './js/Collection.js'; +import * as OxMath from './js/Math.js'; + + +import * as OxAsync from './js/Async.js'; +import * as OxColor from './js/Color.js'; +import * as OxConstants from './js/Constants.js'; +import * as OxDate from './js/Date.js'; +import * as OxDOM from './js/DOM.js'; +import * as OxEncoding from './js/Encoding.js'; +import * as OxFormat from './js/Format.js'; +import * as OxGeo from './js/Geo.js'; +import * as OxHash from './js/Hash.js'; +import * as OxHTML from './js/HTML.js'; +import * as OxJavaScript from './js/JavaScript.js'; +import * as OxLocale from './js/Locale.js'; +import * as OxObject from './js/Object.js'; +import * as OxRegExp from './js/RegExp.js'; +import * as OxRequest from './js/Request.js'; +import * as OxType from './js/Type.js'; +import * as OxVideo from './js/Video.js'; + + +const Ox = function(value) { + return OxCore.wrap(value) +}; + +Object.assign(Ox, + OxCore, + OxFunction, + OxPolyfill, + OxArray, + OxString, + OxCollection, + OxMath, + OxAsync, + OxColor, + OxConstants, + OxDate, + OxDOM, + OxEncoding, + OxFormat, + OxGeo, + OxHash, + OxHTML, + OxJavaScript, + OxLocale, + OxObject, + OxRegExp, + OxRequest, + OxType, + OxVideo, +); + +export default Ox; +export { Ox }; + +// For backward compatibility with global usage +if (typeof window !== 'undefined') { + window.Ox = Ox; + Ox.loadPolyfill(window, Ox) + + /*@ + Ox.documentReady Calls a callback function once the DOM is ready + (callback) -> If true, the document was ready + callback Callback function + @*/ + Ox.documentReady = (function() { + var callbacks = []; + document.onreadystatechange = window.onload = function() { + if (document.readyState == 'complete') { + callbacks.forEach(function(callback) { + callback(); + }); + document.onreadystatechange = window.onload = null; + } + }; + return function(callback) { + if (document.readyState == 'complete') { + callback(); + return true; + } else { + callbacks.push(callback); + return false; + } + }; + }()); +} diff --git a/source/Ox/js/Array.js b/source/Ox/js/Array.js index 6beae511..380aa357 100644 --- a/source/Ox/js/Array.js +++ b/source/Ox/js/Array.js @@ -1,5 +1,19 @@ 'use strict'; +import * as OxCore from './Core.js'; +import * as OxBase from './Base.js'; +import * as OxFunction from './Function.js'; +import * as OxType from './Type.js'; + +const Ox = {}; + +Object.assign(Ox, + OxCore, + OxBase, + OxFunction, + OxType, +); + /*@ Ox.api Turns an array into a list API `Ox.api` takes an array and returns a function that allows you to run @@ -163,7 +177,7 @@ Ox.api Turns an array into a list API > Ox.test.apiResults[9].data {items: [{name: 'John Cale'}, {name: 'Brian Eno'}]} @*/ -Ox.api = function(items, options) { +export function api(items, options) { options = options || {}; @@ -442,7 +456,7 @@ Ox.compact Removes `null` or `undefined` values from an array > Ox.compact([null,,1,,2,,3]) [1, 2, 3] @*/ -Ox.compact = function(array) { +export function compact(array) { return array.filter(function(value) { return value != null; }); @@ -461,7 +475,7 @@ Ox.find Returns array elements that match a string > Ox.find(['Bar', 'Barfoo', 'Foo', 'Foobar'], 'foo', true) ['Foo', 'Foobar'] @*/ -Ox.find = function(array, string, leading) { +export function find(array, string, leading) { var matches = [[], []]; string = string.toLowerCase(); array.forEach(function(value) { @@ -480,7 +494,7 @@ Ox.flatten Flattens an array > Ox.flatten([1, [2, [3], 2], 1]) [1, 2, 3, 2, 1] @*/ -Ox.flatten = function(array) { +export function flatten(array) { var ret = []; array.forEach(function(value) { if (Ox.isArray(value)) { @@ -493,7 +507,7 @@ Ox.flatten = function(array) { }; // FIXME: add docs and tests -Ox.getIndex = function(array, key, value) { +export function getIndex(array, key, value) { return Ox.indexOf(array, function(obj) { return obj[key] === value; }); @@ -509,13 +523,13 @@ Ox.getIndexById Returns the first array index of an object with a given id > Ox.getIndexById([{id: 'foo', str: 'Foo'}, {id: 'bar', str: 'Bar'}], 'baz') -1 @*/ -Ox.getIndexById = function(array, id) { - return Ox.getIndex(array, 'id', id); +export function getIndexById(array, id) { + return getIndex(array, 'id', id); }; // FIXME: add docs and tests -Ox.getObject = function(array, key, value) { - var index = Ox.getIndex(array, key, value); +export function getObject(array, key, value) { + var index = getIndex(array, key, value); return index > -1 ? array[index] : null; }; @@ -529,12 +543,12 @@ Ox.getObjectById Returns the first object in an array with a given id > Ox.getObjectById([{id: 'foo', str: 'Foo'}, {id: 'bar', str: 'Bar'}], 'baz') null @*/ -Ox.getObjectById = function(array, id) { - return Ox.getObject(array, 'id', id); +export function getObjectById(array, id) { + return getObject(array, 'id', id); }; /* -Ox.indexOf = function(arr) { +export function indexOf(arr) { // indexOf for primitives, test for function, deep equal for others }; */ @@ -555,7 +569,7 @@ Ox.last Gets or sets the last element of an array > Ox.last('123') '3' @*/ -Ox.last = function(array, value) { +export function last(array, value) { var ret; if (arguments.length == 1) { ret = array[array.length - 1]; @@ -576,7 +590,7 @@ Ox.makeArray Wraps any non-array in an array. ['foo'] @*/ // FIXME: rename to toArray -Ox.makeArray = function(value) { +export function makeArray(value) { var ret, type = Ox.typeOf(value); if (type == 'arguments' || type == 'nodelist') { ret = Ox.slice(value); @@ -599,7 +613,7 @@ Ox.nextValue Next value, given an array of numbers, a number and a direction > Ox.nextValue([], 1, 1) void 0 @*/ -Ox.nextValue = function(array, value, direction) { +export function nextValue(array, value, direction) { var found = false, nextValue; direction = direction || 1; direction == -1 && array.reverse(); @@ -641,7 +655,7 @@ Ox.range Python-style range > Ox.range(-1, -2, -0.5) [-1, -1.5] @*/ -Ox.range = function() { +export function range() { var array = []; Ox.loop.apply(null, Ox.slice(arguments).concat(function(index) { array.push(index); @@ -768,7 +782,7 @@ Ox.unique Removes duplicate values from an array > Ox.unique('foo') 'fo' @*/ -Ox.unique = function(array) { +export function unique(array) { return Ox.filter(array, function(value, index) { return array.indexOf(value) == index; }); @@ -781,7 +795,7 @@ Ox.zip Zips an array of arrays > Ox.zip([0, 1, 2], [3, 4, 5]) [[0, 3], [1, 4], [2, 5]] @*/ -Ox.zip = function() { +export function zip() { var args = arguments.length == 1 ? arguments[0] : Ox.slice(arguments), array = []; args[0].forEach(function(value, index) { diff --git a/source/Ox/js/Async.js b/source/Ox/js/Async.js index 7607d841..c0b6e17d 100644 --- a/source/Ox/js/Async.js +++ b/source/Ox/js/Async.js @@ -1,287 +1,297 @@ 'use strict'; -(function() { +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; - function asyncMap(forEach, collection, iterator, that, callback) { - var type = Ox.typeOf(collection), - results = type == 'object' ? {} : []; - callback = Ox.last(arguments); - that = arguments.length == 5 ? that : null; - forEach(collection, function(value, key, collection, callback) { - iterator(value, key, collection, function(value) { - results[key] = value; - callback(); - }); - }, that, function() { - callback(type == 'string' ? results.join('') : results); +const Ox = {}; + +Object.assign(Ox, + OxObject, + OxConstants, + OxMath +); + + +function internalAsyncMap(forEach, collection, iterator, that, callback) { + var type = Ox.typeOf(collection), + results = type == 'object' ? {} : []; + callback = Ox.last(arguments); + that = arguments.length == 5 ? that : null; + forEach(collection, function(value, key, collection, callback) { + iterator(value, key, collection, function(value) { + results[key] = value; + callback(); }); + }, that, function() { + callback(type == 'string' ? results.join('') : results); + }); +} + +export function asyncMap(array, iterator, that, callback) { + array = Ox.makeArray(array); + callback = Ox.last(arguments); + that = arguments.length == 4 ? that : null; + if (array.some(Ox.isArray)) { + Ox.serialMap(array, function(value, key, array, callback) { + Ox.parallelMap(Ox.makeArray(value), iterator, callback); + }, callback); + } else { + Ox.parallelMap(array, iterator, callback); } +}; - Ox.asyncMap = function(array, iterator, that, callback) { - array = Ox.makeArray(array); - callback = Ox.last(arguments); - that = arguments.length == 4 ? that : null; - if (array.some(Ox.isArray)) { - Ox.serialMap(array, function(value, key, array, callback) { - Ox.parallelMap(Ox.makeArray(value), iterator, callback); - }, callback); - } else { - Ox.parallelMap(array, iterator, callback); - } - }; - - /*@ - Ox.nonblockingForEach Non-blocking `forEach` with synchronous iterator - (col, iterator[, that], callback[, ms]) -> undefined - collection Collection - iterator Iterator function - value <*> Value - key Key - collection The collection - that The iterator's `this` binding - callback Callback function - ms Number of milliseconds after which to insert a `setTimeout` call - @*/ - Ox.nonblockingForEach = function(collection, iterator, that, callback, ms) { - var i = 0, keys, last = Ox.last(arguments), - n, time, type = Ox.typeOf(collection); - callback = Ox.isFunction(last) ? last : arguments[arguments.length - 2]; - collection = type == 'array' || type == 'object' - ? collection : Ox.slice(collection); - keys = type == 'object' - ? Object.keys(collection) : Ox.range(collection.length); - ms = ms || 1000; - n = Ox.len(collection); - that = arguments.length == 5 || ( - arguments.length == 4 && Ox.isFunction(last) - ) ? that : null; - time = +new Date(); - iterate(); - function iterate() { - Ox.forEach(keys.slice(i), function(key) { - if (key in collection) { - if (iterator.call( - that, collection[key], key, collection - ) === false) { - i = n; - return false; - } +/*@ +Ox.nonblockingForEach Non-blocking `forEach` with synchronous iterator + (col, iterator[, that], callback[, ms]) -> undefined + collection Collection + iterator Iterator function + value <*> Value + key Key + collection The collection + that The iterator's `this` binding + callback Callback function + ms Number of milliseconds after which to insert a `setTimeout` call +@*/ +export function nonblockingForEach(collection, iterator, that, callback, ms) { + var i = 0, keys, last = Ox.last(arguments), + n, time, type = Ox.typeOf(collection); + callback = Ox.isFunction(last) ? last : arguments[arguments.length - 2]; + collection = type == 'array' || type == 'object' + ? collection : Ox.slice(collection); + keys = type == 'object' + ? Object.keys(collection) : Ox.range(collection.length); + ms = ms || 1000; + n = Ox.len(collection); + that = arguments.length == 5 || ( + arguments.length == 4 && Ox.isFunction(last) + ) ? that : null; + time = +new Date(); + iterate(); + function iterate() { + Ox.forEach(keys.slice(i), function(key) { + if (key in collection) { + if (iterator.call( + that, collection[key], key, collection + ) === false) { + i = n; + return false; } - i++; - if (+new Date() >= time + ms) { - return false; // break - } - }); - if (i < n) { - setTimeout(function() { - time = +new Date(); - iterate(); - }, 1); - } else { - callback(); } - } - }; - - /*@ - Ox.nonblockingMap Non-blocking `map` with synchronous iterator - (collection, iterator[, that], callback[, ms]) -> undefined - collection Collection - iterator Iterator function - that The iterator's `this` binding - callback Callback function - ms Number of milliseconds after which to insert a `setTimeout` call - - > Ox.nonblockingMap(Ox.range(100000), Ox.identity, function(r) { Ox.test(r.length, 100000); }) - undefined - @*/ - Ox.nonblockingMap = function(collection, iterator, that, callback, ms) { - var last = Ox.last(arguments), - type = Ox.typeOf(collection), - results = type == 'object' ? {} : []; - callback = Ox.isFunction(last) ? last : arguments[arguments.length - 2]; - that = arguments.length == 5 || ( - arguments.length == 4 && Ox.isFunction(last) - ) ? that : null; - Ox.nonblockingForEach(collection, function(value, key, collection) { - results[key] = iterator.call(that, value, key, collection); - }, function() { - callback(type == 'string' ? results.join('') : results); - }, ms); - }; - - /*@ - Ox.parallelForEach `forEach` with asynchronous iterator, running in parallel - (collection, iterator[, that], callback) -> undefined - collection Collection - iterator Iterator function - value <*> Value - key Key - collection The collection - callback Callback function - that The iterator's this binding - callback Callback function - - > Ox.parallelForEach(Ox.range(10), Ox.test.pfeIterator, function() { Ox.test(Ox.test.pfeNumber, 5); }) - undefined - @*/ - Ox.parallelForEach = function(collection, iterator, that, callback) { - var i = 0, n, type = Ox.typeOf(collection); - callback = callback || (arguments.length == 3 ? arguments[2] : Ox.noop); - collection = type == 'array' || type == 'object' - ? collection : Ox.slice(collection); - n = Ox.len(collection); - that = arguments.length == 4 ? that : null; - Ox.forEach(collection, function(value, key, collection) { - iterator.call(that, value, key, collection, function() { - ++i == n && callback(); - }); + i++; + if (+new Date() >= time + ms) { + return false; // break + } }); - }; - - /*@ - Ox.parallelMap Parallel `map` with asynchronous iterator - (collection, iterator[, that], callback) -> undefined - collection Collection - iterator Iterator function - value <*> Value - key Key - collection The collection - callback Callback function - that The iterator's this binding - callback Callback function - results Results - - > Ox.parallelMap(Ox.range(10), Ox.test.pmIterator, function(r) { Ox.test(Ox.sum(r), 0); }) - undefined - @*/ - Ox.parallelMap = function() { - asyncMap.apply(null, [Ox.parallelForEach].concat(Ox.slice(arguments))); - }; - - /*@ - Ox.serialForEach `forEach` with asynchronous iterator, run serially - (collection, iterator[, that], callback) -> undefined - collection Collection - iterator Iterator function - value <*> Value - key Key - collection The collection - callback Callback function - that The iterator's this binding - callback Callback function - - > Ox.serialForEach(Ox.range(10), Ox.test.sfeIterator, function() { Ox.test(Ox.test.sfeNumber, 5); }) - undefined - @*/ - Ox.serialForEach = function(collection, iterator, that, callback) { - var i = 0, keys, n, type = Ox.typeOf(collection); - callback = callback || (arguments.length == 3 ? arguments[2] : Ox.noop); - collection = type == 'array' || type == 'object' - ? collection : Ox.slice(collection); - keys = type == 'object' - ? Object.keys(collection) : Ox.range(collection.length); - n = Ox.len(collection); - that = arguments.length == 4 ? that : null; - iterate(); - function iterate(value) { - if (value !== false) { - if (keys[i] in collection) { - iterator.call( - that, - collection[keys[i]], - keys[i], - collection, - ++i < n ? iterate : callback - ); - } else { - ++i < n ? iterate() : callback(); - } - } else { - callback(); - } + if (i < n) { + setTimeout(function() { + time = +new Date(); + iterate(); + }, 1); + } else { + callback(); } - }; + } +}; - /*@ - Ox.serialMap Serial `map` with asynchronous iterator - (collection, iterator[, that], callback) -> undefined - collection Collection - iterator Iterator function - value <*> Value - key Key - collection The collection - callback Callback function - that The iterator's this binding +/*@ +Ox.nonblockingMap Non-blocking `map` with synchronous iterator + (collection, iterator[, that], callback[, ms]) -> undefined + collection Collection + iterator Iterator function + that The iterator's `this` binding + callback Callback function + ms Number of milliseconds after which to insert a `setTimeout` call + + > Ox.nonblockingMap(Ox.range(100000), Ox.identity, function(r) { Ox.test(r.length, 100000); }) + undefined +@*/ +export function nonblockingMap(collection, iterator, that, callback, ms) { + var last = Ox.last(arguments), + type = Ox.typeOf(collection), + results = type == 'object' ? {} : []; + callback = Ox.isFunction(last) ? last : arguments[arguments.length - 2]; + that = arguments.length == 5 || ( + arguments.length == 4 && Ox.isFunction(last) + ) ? that : null; + Ox.nonblockingForEach(collection, function(value, key, collection) { + results[key] = iterator.call(that, value, key, collection); + }, function() { + callback(type == 'string' ? results.join('') : results); + }, ms); +}; + +/*@ +Ox.parallelForEach `forEach` with asynchronous iterator, running in parallel + (collection, iterator[, that], callback) -> undefined + collection Collection + iterator Iterator function + value <*> Value + key Key + collection The collection callback Callback function - results Results - - > Ox.serialMap(Ox.range(10), Ox.test.smIterator, function(r) { Ox.test(Ox.sum(r), 0); }) - undefined - @*/ - Ox.serialMap = function(collection, iterator, that, callback) { - asyncMap.apply(null, [Ox.serialForEach].concat(Ox.slice(arguments))); - }; - // FIXME: The above test with 10000 iterations blows the stack + that The iterator's this binding + callback Callback function + + > Ox.parallelForEach(Ox.range(10), Ox.test.pfeIterator, function() { Ox.test(Ox.test.pfeNumber, 5); }) + undefined +@*/ +export function parallelForEach(collection, iterator, that, callback) { + var i = 0, n, type = Ox.typeOf(collection); + callback = callback || (arguments.length == 3 ? arguments[2] : Ox.noop); + collection = type == 'array' || type == 'object' + ? collection : Ox.slice(collection); + n = Ox.len(collection); + that = arguments.length == 4 ? that : null; + Ox.forEach(collection, function(value, key, collection) { + iterator.call(that, value, key, collection, function() { + ++i == n && callback(); + }); + }); +}; + +/*@ +Ox.parallelMap Parallel `map` with asynchronous iterator + (collection, iterator[, that], callback) -> undefined + collection Collection + iterator Iterator function + value <*> Value + key Key + collection The collection + callback Callback function + that The iterator's this binding + callback Callback function + results Results + + > Ox.parallelMap(Ox.range(10), Ox.test.pmIterator, function(r) { Ox.test(Ox.sum(r), 0); }) + undefined +@*/ +export function parallelMap() { + internalAsyncMap.apply(null, [Ox.parallelForEach].concat(Ox.slice(arguments))); +}; + +/*@ +Ox.serialForEach `forEach` with asynchronous iterator, run serially + (collection, iterator[, that], callback) -> undefined + collection Collection + iterator Iterator function + value <*> Value + key Key + collection The collection + callback Callback function + that The iterator's this binding + callback Callback function + + > Ox.serialForEach(Ox.range(10), Ox.test.sfeIterator, function() { Ox.test(Ox.test.sfeNumber, 5); }) + undefined +@*/ +export function serialForEach(collection, iterator, that, callback) { + var i = 0, keys, n, type = Ox.typeOf(collection); + callback = callback || (arguments.length == 3 ? arguments[2] : Ox.noop); + collection = type == 'array' || type == 'object' + ? collection : Ox.slice(collection); + keys = type == 'object' + ? Object.keys(collection) : Ox.range(collection.length); + n = Ox.len(collection); + that = arguments.length == 4 ? that : null; + iterate(); + function iterate(value) { + if (value !== false) { + if (keys[i] in collection) { + iterator.call( + that, + collection[keys[i]], + keys[i], + collection, + ++i < n ? iterate : callback + ); + } else { + ++i < n ? iterate() : callback(); + } + } else { + callback(); + } + } +}; + +/*@ +Ox.serialMap Serial `map` with asynchronous iterator + (collection, iterator[, that], callback) -> undefined + collection Collection + iterator Iterator function + value <*> Value + key Key + collection The collection + callback Callback function + that The iterator's this binding + callback Callback function + results Results + + > Ox.serialMap(Ox.range(10), Ox.test.smIterator, function(r) { Ox.test(Ox.sum(r), 0); }) + undefined +@*/ +export function serialMap(collection, iterator, that, callback) { + internalAsyncMap.apply(null, [Ox.serialForEach].concat(Ox.slice(arguments))); +}; +// FIXME: The above test with 10000 iterations blows the stack -}()); diff --git a/source/Ox/js/Base.js b/source/Ox/js/Base.js new file mode 100644 index 00000000..d0a315df --- /dev/null +++ b/source/Ox/js/Base.js @@ -0,0 +1,49 @@ +/*@ +Ox.filter Filters a collection by a given condition + Unlike `Array.prototype.filter`, `Ox.filter` works for arrays, objects and + strings. + > Ox.filter([2, 1, 0], function(v, i) { return v == i; }) + [1] + > Ox.filter({a: 'c', b: 'b', c: 'a'}, function(v, k) { return v == k; }) + {b: 'b'} + > Ox.filter(' foo bar ', function(v) { return v != ' '; }) + 'foobar' +@*/ +export function filter(collection, iterator, that) { + var ret, type = Ox.typeOf(collection); + iterator = iterator || Ox.identity; + if (type == 'object' || type == 'storage') { + ret = {}; + Ox.forEach(collection, function(value, key) { + if (iterator.call(that, value, key, collection)) { + ret[key] = value; + } + }); + } else { + ret = Ox.slice(collection).filter(iterator, that); + if (type == 'string') { + ret = ret.join(''); + } + } + return ret; +}; + +/*@ +Ox.indexOf Returns the first index of a collection element that passes a test + > Ox.indexOf([1, 2, 3], function(val) { return val % 2 == 0; }) + 1 + > Ox.indexOf({a: 1, b: 2, c: 3}, function(val) { return val % 2 == 0; }) + 'b' + > Ox.indexOf('FooBar', function(val) { return val == val.toUpperCase(); }) + 0 + > Ox.indexOf([1, 2, 3], function(val) { return val == 0; }) + -1 +@*/ +export function indexOf(collection, test) { + var index = Ox.forEach(collection, function(value) { + return !test(value); // break if test succeeds + }); + return Ox.isObject(collection) ? Object.keys(collection)[index] || null + : index == collection.length ? -1 : index; +}; + diff --git a/source/Ox/js/Collection.js b/source/Ox/js/Collection.js index efcb41ac..01b4bf82 100644 --- a/source/Ox/js/Collection.js +++ b/source/Ox/js/Collection.js @@ -1,5 +1,22 @@ 'use strict'; +import * as OxCore from './Core.js'; +import * as OxFunction from './Function.js'; +import * as OxConstants from './Constants.js'; +import * as OxType from './Type.js'; +import * as OxObject from './Object.js'; + +const Ox = {}; + +Object.assign(Ox, + OxCore, + OxConstants, + OxType, + OxObject, + OxFunction, +); + + /*@ Ox.avg Returns the average of an array's values, or an object's properties (collection) -> Average value @@ -11,8 +28,8 @@ Ox.avg Returns the average of an array's values, or an object's properties > Ox.avg('avg is 0.1') 0.1 @*/ -Ox.avg = function(collection) { - return Ox.sum(collection) / Ox.len(collection); +export function avg(collection) { + return Ox.sum(collection) / len(collection); }; /*@ @@ -28,13 +45,13 @@ Ox.clone Returns a (shallow or deep) copy of an array or object > (function() { var a = [[0, 1]], b = Ox.clone(a, true); a[0][0] = null; return b[0]; }()) [0, 1] @*/ -Ox.clone = function(collection, deep) { +export function clone(collection, deep) { var ret, type = Ox.typeOf(collection); if (type != 'array' && type != 'object') { ret = collection; } else if (deep) { ret = type == 'array' ? [] : {}; - Ox.forEach(collection, function(value, key) { + forEach(collection, function(value, key) { type = Ox.typeOf(value); ret[key] = type == 'array' || type == 'object' ? Ox.clone(value, true) : value; @@ -59,7 +76,7 @@ Ox.contains Tests if a collection contains a value > Ox.contains('foobar', 'bar') true @*/ -Ox.contains = function(collection, value) { +export function contains(collection, value) { var type = Ox.typeOf(collection); return ( type == 'nodelist' || type == 'object' @@ -84,9 +101,9 @@ Ox.count Counts the occurences of values in a collection > Ox.count('foo', 'x') 0 @*/ -Ox.count = function(collection, value) { +export function count(collection, value) { var count = {}; - Ox.forEach(collection, function(value) { + forEach(collection, function(value) { count[value] = (count[value] || 0) + 1; }); return value ? count[value] || 0 : count; @@ -111,42 +128,13 @@ Ox.every Tests if every element of a collection satisfies a given condition > Ox.every([true, true, true]) true @*/ -Ox.every = function(collection, iterator, that) { +export function every(collection, iterator, that) { iterator = iterator || Ox.identity; - return Ox.forEach(collection, function(value, key, collection) { + return forEach(collection, function(value, key, collection) { return !!iterator.call(that, value, key, collection); - }) == Ox.len(collection); + }) == len(collection); }; -/*@ -Ox.filter Filters a collection by a given condition - Unlike `Array.prototype.filter`, `Ox.filter` works for arrays, objects and - strings. - > Ox.filter([2, 1, 0], function(v, i) { return v == i; }) - [1] - > Ox.filter({a: 'c', b: 'b', c: 'a'}, function(v, k) { return v == k; }) - {b: 'b'} - > Ox.filter(' foo bar ', function(v) { return v != ' '; }) - 'foobar' -@*/ -Ox.filter = function(collection, iterator, that) { - var ret, type = Ox.typeOf(collection); - iterator = iterator || Ox.identity; - if (type == 'object' || type == 'storage') { - ret = {}; - Ox.forEach(collection, function(value, key) { - if (iterator.call(that, value, key, collection)) { - ret[key] = value; - } - }); - } else { - ret = Ox.slice(collection).filter(iterator, that); - if (type == 'string') { - ret = ret.join(''); - } - } - return ret; -}; /*@ Ox.forEach forEach loop @@ -171,7 +159,7 @@ Ox.forEach forEach loop > Ox.forEach({a: 'f', b: 'o', c: 'o'}, function(v, k) { return v != 'o' }); 1 @*/ -Ox.forEach = function(collection, iterator, that) { +export function forEach(collection, iterator, that) { var i = 0, key, type = Ox.typeOf(collection); if (type == 'object' || type == 'storage') { for (key in collection) { @@ -197,25 +185,6 @@ Ox.forEach = function(collection, iterator, that) { return i; }; -/*@ -Ox.indexOf Returns the first index of a collection element that passes a test - > Ox.indexOf([1, 2, 3], function(val) { return val % 2 == 0; }) - 1 - > Ox.indexOf({a: 1, b: 2, c: 3}, function(val) { return val % 2 == 0; }) - 'b' - > Ox.indexOf('FooBar', function(val) { return val == val.toUpperCase(); }) - 0 - > Ox.indexOf([1, 2, 3], function(val) { return val == 0; }) - -1 -@*/ -Ox.indexOf = function(collection, test) { - var index = Ox.forEach(collection, function(value) { - return !test(value); // break if test succeeds - }); - return Ox.isObject(collection) ? Object.keys(collection)[index] || null - : index == collection.length ? -1 : index; -}; - /*@ Ox.indicesOf Returns all indices of collection elements that pass a test > Ox.indicesOf([1, 2, 3], function(val) { return val % 2 == 1; }) @@ -227,9 +196,9 @@ Ox.indicesOf Returns all indices of collection elements that pass a test > Ox.indicesOf([1, 2, 3], function(val) { return val == 0; }) [] @*/ -Ox.indicesOf = function(collection, test) { +export function indicesOf(collection, test) { var ret = []; - Ox.forEach(collection, function(value, index) { + forEach(collection, function(value, index) { test(value) && ret.push(index); }); return ret; @@ -256,7 +225,7 @@ Ox.len Returns the length of an array, nodelist, object, storage or string > Ox.len(function(a, b, c) {}) undefined @*/ -Ox.len = function(collection) { +export function len(collection) { var ret, type = Ox.typeOf(collection); if ( type == 'arguments' || type == 'array' @@ -282,11 +251,11 @@ Ox.map Transforms the values of an array, object or string > Ox.map([,], function(v, i) { return i; }) [,] @*/ -Ox.map = function(collection, iterator, that) { +export function map(collection, iterator, that) { var ret, type = Ox.typeOf(collection); if (type == 'object' || type == 'storage') { ret = {}; - Ox.forEach(collection, function(value, key) { + forEach(collection, function(value, key) { ret[key] = iterator.call(that, value, key, collection); }); } else { @@ -309,7 +278,7 @@ Ox.max Returns the maximum value of a collection > Ox.max([]) -Infinity @*/ -Ox.max = function(collection) { +export function max(collection) { var ret, values = Ox.values(collection); if (values.length < Ox.STACK_LENGTH) { ret = Math.max.apply(null, values); @@ -332,7 +301,7 @@ Ox.min Returns the minimum value of a collection > Ox.min([]) Infinity @*/ -Ox.min = function(collection) { +export function min(collection) { var ret, values = Ox.values(collection); if (values.length < Ox.STACK_LENGTH) { ret = Math.min.apply(null, values); @@ -359,8 +328,8 @@ Ox.numberOf Returns the number of elements in a collection that pass a test > Ox.numberOf('foo', function(v, k, c) { return v == c[k - 1]; }) 1 @*/ -Ox.numberOf = function(collection, test) { - return Ox.len(Ox.filter(collection, test)); +export function numberOf(collection, test) { + return len(Ox.filter(collection, test)); }; /*@ @@ -381,7 +350,7 @@ Ox.remove Removes an element from an array or object and returns it > Ox.test.collection [['a', 'c'], {a: 0, c: 2}] @*/ -Ox.remove = function(collection, element) { +export function remove(collection, element) { var ret, key; if (Ox.isArray(collection)) { key = collection.indexOf(element); @@ -405,7 +374,7 @@ Ox.reverse Reverses an array or string > Ox.reverse('foobar') 'raboof' @*/ -Ox.reverse = function(collection) { +export function reverse(collection) { return Ox.isArray(collection) ? Ox.clone(collection).reverse() : collection.toString().split('').reverse().join(''); @@ -420,7 +389,7 @@ Ox.shuffle Randomizes the order of values within a collection > Ox.shuffle('123').split('').sort().join('') '123' @*/ -Ox.shuffle = function(collection) { +export function shuffle(collection) { var keys, ret, type = Ox.typeOf(collection), values; if (type == 'object' || type == 'storage') { keys = Object.keys(collection); @@ -443,57 +412,6 @@ Ox.shuffle = function(collection) { return ret; }; -/*@ -Ox.slice Alias for `Array.prototype.slice.call` - (collection[, start[, stop]]) -> Array - collection Array-like - start Start position - stop Stop position - > (function() { return Ox.slice(arguments); }(1, 2, 3)) - [1, 2, 3] - > Ox.slice('foo', 0, 1); - ['f'] - > Ox.slice({0: 'f', 1: 'o', 2: 'o', length: 3}, -2) - ['o', 'o'] -@*/ -// FIXME: remove toArray alias -Ox.slice = Ox.toArray = function(collection, start, stop) { - return Array.prototype.slice.call(collection, start, stop); -}; -// IE8 can't apply slice to NodeLists, returns an empty array if undefined is -// passed as stop and returns an array of null values if a string is passed as -// value. Firefox 3.6 returns an array of undefined values if a string is passed -// as value. -if ( - Ox.slice([0]).length == 0 - || Ox.slice('0')[0] === null - || Ox.slice('0')[0] === void 0 - || !(function() { - try { - return Ox.slice(document.getElementsByTagName('a')); - } catch (error) {} - }()) -) { - // FIXME: remove toArray alias - Ox.slice = Ox.toArray = function(collection, start, stop) { - var args = stop === void 0 ? [start] : [start, stop], - array = [], index, length, ret; - if (Ox.typeOf(collection) == 'string') { - collection = collection.split(''); - } - try { - ret = Array.prototype.slice.apply(collection, args); - } catch (error) { - length = collection.length; - for (index = 0; index < length; index++) { - array[index] = collection[index]; - } - ret = Array.prototype.slice.apply(array, args); - } - return ret; - }; -} - /*@ Ox.some Tests if one or more elements of a collection meet a given condition Unlike `Array.prototype.some`, `Ox.some` works for arrays, objects and @@ -507,11 +425,11 @@ Ox.some Tests if one or more elements of a collection meet a given condition > Ox.some([false, null, 0, '', void 0]) false @*/ -Ox.some = function(collection, iterator, that) { +export function some(collection, iterator, that) { iterator = iterator || Ox.identity; - return Ox.forEach(collection, function(value, key, collection) { + return forEach(collection, function(value, key, collection) { return !iterator.call(that, value, key, collection); - }) < Ox.len(collection); + }) < len(collection); }; /*@ @@ -529,10 +447,10 @@ Ox.sum Returns the sum of the values of a collection > Ox.sum('08', -2, 'foo') 6 @*/ -Ox.sum = function(collection) { +export function sum(collection) { var ret = 0; collection = arguments.length > 1 ? Ox.slice(arguments) : collection; - Ox.forEach(collection, function(value) { + forEach(collection, function(value) { value = +value; ret += isFinite(value) ? value : 0; }); @@ -541,7 +459,7 @@ Ox.sum = function(collection) { /* FIXME: do we need this kind of zip functionality? -Ox.arrayToObject = function(array, key) { +export function arrayToObject(array, key) { var ret = {}; array.forEach(function(v) { ret[v[key]] = v; @@ -549,9 +467,9 @@ Ox.arrayToObject = function(array, key) { return ret; }; -Ox.objectToArray = function(object, key) { +export function objectToArray(object, key) { var ret = []; - Ox.forEach(object, function(v, k) { + forEach(object, function(v, k) { ret.push(Ox.extend(v, key, k)); }); return ret; @@ -574,13 +492,13 @@ Ox.values Returns the values of a collection > Ox.values([1,,3]) [1,,3] @*/ -Ox.values = function(collection) { +export function values(collection) { var ret, type = Ox.typeOf(collection); if (type == 'array' || type == 'nodelist') { ret = Ox.slice(collection); } else if (type == 'object' || type == 'storage') { ret = []; - Ox.forEach(collection, function(value) { + forEach(collection, function(value) { ret.push(value); }); } else if (type == 'string') { @@ -613,9 +531,9 @@ Ox.walk Iterates over a nested data structure > Ox.test.array [['a'], ['b', 'c'], ['b', 'd']] @*/ -Ox.walk = function(collection, iterator, that, keys) { +export function walk(collection, iterator, that, keys) { keys = keys || []; - Ox.forEach(collection, function(value, key) { + forEach(collection, function(value, key) { var keys_ = keys.concat(key); iterator.call(that, value, keys_, collection); Ox.walk(collection[key], iterator, that, keys_); diff --git a/source/Ox/js/Color.js b/source/Ox/js/Color.js index ce01d81d..c58b30cf 100644 --- a/source/Ox/js/Color.js +++ b/source/Ox/js/Color.js @@ -1,5 +1,17 @@ 'use strict'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; + +const Ox = {}; + +Object.assign(Ox, + OxObject, + OxConstants, + OxMath +); + /*@ Ox.hsl Takes RGB values and returns HSL values (rgb) <[n]> HSL values @@ -15,7 +27,7 @@ Ox.hsl Takes RGB values and returns HSL values > Ox.hsl(0, 255, 0) [120, 1, 0.5] @*/ -Ox.hsl = function(rgb) { +export function hsl(rgb) { var hsl = [0, 0, 0], max, min; if (arguments.length == 3) { rgb = Ox.slice(arguments); @@ -62,7 +74,7 @@ Ox.rgb Takes HSL values and returns RGB values [0, 255, 0] @*/ -Ox.rgb = function(hsl) { +export function rgb(hsl) { var rgb = [0, 0, 0], v1, v2, v3; if (arguments.length == 3) { hsl = Ox.slice(arguments); @@ -106,7 +118,7 @@ Ox.toHex Format RGB array as hex value > Ox.toHex([192, 128, 64]) 'C08040' @*/ -Ox.toHex = function(rgb) { +export function toHex(rgb) { return rgb.map(function(value) { return Ox.pad(value.toString(16).toUpperCase(), 'left', 2, '0'); }).join(''); @@ -117,7 +129,7 @@ Ox.toRGB Format hex value as RGB array > Ox.toRGB('C08040') [192, 128, 64] @*/ -Ox.toRGB = function(hex) { +export function toRGB(hex) { return Ox.range(3).map(function(index) { return parseInt(hex.substr(index * 2, 2), 16); }); diff --git a/source/Ox/js/Constants.js b/source/Ox/js/Constants.js index 1fe2d45b..a0e0004b 100644 --- a/source/Ox/js/Constants.js +++ b/source/Ox/js/Constants.js @@ -1,30 +1,38 @@ 'use strict'; +import * as OxMath from './Math.js'; + +const Ox = {}; + +Object.assign(Ox, + OxMath +); + //@ Ox.AMPM <[s]> ['AM', 'PM'] -Ox.AMPM = ['AM', 'PM']; +export const AMPM = ['AM', 'PM']; //@ Ox.BASE_32_ALIASES Base 32 aliases -Ox.BASE_32_ALIASES = {'I': '1', 'L': '1', 'O': '0', 'U': 'V'}, +export const BASE_32_ALIASES = {'I': '1', 'L': '1', 'O': '0', 'U': 'V'}; //@ Ox.BASE_32_DIGITS Base 32 digits -Ox.BASE_32_DIGITS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; +export const BASE_32_DIGITS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; //@ Ox.BCAD <[s]> ['BC', 'AD'] -Ox.BCAD = ['BC', 'AD']; +export const BCAD = ['BC', 'AD']; /*@ Ox.EARTH_RADIUS Radius of the earth in meters See http://en.wikipedia.org/wiki/WGS-84 */ -Ox.EARTH_RADIUS = 6378137; +export const EARTH_RADIUS = 6378137; //@ Ox.EARTH_CIRCUMFERENCE Circumference of the earth in meters -Ox.EARTH_CIRCUMFERENCE = 2 * Math.PI * Ox.EARTH_RADIUS; +export const EARTH_CIRCUMFERENCE = 2 * Math.PI * EARTH_RADIUS; //@ Ox.EARTH_SURFACE Surface of the earth in square meters -Ox.EARTH_SURFACE = 4 * Math.PI * Math.pow(Ox.EARTH_RADIUS, 2); +export const EARTH_SURFACE = 4 * Math.PI * Math.pow(EARTH_RADIUS, 2); //@ Ox.HTML_ENTITIES HTML entities for ... (FIXME) -Ox.HTML_ENTITIES = { +export const HTML_ENTITIES = { '"': '"', '&': '&', "'": ''', '<': '<', '>': '>' }; //@ Ox.KEYS Names for key codes // The dot notation ('0.numpad') allows for namespaced events ('key_0.numpad'), // so that binding to 'key_0' will catch both 'key_0' and 'key_0.numpad'. -Ox.KEYS = { +export const KEYS = { 0: 'section', 8: 'backspace', 9: 'tab', 12: 'clear', 13: 'enter', 16: 'shift', 17: 'control', 18: 'alt', 20: 'capslock', 27: 'escape', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', @@ -55,9 +63,9 @@ Ox.KEYS = { // see dojo, for ex. }; //@ Ox.LOCALE Default locale -Ox.LOCALE = 'en'; +export const LOCALE = 'en'; //@ Ox.LOCALE_NAMES Locale names -Ox.LOCALE_NAMES = { +export const LOCALE_NAMES = { 'ar': 'العربية', 'de': 'Deutsch', 'el': 'Ελληνικά', @@ -67,51 +75,56 @@ Ox.LOCALE_NAMES = { 'tr': 'Türkçe' }; //@ Ox.LOCALES Locales per module -Ox.LOCALES = {}; +export const LOCALES = {}; //@ Ox.MAX_LATITUDE Maximum latitude of a Mercator projection -Ox.MAX_LATITUDE = Ox.deg(Math.atan(Ox.sinh(Math.PI))); +export const MAX_LATITUDE = Ox.deg(Math.atan(Ox.sinh(Math.PI))); //@ Ox.MIN_LATITUDE Minimum latitude of a Mercator projection -Ox.MIN_LATITUDE = -Ox.MAX_LATITUDE; +export const MIN_LATITUDE = -Ox.MAX_LATITUDE; //@ Ox.MODIFIER_KEYS Names for modifier keys // meta comes last so that one can differentiate between // alt_control_shift_meta.left and alt_control_shift_meta.right -Ox.MODIFIER_KEYS = { +export const MODIFIER_KEYS = { altKey: 'alt', // Mac: option ctrlKey: 'control', shiftKey: 'shift', metaKey: 'meta' // Mac: command }; //@ Ox.MONTHS <[s]> Names of months -Ox.MONTHS = [ +export const MONTHS = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; //@ Ox.SHORT_MONTHS <[s]> Short names of months -Ox.SHORT_MONTHS = Ox.MONTHS.map(function(val) { +export const SHORT_MONTHS = MONTHS.map(function(val) { return val.slice(0, 3); }); //@ Ox.PATH Path of Ox.js -Ox.PATH = (function() { +export const PATH = (function() { // IE8 can't apply slice to NodeLists, see Ox.slice - var index, regexp = /Ox\.js(\?.+|)$/, - scripts = document.getElementsByTagName('script'), src; + var index, regexp = /Ox\.js(\?.+|)$/, scripts, src; + try { + scripts = document.getElementsByTagName('script') + } catch(e) { + scripts = '' + } for (index = scripts.length - 1; index >= 0; index--) { src = scripts[index].src; if (regexp.test(src)) { return src.replace(regexp, ''); } } + return '' }()); //@ Ox.MODE Mode ('dev' or 'min') -Ox.MODE = Ox.PATH.slice(0, -1).split('/').pop(); +export const MODE = PATH.slice(0, -1).split('/').pop(); //@ Ox.PREFIXES <[str]> `['', 'K', 'M', 'G', 'T', 'P']` -Ox.PREFIXES = ['', 'K', 'M', 'G', 'T', 'P']; +export const PREFIXES = ['', 'K', 'M', 'G', 'T', 'P']; //@ Ox.SEASONS <[s]> Names of the seasons of the year -Ox.SEASONS = ['Winter', 'Spring', 'Summer', 'Fall']; +export const SEASONS = ['Winter', 'Spring', 'Summer', 'Fall']; //@ Ox.STACK_SIZE Maximum number of arguments -Ox.STACK_SIZE = 65536; +export const STACK_SIZE = 65536; //@ Ox.SYMBOLS Unicode characters for symbols -Ox.SYMBOLS = { +export const SYMBOLS = { dollar: '\u0024', cent: '\u00A2', pound: '\u00A3', currency: '\u00A4', yen: '\u00A5', bullet: '\u2022', ellipsis: '\u2026', permille: '\u2030', colon: '\u20A1', cruzeiro: '\u20A2', franc: '\u20A3', lira: '\u20A4', @@ -138,12 +151,12 @@ Ox.SYMBOLS = { click: '\uF803', apple: '\uF8FF' }; //@ Ox.VERSION OxJS version number -Ox.VERSION = '0.1'; +export const VERSION = '0.1'; //@ Ox.WEEKDAYS <[s]> Names of weekdays -Ox.WEEKDAYS = [ +export const WEEKDAYS = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]; //@ Ox.SHORT_WEEKDAYS <[s]> Short names of weekdays -Ox.SHORT_WEEKDAYS = Ox.WEEKDAYS.map(function(val) { +export const SHORT_WEEKDAYS = WEEKDAYS.map(function(val) { return val.slice(0, 3); }); diff --git a/source/Ox/js/Core.js b/source/Ox/js/Core.js index 8a8e402c..d278ae49 100644 --- a/source/Ox/js/Core.js +++ b/source/Ox/js/Core.js @@ -2,15 +2,23 @@ 'use strict'; -/*@ -Ox The `Ox` object - See `Ox.wrap` for details. - (value) -> wrapped value - value <*> Any value -@*/ -this.Ox = function(value) { - return Ox.wrap(value); -}; +import * as OxType from './Type.js'; +import * as OxCollection from './Collection.js'; +import * as OxObject from './Object.js'; +import * as OxDOM from './DOM.js'; +import * as OxString from './String.js'; +import * as OxRequest from './Request.js'; + +const Ox = {}; + +Object.assign(Ox, + OxType, + OxCollection, + OxObject, + OxDOM, + OxString, + OxRequest, +); /*@ Ox.load Loads OxJS and, optionally, one or more modules @@ -52,7 +60,7 @@ Ox.load Loads OxJS and, optionally, one or more modules callback Callback function success If true, all modules have been loaded successfully @*/ -Ox.load = function() { +export function load() { var callback = arguments[arguments.length - 1], length, loaded = 0, localeFiles = [], modules = {}, succeeded = 0, type = Ox.typeOf(arguments[0]); @@ -76,20 +84,18 @@ Ox.load = function() { if (!length) { callback(true); } else { - Ox.forEach(modules, function(options, module) { - Ox.getFile( - Ox.PATH + module + '/' + module + '.js?' + Ox.VERSION, - function() { - Ox.load[module](options, function(success) { - succeeded += success; - if (++loaded == length) { - Ox.setLocale(Ox.LOCALE, function() { - callback(succeeded == length); - }); - } + Ox.forEach(modules, async function(options, module) { + console.log("load module!", module, options) + // Ox.PATH + module + '/index.js?' + Ox.VERSION, + const obj = await import('../../' + module + '/index.js?' + Ox.VERSION); + Ox.load[module](options, function(success) { + succeeded += success; + if (++loaded == length) { + Ox.setLocale(Ox.LOCALE, function() { + callback(succeeded == length); }); } - ); + }); }); } }); @@ -110,7 +116,7 @@ Ox.localStorage localStorage wrapper > Ox.test.localStorage.delete('foo')() {} @*/ -Ox.localStorage = function(namespace) { +export function localStorage(namespace) { var localStorage; try { // this will fail if third party cookies/storage is not allowed @@ -163,7 +169,7 @@ Ox.localStorage = function(namespace) { Ox.Log Logging module @*/ Ox.Log = (function() { - var storage = Ox.localStorage('Ox'), + var storage = localStorage('Ox'), log = storage('log') || {filter: [], filterEnabled: true}, that = function() { var ret; @@ -239,7 +245,7 @@ Ox.loop For-loop, functional-style > Ox.loop(0, 3, 2, function() {}) 4 @*/ -Ox.loop = function() { +export function loop() { var length = arguments.length, start = length > 2 ? arguments[0] : 0, stop = arguments[length > 2 ? 1 : 0], @@ -263,7 +269,7 @@ Ox.print Prints its arguments to the console > Ox.print('foo', 'bar').split(' ').slice(1).join(' ') 'foo bar' @*/ -Ox.print = function() { +export function print() { var args = Ox.slice(arguments), date = new Date(); args.unshift( date.toString().split(' ')[4] + '.' + (+date).toString().slice(-3) @@ -276,7 +282,7 @@ Ox.print = function() { Ox.trace Prints its arguments to the console, followed by a stack trace (arg, ...) -> String @*/ -Ox.trace = function() { +export function trace() { var args = Ox.slice(arguments); try { throw new Error(); @@ -296,12 +302,10 @@ Ox.uid Returns a unique id > Ox.uid() != Ox.uid() true @*/ -Ox.uid = (function() { - var uid = 0; - return function() { - return ++uid; - }; -}()); +var _uid = 0; +export function uid() { + return ++_uid; +} /*@ Ox.wrap Wraps a value so that one can directly call any Ox function on it @@ -317,7 +321,7 @@ Ox.wrap Wraps a value so that one can directly call any Ox function on it > Ox.wrap('foobar').value() 'foobar' @*/ -Ox.wrap = function(value, chained) { +export function wrap(value, chained) { // somewhat inspired by underscore.js var wrapper = { chain: function() { @@ -341,3 +345,55 @@ Ox.wrap = function(value, chained) { }); return wrapper; }; + +/*@ +Ox.slice Alias for `Array.prototype.slice.call` + (collection[, start[, stop]]) -> Array + collection Array-like + start Start position + stop Stop position + > (function() { return Ox.slice(arguments); }(1, 2, 3)) + [1, 2, 3] + > Ox.slice('foo', 0, 1); + ['f'] + > Ox.slice({0: 'f', 1: 'o', 2: 'o', length: 3}, -2) + ['o', 'o'] +@*/ +// FIXME: remove toArray alias +export function slice(collection, start, stop) { + return Array.prototype.slice.call(collection, start, stop); +}; +// IE8 can't apply slice to NodeLists, returns an empty array if undefined is +// passed as stop and returns an array of null values if a string is passed as +// value. Firefox 3.6 returns an array of undefined values if a string is passed +// as value. +if ( + slice([0]).length == 0 + || slice('0')[0] === null + || slice('0')[0] === void 0 + || !(function() { + try { + return Ox.slice(document.getElementsByTagName('a')); + } catch (error) {} + }()) +) { + // FIXME: remove toArray alias + slice = function(collection, start, stop) { + var args = stop === void 0 ? [start] : [start, stop], + array = [], index, length, ret; + if (Ox.typeOf(collection) == 'string') { + collection = collection.split(''); + } + try { + ret = Array.prototype.slice.apply(collection, args); + } catch (error) { + length = collection.length; + for (index = 0; index < length; index++) { + array[index] = collection[index]; + } + ret = Array.prototype.slice.apply(array, args); + } + return ret; + }; +} + diff --git a/source/Ox/js/DOM.js b/source/Ox/js/DOM.js index 71ef21cc..ed270c20 100644 --- a/source/Ox/js/DOM.js +++ b/source/Ox/js/DOM.js @@ -1,5 +1,27 @@ 'use strict'; +import * as OxCore from './Core.js'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; +import * as OxType from './Type.js'; +import * as OxArray from './Array.js'; +import * as OxCollection from './Collection.js'; +import * as OxString from './String.js'; + +const Ox = {}; + +Object.assign(Ox, + OxCore, + OxObject, + OxConstants, + OxMath, + OxType, + OxArray, + OxCollection, + OxString, +); + /*@ Ox.$ Generic HTML element, mimics jQuery value tagname, selector, html element, `window`, or `document` @@ -31,7 +53,7 @@ Ox.$ Generic HTML element, mimics jQuery > Ox.$('').val('red').val() 'red' @*/ -Ox.$ = Ox.element = function $(value) { +export function element(value) { var elements = Ox.isArray(value) ? value // array of elements : Ox.isNodeList(value) ? Ox.slice(value) // nodelist @@ -796,6 +818,8 @@ Ox.$ = Ox.element = function $(value) { }; +export const $ = element; + /*@ Ox.canvas Generic canvas object Returns an object with the properties: `canvas`, `context`, `data` and @@ -806,7 +830,7 @@ Ox.canvas Generic canvas object height Height in px image Image object @*/ -Ox.canvas = function() { +export function canvas() { var c = {}, isImage = arguments.length == 1, image = isImage ? arguments[0] : { width: arguments[0], height: arguments[1] @@ -821,13 +845,24 @@ Ox.canvas = function() { return c; }; + +var callbacks = []; /*@ Ox.documentReady Calls a callback function once the DOM is ready (callback) -> If true, the document was ready callback Callback function @*/ -Ox.documentReady = (function() { - var callbacks = []; +export function documentReady(callback) { + if (document.readyState == 'complete') { + callback(); + return true; + } else { + callbacks.push(callback); + return false; + } +} + +if (typeof document !== 'undefined') { document.onreadystatechange = window.onload = function() { if (document.readyState == 'complete') { callbacks.forEach(function(callback) { @@ -836,13 +871,4 @@ Ox.documentReady = (function() { document.onreadystatechange = window.onload = null; } }; - return function(callback) { - if (document.readyState == 'complete') { - callback(); - return true; - } else { - callbacks.push(callback); - return false; - } - }; -}()); +} diff --git a/source/Ox/js/Date.js b/source/Ox/js/Date.js index 74668b2e..62c5511b 100644 --- a/source/Ox/js/Date.js +++ b/source/Ox/js/Date.js @@ -1,5 +1,17 @@ 'use strict'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; + +const Ox = {}; + +Object.assign(Ox, + OxObject, + OxConstants, + OxMath +); + //@ Ox.getDate Get the day of a date, optionally UTC // see Ox.setSeconds for source code @@ -21,7 +33,7 @@ Ox.getDateInWeek Get the date that falls on a given weekday in the same week "Monday, December 27, 1999" @*/ // fixme: why is this Monday first? shouldn't it then be "getDateInISOWeek"?? -Ox.getDateInWeek = function(date, weekday, utc) { +export function getDateInWeek(date, weekday, utc) { date = Ox.makeDate(date); var sourceWeekday = Ox.getISODay(date, utc), targetWeekday = Ox.isNumber(weekday) ? weekday @@ -50,7 +62,7 @@ Ox.getDayOfTheYear Get the day of the year for a given date > Ox.getDayOfTheYear(new Date("12/31/2004")) 366 @*/ -Ox.getDayOfTheYear = function(date, utc) { +export function getDayOfTheYear(date, utc) { date = Ox.makeDate(date); var month = Ox.getMonth(date, utc), year = Ox.getFullYear(date, utc); @@ -68,7 +80,7 @@ Ox.getDaysInMonth Get the number of days in a given month > Ox.getDaysInMonth(new Date('01/01/2004'), "February") 29 @*/ -Ox.getDaysInMonth = function(year, month) { +export function getDaysInMonth(year, month) { year = Ox.makeYear(year); month = Ox.isNumber(month) ? month : Ox.indexOf(Ox.MONTHS, function(v) { @@ -87,7 +99,7 @@ Ox.getDaysInYear Get the number of days in a given year > Ox.getDaysInYear(new Date('01/01/2004')) 366 @*/ -Ox.getDaysInYear = function(year, utc) { +export function getDaysInYear(year, utc) { return 365 + Ox.isLeapYear(Ox.makeYear(year, utc)); }; @@ -97,7 +109,7 @@ Ox.getFirstDayOfTheYear Get the weekday of the first day of a given year > Ox.getFirstDayOfTheYear(new Date('01/01/2000')) 6 @*/ -Ox.getFirstDayOfTheYear = function(date, utc) { +export function getFirstDayOfTheYear(date, utc) { date = Ox.makeDate(date); date = Ox.setMonth(date, 0, utc); date = Ox.setDate(date, 1, utc); @@ -114,7 +126,7 @@ Ox.getISODate Get the ISO date string for a given date > Ox.getISODate(new Date('01/01/2000')) '2000-01-01T00:00:00Z' @*/ -Ox.getISODate = function(date, utc) { +export function getISODate(date, utc) { return Ox.formatDate(Ox.makeDate(date), '%FT%TZ', utc); }; @@ -128,7 +140,7 @@ Ox.getISODay Get the ISO weekday of a given date > Ox.getISODay(new Date('01/03/2000')) 1 @*/ -Ox.getISODay = function(date, utc) { +export function getISODay(date, utc) { return Ox.getDay(Ox.makeDate(date), utc) || 7; }; @@ -143,7 +155,7 @@ Ox.getISOWeek Get the ISO week of a given date 1 @*/ -Ox.getISOWeek = function(date, utc) { +export function getISOWeek(date, utc) { date = Ox.makeDate(date); // set date to Thursday of the same week return Math.floor((Ox.getDayOfTheYear(Ox.setDate( @@ -162,7 +174,7 @@ Ox.getISOYear Get the ISO year of a given date 2000 @*/ -Ox.getISOYear = function(date, utc) { +export function getISOYear(date, utc) { date = Ox.makeDate(date); // set date to Thursday of the same week return Ox.getFullYear(Ox.setDate( @@ -180,7 +192,7 @@ Ox.getISOYear = function(date, utc) { // see Ox.setSeconds for source code //@ Ox.getTime Alias for `+new Date()` -Ox.getTime = function(utc) { +export function getTime(utc) { return +new Date() - (utc ? Ox.getTimezoneOffset() : 0); }; @@ -189,7 +201,7 @@ Ox.getTimezoneOffset Get the local time zone offset in milliseconds ([date]) -> Offset in milliseconds date Return offset at this date (if undefined, return current offset) @*/ -Ox.getTimezoneOffset = function(date) { +export function getTimezoneOffset(date) { return Ox.makeDate(date).getTimezoneOffset() * 60000; }; @@ -201,7 +213,7 @@ Ox.getTimezoneOffsetString Get the local time zone offset as a string > Ox.getTimezoneOffsetString(new Date('01/01/2000')).length 5 @*/ -Ox.getTimezoneOffsetString = function(date) { +export function getTimezoneOffsetString(date) { var offset = Ox.makeDate(date).getTimezoneOffset(); return (offset <= 0 ? '+' : '-') + Ox.pad(Math.floor(Math.abs(offset) / 60), 2) @@ -218,7 +230,7 @@ Ox.getWeek Get the week of a given day > Ox.getWeek(new Date('01/03/2000')) 1 @*/ -Ox.getWeek = function(date, utc) { +export function getWeek(date, utc) { date = Ox.makeDate(date); return Math.floor((Ox.getDayOfTheYear(date, utc) + Ox.getFirstDayOfTheYear(date, utc) - 1) / 7); @@ -233,7 +245,7 @@ Ox.isLeapYear Returns true if a given year is a leap year > Ox.isLeapYear(new Date('01/01/2004')) true @*/ -Ox.isLeapYear = function(year, utc) { +export function isLeapYear(year, utc) { year = Ox.makeYear(year, utc); return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); }; @@ -255,7 +267,7 @@ Ox.makeDate Takes a date, number or string, returns a date > Ox.formatDate(Ox.makeDate(Ox.parseDate('-50')), '%Y') '-50' @*/ -Ox.makeDate = function(date) { +export function makeDate(date) { // Safari 4/5 (<= 534.59.10) doesn't parse YYYY, YYYY-MM or YYYY-MM-DD if (Ox.isString(date) && Ox.isInvalidDate(new Date(date))) { if (/^\d{4}$/.test(date)) { @@ -280,7 +292,7 @@ Ox.makeYear Takes a date, number or string, returns a year > Ox.makeYear('1970') 1970 @*/ -Ox.makeYear = function(date, utc) { +export function makeYear(date, utc) { return Ox.isDate(date) ? Ox.getFullYear(date, utc) : parseInt(date, 10); }; @@ -295,7 +307,7 @@ Ox.parseDate Takes a string ('YYYY-MM-DD HH:MM:SS.MMM') and returns a date > Ox.parseDate('50', true).getUTCFullYear() 50 @*/ -Ox.parseDate = function(string, utc) { +export function parseDate(string, utc) { var date, defaults = [, 1, 1, 0, 0, 0, 0], values = /(-?\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?\.?(\d+)?/ @@ -320,7 +332,7 @@ Ox.parseDate = function(string, utc) { }; /* -Ox.parseDateRange = function(start, end, utc) { +export function parseDateRange(start, end, utc) { var dates = [ Ox.parseDate(start, utc), Ox.parseDate(end, utc) diff --git a/source/Ox/js/Encoding.js b/source/Ox/js/Encoding.js index 974108ac..26afd836 100644 --- a/source/Ox/js/Encoding.js +++ b/source/Ox/js/Encoding.js @@ -1,5 +1,17 @@ 'use strict'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; + +const Ox = {}; + +Object.assign(Ox, + OxObject, + OxConstants, + OxMath +); + /*@ Ox.encodeBase26 Encode a number as bijective base26 See @@ -15,7 +27,7 @@ Ox.encodeBase26 Encode a number as bijective base26 > Ox.encodeBase26(4461) 'FOO' @*/ -Ox.encodeBase26 = function(number) { +export function encodeBase26(number) { var string = ''; while (number) { string = String.fromCharCode(65 + (number - 1) % 26) + string; @@ -31,7 +43,7 @@ Ox.decodeBase26 Decodes a bijective base26-encoded number > Ox.decodeBase26('foo') 4461 @*/ -Ox.decodeBase26 = function(string) { +export function decodeBase26(string) { return string.toUpperCase().split('').reverse().reduce(function(p, c, i) { return p + (c.charCodeAt(0) - 64) * Math.pow(26, i); }, 0); @@ -45,7 +57,7 @@ Ox.encodeBase32 Encode a number as base32 > Ox.encodeBase32(33819) '110V' @*/ -Ox.encodeBase32 = function(number) { +export function encodeBase32(number) { return Ox.map(number.toString(32), function(char) { return Ox.BASE_32_DIGITS[parseInt(char, 32)]; }); @@ -61,7 +73,7 @@ Ox.decodeBase32 Decodes a base32-encoded number > Ox.decodeBase32('?').toString() 'NaN' @*/ -Ox.decodeBase32 = function(string) { +export function decodeBase32(string) { return parseInt(Ox.map(string.toUpperCase(), function(char) { var index = Ox.BASE_32_DIGITS.indexOf( Ox.BASE_32_ALIASES[char] || char @@ -75,7 +87,7 @@ Ox.encodeBase64 Encode a number as base64 > Ox.encodeBase64(32394) 'foo' @*/ -Ox.encodeBase64 = function(number) { +export function encodeBase64(number) { return btoa(Ox.encodeBase256(number)).replace(/=/g, ''); }; @@ -84,7 +96,7 @@ Ox.decodeBase64 Decodes a base64-encoded number > Ox.decodeBase64('foo') 32394 @*/ -Ox.decodeBase64 = function(string) { +export function decodeBase64(string) { return Ox.decodeBase256(atob(string)); }; @@ -93,7 +105,7 @@ Ox.encodeBase128 Encode a number as base128 > Ox.encodeBase128(1685487) 'foo' @*/ -Ox.encodeBase128 = function(number) { +export function encodeBase128(number) { var string = ''; while (number) { string = Ox.char(number & 127) + string; @@ -107,7 +119,7 @@ Ox.decodeBase128 Decode a base128-encoded number > Ox.decodeBase128('foo') 1685487 @*/ -Ox.decodeBase128 = function(string) { +export function decodeBase128(string) { return string.split('').reverse().reduce(function(p, c, i) { return p + (c.charCodeAt(0) << i * 7); }, 0); @@ -118,7 +130,7 @@ Ox.encodeBase256 Encode a number as base256 > Ox.encodeBase256(6713199) 'foo' @*/ -Ox.encodeBase256 = function(number) { +export function encodeBase256(number) { var string = ''; while (number) { string = Ox.char(number & 255) + string; @@ -132,7 +144,7 @@ Ox.decodeBase256 Decode a base256-encoded number > Ox.decodeBase256('foo') 6713199 @*/ -Ox.decodeBase256 = function(string) { +export function decodeBase256(string) { return string.split('').reverse().reduce(function(p, c, i) { return p + (c.charCodeAt(0) << i * 8); }, 0); @@ -149,7 +161,7 @@ Ox.encodeDeflate Encodes a string, using deflate > Ox.decodeDeflate(Ox.encodeDeflate('foo'), function(str) { Ox.test(str, 'foo'); }) undefined @*/ -Ox.encodeDeflate = function(string, callback) { +export function encodeDeflate(string, callback) { // Make sure we can encode the full unicode range of characters. string = Ox.encodeUTF8(string); // We can only safely write to RGB, so we need 1 pixel for 3 bytes. @@ -198,7 +210,7 @@ Ox.decodeDeflate Decodes an deflate-encoded string str The decoded string @*/ -Ox.decodeDeflate = function(string, callback) { +export function decodeDeflate(string, callback) { var image = new Image(), // PNG file signature and IHDR chunk data = '\u0089PNG\r\n\u001A\n\u0000\u0000\u0000\u000DIHDR' @@ -292,7 +304,7 @@ Ox.encodeUTF8 Encodes a string as UTF-8 > Ox.encodeUTF8("¥€$") "\u00C2\u00A5\u00E2\u0082\u00AC\u0024" @*/ -Ox.encodeUTF8 = function(string) { +export function encodeUTF8(string) { return Ox.map(string, function(char) { var code = char.charCodeAt(0), string = ''; @@ -320,7 +332,7 @@ Ox.decodeUTF8 Decodes an UTF-8-encoded string > Ox.decodeUTF8('\u00C2\u00A5\u00E2\u0082\u00AC\u0024') '¥€$' @*/ -Ox.decodeUTF8 = function(string) { +export function decodeUTF8(string) { var code, i = 0, length = string.length, ret = ''; function error(byte, position) { throw new RangeError( diff --git a/source/Ox/js/Format.js b/source/Ox/js/Format.js index 6870c75b..82c2292d 100644 --- a/source/Ox/js/Format.js +++ b/source/Ox/js/Format.js @@ -1,5 +1,17 @@ 'use strict'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; + +const Ox = {}; + +Object.assign(Ox, + OxObject, + OxConstants, + OxMath +); + /*@ Ox.formatArea Formats a number of meters as square meters or kilometers > Ox.formatArea(1000) @@ -8,7 +20,7 @@ Ox.formatArea Formats a number of meters as square meters or kilometers '1 km\u00B2' @*/ -Ox.formatArea = function(number, decimals) { +export function formatArea(number, decimals) { var k = number >= 1000000 ? 'k' : ''; decimals = Ox.isUndefined(decimals) ? 8 : decimals; return Ox.formatNumber( @@ -25,7 +37,7 @@ Ox.formatCount Returns a string like "2 items", "1 item" or "no items". > Ox.formatCount(1000, 'city', 'cities') '1,000 cities' @*/ -Ox.formatCount = function(number, singular, plural) { +export function formatCount(number, singular, plural) { plural = (plural || singular + 's') + (number === 2 ? '{2}' : ''); return (number === 0 ? Ox._('no') : Ox.formatNumber(number)) + ' ' + Ox._(number === 1 ? singular : plural); @@ -36,7 +48,7 @@ Ox.formatCurrency Formats a number with a currency symbol > Ox.formatCurrency(1000, '$', 2) '$1,000.00' @*/ -Ox.formatCurrency = function(number, string, decimals) { +export function formatCurrency(number, string, decimals) { return string + Ox.formatNumber(number, decimals); }; @@ -397,7 +409,7 @@ Ox.formatDateRange Formats a date range as a string > Ox.formatDateRange('-50-01-01 00:00:00', '-50-01-01 23:59:59') 'Sun, Jan 1, 50 BC, 00:00:00 - 23:59:59' @*/ -Ox.formatDateRange = function(start, end, utc) { +export function formatDateRange(start, end, utc) { end = end || Ox.formatDate(new Date(), '%Y-%m-%d'); var isOneUnit = false, range = [start, end], @@ -487,7 +499,7 @@ Ox.formatDateRangeDuration Formats the duration of a date range as a string > Ox.formatDateRangeDuration('2000-02', '2000-03', true) '1 month' @*/ -Ox.formatDateRangeDuration = function(start, end, utc) { +export function formatDateRangeDuration(start, end, utc) { end = end || Ox.formatDate(new Date(), '%Y-%m-%d'); var date = Ox.parseDate(start, utc), dates = [start, end].map(function(string) { @@ -535,7 +547,7 @@ Ox.formatDegrees Formats degrees as D°MM'SS" > Ox.formatDegrees(-111.11, 'lng') "111°06'36\"W" @*/ -Ox.formatDegrees = function(degrees, mode) { +export function formatDegrees(degrees, mode) { var days = 0, seconds = Math.round(Math.abs(degrees) * 3600), sign = degrees < 0 ? '-' : '', @@ -558,11 +570,13 @@ Ox.formatDimensions Formats valus as dimension > Ox.formatDimensions([1920, 1080], 'px') "1,920 × 1,080 px" @*/ -Ox.formatDimensions = Ox.formatResolution = function(array, string) { +export function formatDimensions(array, string) { return array.map(function(value) { return Ox.formatNumber(value); }).join(' × ') + (string ? ' ' + string : ''); }; +export const formatResolution = formatDimensions; + /*@ Ox.formatDuration Formats a duration as a string @@ -595,7 +609,7 @@ Ox.formatDuration Formats a duration as a string > Ox.formatDuration(0, 'long') '' @*/ -Ox.formatDuration = function(seconds/*, decimals, format*/) { +export function formatDuration(seconds/*, decimals, format*/) { var last = Ox.last(arguments), format = last == 'short' || last == 'long' ? last : 'none', decimals = Ox.isNumber(arguments[1]) ? arguments[1] : 0, @@ -649,7 +663,7 @@ Ox.formatISBN Formats a string as an ISBN of a given length (10 or 13) > Ox.formatISBN('978-0-306-40615-7', 10) '0306406152' @*/ -Ox.formatISBN = function(isbn, length, dashes) { +export function formatISBN(isbn, length, dashes) { var ret = ''; function getCheckDigit(isbn) { var mod = isbn.length == 10 ? 11 : 10 @@ -697,7 +711,7 @@ Ox.formatNumber Formats a number with thousands separators > Ox.formatNumber(666666.666) "666,667" @*/ -Ox.formatNumber = function(number, decimals) { +export function formatNumber(number, decimals) { var array = [], abs = Math.abs(number), split = abs.toFixed(decimals).split('.'); @@ -726,7 +740,7 @@ Ox.formatOrdinal Formats a number as an ordinal > Ox.formatOrdinal(13) "13th" @*/ -Ox.formatOrdinal = function(number) { +export function formatOrdinal(number) { var string = Ox.formatNumber(number), length = string.length, last = string[length - 1], @@ -751,7 +765,7 @@ Ox.formatPercent Formats the relation of two numbers as a percentage > Ox.formatPercent(1, 1000, 2) "0.10%" @*/ -Ox.formatPercent = function(number, total, decimals) { +export function formatPercent(number, total, decimals) { return Ox.formatNumber(number / total * 100, decimals) + Ox._('%'); }; @@ -772,7 +786,7 @@ Ox.formatRoman Formats a number as a roman numeral > Ox.formatRoman(10000) 'MMMMMMMMMM' @*/ -Ox.formatRoman = function(number) { +export function formatRoman(number) { var string = ''; Ox.forEach({ M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90, @@ -789,7 +803,7 @@ Ox.formatRoman = function(number) { /*@ Ox.formatSRT Formats subtitles as SRT @*/ -Ox.formatSRT = function(subtitles) { +export function formatSRT(subtitles) { return '\ufeff' + Ox.sortBy(subtitles, ['in', 'out']).map(function(subtitle, index) { return [ index + 1, @@ -816,7 +830,7 @@ Ox.formatString Basic string formatting > Ox.formatString('{b}', {a: 'foobar'}, true) '{b}' @*/ -Ox.formatString = function(string, collection, keepUnmatched) { +export function formatString(string, collection, keepUnmatched) { return string.replace(/\{([^}]+)\}/g, function(string, match) { // make sure to not split at escaped dots ('\.') var key, @@ -844,7 +858,7 @@ Ox.formatUnit Formats a number with a unit > Ox.formatUnit(100/3, '%') '33%' @*/ -Ox.formatUnit = function(number, string, decimals) { +export function formatUnit(number, string, decimals) { return Ox.formatNumber(number, decimals) + (/^[:%]/.test(string) ? '' : ' ') + string; }; @@ -859,7 +873,7 @@ Ox.formatValue Formats a numerical value "1.15 GiB" @*/ // fixme: is this the best name? -Ox.formatValue = function(number, string, bin) { +export function formatValue(number, string, bin) { var base = bin ? 1024 : 1000, length = Ox.PREFIXES.length, ret; diff --git a/source/Ox/js/Function.js b/source/Ox/js/Function.js index 5d2a41d1..5c70bb4c 100644 --- a/source/Ox/js/Function.js +++ b/source/Ox/js/Function.js @@ -1,5 +1,17 @@ 'use strict'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; + +const Ox = {}; + +Object.assign(Ox, + OxObject, + OxConstants, + OxMath +); + /*@ Ox.cache Memoize a function fn function @@ -15,7 +27,7 @@ Ox.cache Memoize a function false @*/ // TODO: add async test -Ox.cache = function(fn, options) { +export function cache(fn, options) { var cache = {}, ret; options = options || {}; options.async = options.async || false; @@ -65,7 +77,7 @@ Ox.debounce Runs a function once it stops being called for a given interval ms Interval in milliseconds immediate If true, function is called once immediately @*/ -Ox.debounce = function(fn/*, ms, immediate*/) { +export function debounce(fn/*, ms, immediate*/) { var args, immediate = Ox.last(arguments) === true, ms = Ox.isNumber(arguments[1]) ? arguments[1] : 250, @@ -95,7 +107,7 @@ Ox.identity Returns its first argument > Ox.identity(Infinity) Infinity @*/ -Ox.identity = function(value) { +export function identity(value) { return value; }; @@ -108,7 +120,7 @@ Ox.noop Returns undefined and calls optional callback without arguments > Ox.noop(1, 2, 3, function() { Ox.test(arguments.length, 0); }) undefined @*/ -Ox.noop = function() { +export function noop() { var callback = Ox.last(arguments); Ox.isFunction(callback) && callback(); }; @@ -118,7 +130,7 @@ Ox.once Runs a function once, and then never again (fn) -> Function that will run only once fn Function to run once @*/ -Ox.once = function(fn) { +export function once(fn) { var once = false; return function() { if (!once) { @@ -139,11 +151,11 @@ Ox.queue Queue of asynchronous function calls with cached results fn Queued function maxThreads Number of parallel function calls @*/ -Ox.queue = function(fn, maxThreads) { +export function queue(fn, maxThreads) { maxThreads = maxThreads || 10; var processing = [], queued = [], - ret = Ox.cache(function() { + ret = cache(function() { var args = Ox.slice(arguments); queued.push({args: args, key: getKey(args)}); process(); @@ -199,7 +211,7 @@ Ox.throttle Runs a function at most once per given interval fn Function to throttle ms Interval in milliseconds @*/ -Ox.throttle = function(fn, ms) { +export function throttle(fn, ms) { var args, timeout; ms = arguments.length == 1 ? 250 : ms; @@ -222,7 +234,7 @@ Ox.throttle = function(fn, ms) { Ox.time Returns the time it takes to execute a given function (fn) -> Time in milliseconds @*/ -Ox.time = function(fn) { +export function time(fn) { var time = new Date(); fn(); return new Date() - time; diff --git a/source/Ox/js/Geo.js b/source/Ox/js/Geo.js index aa341cf1..b635932d 100644 --- a/source/Ox/js/Geo.js +++ b/source/Ox/js/Geo.js @@ -1,480 +1,488 @@ 'use strict'; -(function() { +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; - // fixme: make all this work with different types of "points" - // i.e. {lat, lng}, [lat, lng] +const Ox = {}; - function deg(point) { - return Ox.map(point, function(val) { - return Ox.mod(Ox.deg(val) + 180, 360) - 180; - }); +Object.assign(Ox, + OxObject, + OxConstants, + OxMath +); + +// fixme: make all this work with different types of "points" +// i.e. {lat, lng}, [lat, lng] + +function deg(point) { + return Ox.map(point, function(val) { + return Ox.mod(Ox.deg(val) + 180, 360) - 180; + }); +} + +function rad(point) { + return Ox.map(point, function(val) { + return Ox.rad(val); + }); +} + +function splitArea(area) { + return Ox.crossesDateline(area.sw, area.ne) ? [ + {sw: area.sw, ne: {lat: area.ne.lat, lng: 180}}, + {sw: {lat: area.sw.lat, lng: -180}, ne: area.ne} + ] : [area]; +} + +/*@ +Ox.crossesDateline Returns true if a given line crosses the dateline + > Ox.crossesDateline({lat: 0, lng: -90}, {lat: 0, lng: 90}) + false + > Ox.crossesDateline({lat: 0, lng: 90}, {lat: 0, lng: -90}) + true +@*/ +// FIXME: argument should be {w: ..., e: ...} +export function crossesDateline(pointA, pointB) { + return pointA.lng > pointB.lng; +}; + +/*@ +Ox.getArea Returns the area in square meters of a given rectancle +@*/ +// FIXME: argument should be {sw: ..., ne: ...} +export function getArea(pointA, pointB) { + /* + area of a ring between two latitudes: + 2 * PI * r^2 * abs(sin(latA) - sin(latB)) + see http://mathforum.org/library/drmath/view/63767.html + => + 2 * Math.PI + * Math.pow(Ox.EARTH_RADIUS, 2) + * Math.abs(Math.sin(Ox.rad(latA)) - Math.sin(Ox.rad(latB))) + * Math.abs(Ox.rad(lngA) - Ox.rad(lngB)) + / (2 * Math.PI) + */ + if (Ox.crossesDateline(pointA, pointB)) { + pointB.lng += 360; } + pointA = rad(pointA); + pointB = rad(pointB); + return Math.pow(Ox.EARTH_RADIUS, 2) + * Math.abs(Math.sin(pointA.lat) - Math.sin(pointB.lat)) + * Math.abs(pointA.lng - pointB.lng); +}; - function rad(point) { - return Ox.map(point, function(val) { - return Ox.rad(val); - }); +/*@ +Ox.getAverageBearing Returns the average of two bearings + > Ox.getAverageBearing(0, 90) + 45 + > Ox.getAverageBearing(10, 350) + 0 +@*/ +// FIXME: find the proper name of this operation +// FIMXE: use in manhattan grid example +export function getAverageBearing(bearingA, bearingB) { + return Ox.mod((bearingA + bearingB) / 2 + ( + Math.abs(bearingA - bearingB) > 180 ? 180 : 0 + ), 360); +}; + +/*@ +Ox.getBearing Returns the bearing from one point to another + > Ox.getBearing({lat: -45, lng: 0}, {lat: 45, lng: 0}) + 0 + > Ox.getBearing({lat: 0, lng: -90}, {lat: 0, lng: 90}) + 90 +@*/ +export function getBearing(pointA, pointB) { + pointA = rad(pointA); + pointB = rad(pointB); + var x = Math.cos(pointA.lat) * Math.sin(pointB.lat) + - Math.sin(pointA.lat) * Math.cos(pointB.lat) + * Math.cos(pointB.lng - pointA.lng), + y = Math.sin(pointB.lng - pointA.lng) + * Math.cos(pointB.lat); + return (Ox.deg(Math.atan2(y, x)) + 360) % 360; +}; + +// FIXME: name, docs +export function getBearingDifference(bearingA, bearingB) { + var difference = Math.abs(bearingA - bearingB); + return difference > 180 ? 360 - difference : difference; +}; + +/*@ +Ox.getCenter Returns the center of a recangle on a spehre + > Ox.getCenter({lat: -45, lng: -90}, {lat: 45, lng: 90}) + {lat: 0, lng: 0} +@*/ +export function getCenter(pointA, pointB) { + pointA = rad(pointA); + pointB = rad(pointB); + var x = Math.cos(pointB.lat) + * Math.cos(pointB.lng - pointA.lng), + y = Math.cos(pointB.lat) + * Math.sin(pointB.lng - pointA.lng), + d = Math.sqrt( + Math.pow(Math.cos(pointA.lat) + x, 2) + Math.pow(y, 2) + ), + lat = Math.atan2(Math.sin(pointA.lat) + Math.sin(pointB.lat), d), + lng = pointA.lng + Math.atan2(y, Math.cos(pointA.lat) + x); + return deg({lat: lat, lng: lng}); +}; + +/*@ +Ox.getCircle Returns points on a circle around a given point + (center, radius, precision) -> Points + center Center point ({lat, lng}) + radius Radius in meters + precision Precision (the circle will have 2^precision segments) +@*/ +export function getCircle(center, radius, precision) { + return Ox.range( + 0, 360, 360 / Math.pow(2, precision) + ).map(function(bearing) { + return Ox.getPoint(center, radius, bearing); + }); +}; + +// FIXME: name, docs +export function getClosestBearing(bearing, bearings) { + var differences = bearings.map(function(bearing_) { + return getBearingDifference(bearing, bearing_); + }); + return bearings[differences.indexOf(Ox.min(differences))]; +}; + +/*@ +Ox.getDegreesPerMeter Returns degrees per meter at a given latitude + > 360 / Ox.getDegreesPerMeter(0) + Ox.EARTH_CIRCUMFERENCE +@*/ +export function getDegreesPerMeter(lat) { + return 360 / Ox.EARTH_CIRCUMFERENCE / Math.cos(lat * Math.PI / 180); +}; + +/*@ +Ox.getDistance Returns the distance in meters between two points + > Ox.getDistance({lat: -45, lng: -90}, {lat: 45, lng: 90}) * 2 + Ox.EARTH_CIRCUMFERENCE +@*/ +export function getDistance(pointA, pointB) { + pointA = rad(pointA); + pointB = rad(pointB); + return Math.acos( + Math.sin(pointA.lat) * Math.sin(pointB.lat) + + Math.cos(pointA.lat) * Math.cos(pointB.lat) + * Math.cos(pointB.lng - pointA.lng) + ) * Ox.EARTH_RADIUS; +}; + +/*@ +Ox.getLatLngByXY Returns lat/lng for a given x/y on a 1x1 mercator projection + > Ox.getLatLngByXY({x: 0.5, y: 0.5}) + {lat: -0, lng: 0} +@*/ +export function getLatLngByXY(xy) { + function getValue(value) { + return (value - 0.5) * 2 * Math.PI; } + return { + lat: -Ox.deg(Math.atan(Ox.sinh(getValue(xy.y)))), + lng: Ox.deg(getValue(xy.x)) + }; +}; - function splitArea(area) { - return Ox.crossesDateline(area.sw, area.ne) ? [ - {sw: area.sw, ne: {lat: area.ne.lat, lng: 180}}, - {sw: {lat: area.sw.lat, lng: -180}, ne: area.ne} - ] : [area]; +/*@ +Ox.getLine Returns points on a line between two points + (pointA, pointB, precision) -> Points + pointA Start point ({lat, lng}) + pointB End point ({lat, lng}) + precision Precision (the line will have 2^precision segments) +@*/ +export function getLine(pointA, pointB, precision) { + var line = [pointA, pointB], points; + while (precision > 0) { + points = [line[0]]; + Ox.loop(line.length - 1, function(i) { + points.push( + Ox.getCenter(line[i], line[i + 1]), + line[i + 1] + ); + }); + line = points; + precision--; } + return line; +}; - /*@ - Ox.crossesDateline Returns true if a given line crosses the dateline - > Ox.crossesDateline({lat: 0, lng: -90}, {lat: 0, lng: 90}) - false - > Ox.crossesDateline({lat: 0, lng: 90}, {lat: 0, lng: -90}) - true - @*/ - // FIXME: argument should be {w: ..., e: ...} - Ox.crossesDateline = function(pointA, pointB) { - return pointA.lng > pointB.lng; +/*@ +Ox.getMetersPerDegree Returns meters per degree at a given latitude + > Ox.getMetersPerDegree(0) * 360 + Ox.EARTH_CIRCUMFERENCE +@*/ +export function getMetersPerDegree(lat) { + return Math.cos(lat * Math.PI / 180) * Ox.EARTH_CIRCUMFERENCE / 360; +}; + +/*@ +Ox.getPoint Returns a point at a given distance/bearing from a given point + > Ox.getPoint({lat: -45, lng: 0}, Ox.EARTH_CIRCUMFERENCE / 4, 0) + {lat: 45, lng: 0} +@*/ +export function getPoint(point, distance, bearing) { + var pointB = {}; + point = rad(point); + distance /= Ox.EARTH_RADIUS; + bearing = Ox.rad(bearing); + pointB.lat = Math.asin( + Math.sin(point.lat) * Math.cos(distance) + + Math.cos(point.lat) * Math.sin(distance) * Math.cos(bearing) + ); + pointB.lng = point.lng + Math.atan2( + Math.sin(bearing) * Math.sin(distance) * Math.cos(point.lat), + Math.cos(distance) - Math.sin(point.lat) * Math.sin(pointB.lat) + ); + return deg(pointB); +}; + +/*@ +Ox.getXYByLatLng Returns x/y on a 1x1 mercator projection for a given lat/lng + > Ox.getXYByLatLng({lat: 0, lng: 0}) + {x: 0.5, y: 0.5} +@*/ +export function getXYByLatLng(latlng) { + function getValue(value) { + return value / (2 * Math.PI) + 0.5; + } + return { + x: getValue(Ox.rad(latlng.lng)), + y: getValue(Ox.asinh(Math.tan(Ox.rad(-latlng.lat)))) }; +}; - /*@ - Ox.getArea Returns the area in square meters of a given rectancle - @*/ - // FIXME: argument should be {sw: ..., ne: ...} - Ox.getArea = function(pointA, pointB) { - /* - area of a ring between two latitudes: - 2 * PI * r^2 * abs(sin(latA) - sin(latB)) - see http://mathforum.org/library/drmath/view/63767.html - => - 2 * Math.PI - * Math.pow(Ox.EARTH_RADIUS, 2) - * Math.abs(Math.sin(Ox.rad(latA)) - Math.sin(Ox.rad(latB))) - * Math.abs(Ox.rad(lngA) - Ox.rad(lngB)) - / (2 * Math.PI) - */ - if (Ox.crossesDateline(pointA, pointB)) { - pointB.lng += 360; - } - pointA = rad(pointA); - pointB = rad(pointB); - return Math.pow(Ox.EARTH_RADIUS, 2) - * Math.abs(Math.sin(pointA.lat) - Math.sin(pointB.lat)) - * Math.abs(pointA.lng - pointB.lng); - }; +/*@ +Ox.isPolar Returns true if a given point is outside the bounds of a mercator projection + > Ox.isPolar({lat: 90, lng: 0}) + true +@*/ +export function isPolar(point) { + return point.lat < Ox.MIN_LATITUDE || point.lat > Ox.MAX_LATITUDE; +}; - /*@ - Ox.getAverageBearing Returns the average of two bearings - > Ox.getAverageBearing(0, 90) - 45 - > Ox.getAverageBearing(10, 350) - 0 - @*/ - // FIXME: find the proper name of this operation - // FIMXE: use in manhattan grid example - Ox.getAverageBearing = function(bearingA, bearingB) { - return Ox.mod((bearingA + bearingB) / 2 + ( - Math.abs(bearingA - bearingB) > 180 ? 180 : 0 - ), 360); - }; - - /*@ - Ox.getBearing Returns the bearing from one point to another - > Ox.getBearing({lat: -45, lng: 0}, {lat: 45, lng: 0}) - 0 - > Ox.getBearing({lat: 0, lng: -90}, {lat: 0, lng: 90}) - 90 - @*/ - Ox.getBearing = function(pointA, pointB) { - pointA = rad(pointA); - pointB = rad(pointB); - var x = Math.cos(pointA.lat) * Math.sin(pointB.lat) - - Math.sin(pointA.lat) * Math.cos(pointB.lat) - * Math.cos(pointB.lng - pointA.lng), - y = Math.sin(pointB.lng - pointA.lng) - * Math.cos(pointB.lat); - return (Ox.deg(Math.atan2(y, x)) + 360) % 360; - }; - - // FIXME: name, docs - Ox.getBearingDifference = function(bearingA, bearingB) { - var difference = Math.abs(bearingA - bearingB); - return difference > 180 ? 360 - difference : difference; - }; - - /*@ - Ox.getCenter Returns the center of a recangle on a spehre - > Ox.getCenter({lat: -45, lng: -90}, {lat: 45, lng: 90}) - {lat: 0, lng: 0} - @*/ - Ox.getCenter = function(pointA, pointB) { - pointA = rad(pointA); - pointB = rad(pointB); - var x = Math.cos(pointB.lat) - * Math.cos(pointB.lng - pointA.lng), - y = Math.cos(pointB.lat) - * Math.sin(pointB.lng - pointA.lng), - d = Math.sqrt( - Math.pow(Math.cos(pointA.lat) + x, 2) + Math.pow(y, 2) - ), - lat = Math.atan2(Math.sin(pointA.lat) + Math.sin(pointB.lat), d), - lng = pointA.lng + Math.atan2(y, Math.cos(pointA.lat) + x); - return deg({lat: lat, lng: lng}); - }; - - /*@ - Ox.getCircle Returns points on a circle around a given point - (center, radius, precision) -> Points - center Center point ({lat, lng}) - radius Radius in meters - precision Precision (the circle will have 2^precision segments) - @*/ - Ox.getCircle = function(center, radius, precision) { - return Ox.range( - 0, 360, 360 / Math.pow(2, precision) - ).map(function(bearing) { - return Ox.getPoint(center, radius, bearing); - }); - }; - - // FIXME: name, docs - Ox.getClosestBearing = function(bearing, bearings) { - var differences = bearings.map(function(bearing_) { - return getBearingDifference(bearing, bearing_); - }); - return bearings[differences.indexOf(Ox.min(differences))]; - }; - - /*@ - Ox.getDegreesPerMeter Returns degrees per meter at a given latitude - > 360 / Ox.getDegreesPerMeter(0) - Ox.EARTH_CIRCUMFERENCE - @*/ - Ox.getDegreesPerMeter = function(lat) { - return 360 / Ox.EARTH_CIRCUMFERENCE / Math.cos(lat * Math.PI / 180); - }; - - /*@ - Ox.getDistance Returns the distance in meters between two points - > Ox.getDistance({lat: -45, lng: -90}, {lat: 45, lng: 90}) * 2 - Ox.EARTH_CIRCUMFERENCE - @*/ - Ox.getDistance = function(pointA, pointB) { - pointA = rad(pointA); - pointB = rad(pointB); - return Math.acos( - Math.sin(pointA.lat) * Math.sin(pointB.lat) - + Math.cos(pointA.lat) * Math.cos(pointB.lat) - * Math.cos(pointB.lng - pointA.lng) - ) * Ox.EARTH_RADIUS; - }; - - /*@ - Ox.getLatLngByXY Returns lat/lng for a given x/y on a 1x1 mercator projection - > Ox.getLatLngByXY({x: 0.5, y: 0.5}) - {lat: -0, lng: 0} - @*/ - Ox.getLatLngByXY = function(xy) { - function getValue(value) { - return (value - 0.5) * 2 * Math.PI; - } - return { - lat: -Ox.deg(Math.atan(Ox.sinh(getValue(xy.y)))), - lng: Ox.deg(getValue(xy.x)) - }; - }; - - /*@ - Ox.getLine Returns points on a line between two points - (pointA, pointB, precision) -> Points - pointA Start point ({lat, lng}) - pointB End point ({lat, lng}) - precision Precision (the line will have 2^precision segments) - @*/ - Ox.getLine = function(pointA, pointB, precision) { - var line = [pointA, pointB], points; - while (precision > 0) { - points = [line[0]]; - Ox.loop(line.length - 1, function(i) { - points.push( - Ox.getCenter(line[i], line[i + 1]), - line[i + 1] - ); - }); - line = points; - precision--; - } - return line; - }; - - /*@ - Ox.getMetersPerDegree Returns meters per degree at a given latitude - > Ox.getMetersPerDegree(0) * 360 - Ox.EARTH_CIRCUMFERENCE - @*/ - Ox.getMetersPerDegree = function(lat) { - return Math.cos(lat * Math.PI / 180) * Ox.EARTH_CIRCUMFERENCE / 360; - }; - - /*@ - Ox.getPoint Returns a point at a given distance/bearing from a given point - > Ox.getPoint({lat: -45, lng: 0}, Ox.EARTH_CIRCUMFERENCE / 4, 0) - {lat: 45, lng: 0} - @*/ - Ox.getPoint = function(point, distance, bearing) { - var pointB = {}; - point = rad(point); - distance /= Ox.EARTH_RADIUS; - bearing = Ox.rad(bearing); - pointB.lat = Math.asin( - Math.sin(point.lat) * Math.cos(distance) - + Math.cos(point.lat) * Math.sin(distance) * Math.cos(bearing) - ); - pointB.lng = point.lng + Math.atan2( - Math.sin(bearing) * Math.sin(distance) * Math.cos(point.lat), - Math.cos(distance) - Math.sin(point.lat) * Math.sin(pointB.lat) - ); - return deg(pointB); - }; - - /*@ - Ox.getXYByLatLng Returns x/y on a 1x1 mercator projection for a given lat/lng - > Ox.getXYByLatLng({lat: 0, lng: 0}) - {x: 0.5, y: 0.5} - @*/ - Ox.getXYByLatLng = function(latlng) { - function getValue(value) { - return value / (2 * Math.PI) + 0.5; - } - return { - x: getValue(Ox.rad(latlng.lng)), - y: getValue(Ox.asinh(Math.tan(Ox.rad(-latlng.lat)))) - }; - }; - - /*@ - Ox.isPolar Returns true if a given point is outside the bounds of a mercator projection - > Ox.isPolar({lat: 90, lng: 0}) - true - @*/ - Ox.isPolar = function(point) { - return point.lat < Ox.MIN_LATITUDE || point.lat > Ox.MAX_LATITUDE; - }; - - /*@ - Ox.containsArea Returns true if an area contains another area - - > Ox.containsArea(Ox.test.areas[0], Ox.test.areas[1]) - false - > Ox.containsArea(Ox.test.areas[2], Ox.test.areas[3]) - true - @*/ - // FIXME: Shouldn't this be rewritten as a test - // if the intersection is equal to the inner area? - Ox.containsArea = function(areaA, areaB) { - // If an area crosses the dateline, - // we split it into two parts, - // west and east of the dateline - var areas = [areaA, areaB].map(splitArea), ret; - function contains(areaA, areaB) { - return areaA.sw.lat <= areaB.sw.lat - && areaA.sw.lng <= areaB.sw.lng - && areaA.ne.lat >= areaB.ne.lat - && areaA.ne.lng >= areaB.ne.lng; - } - // For each part of the inner area, test if it - // is contained in any part of the outer area - Ox.forEach(areas[1], function(area1) { - Ox.forEach(areas[0], function(area0) { - ret = contains(area0, area1); - // Break if the outer part contains the inner part - return !ret; - }); - // Break if no outer part contains the inner part - return ret; +/*@ +Ox.containsArea Returns true if an area contains another area + + > Ox.containsArea(Ox.test.areas[0], Ox.test.areas[1]) + false + > Ox.containsArea(Ox.test.areas[2], Ox.test.areas[3]) + true +@*/ +// FIXME: Shouldn't this be rewritten as a test +// if the intersection is equal to the inner area? +export function containsArea(areaA, areaB) { + // If an area crosses the dateline, + // we split it into two parts, + // west and east of the dateline + var areas = [areaA, areaB].map(splitArea), ret; + function contains(areaA, areaB) { + return areaA.sw.lat <= areaB.sw.lat + && areaA.sw.lng <= areaB.sw.lng + && areaA.ne.lat >= areaB.ne.lat + && areaA.ne.lng >= areaB.ne.lng; + } + // For each part of the inner area, test if it + // is contained in any part of the outer area + Ox.forEach(areas[1], function(area1) { + Ox.forEach(areas[0], function(area0) { + ret = contains(area0, area1); + // Break if the outer part contains the inner part + return !ret; }); + // Break if no outer part contains the inner part return ret; - }; + }); + return ret; +}; - /*@ - Ox.intersectAreas Returns the intersection of two areas, or null - - > Ox.intersectAreas([Ox.test.areas[0], Ox.test.areas[1]]) - {sw: {lat: 0, lng: 0}, ne: {lat: 0, lng: 0}} - > Ox.intersectAreas([Ox.test.areas[2], Ox.test.areas[3]]) - {sw: {lat: 25, lng: -155}, ne: {lat: 30, lng: -150}} - @*/ - // FIXME: handle the a corner case where - // two areas have two intersections - Ox.intersectAreas = function(areas) { - var intersections, ret; - // If an area crosses the dateline, - // we split it into two parts, - // west and east of the dateline - areas = areas.map(splitArea); - ret = areas[0]; - function intersect(areaA, areaB) { - return areaA.sw.lat > areaB.ne.lat - || areaA.sw.lng > areaB.ne.lng - || areaA.ne.lat < areaB.sw.lat - || areaA.ne.lng < areaB.sw.lng - ? null : { - sw: { - lat: Math.max(areaA.sw.lat, areaB.sw.lat), - lng: Math.max(areaA.sw.lng, areaB.sw.lng) - }, - ne: { - lat: Math.min(areaA.ne.lat, areaB.ne.lat), - lng: Math.min(areaA.ne.lng, areaB.ne.lng) - } - }; - } - Ox.forEach(areas.slice(1), function(parts) { - if (ret.length == 1 && parts.length == 1) { - ret = intersect(ret[0], parts[0]); - } else { - // intersect each part of the intersection - // with all parts of the next area - intersections = Ox.compact(ret.map(function(part) { - return Ox.intersectAreas(parts.concat(part)); - })); - ret = intersections.length == 0 ? null - : Ox.joinAreas(intersections); +/*@ +Ox.intersectAreas Returns the intersection of two areas, or null + + > Ox.intersectAreas([Ox.test.areas[0], Ox.test.areas[1]]) + {sw: {lat: 0, lng: 0}, ne: {lat: 0, lng: 0}} + > Ox.intersectAreas([Ox.test.areas[2], Ox.test.areas[3]]) + {sw: {lat: 25, lng: -155}, ne: {lat: 30, lng: -150}} +@*/ +// FIXME: handle the a corner case where +// two areas have two intersections +export function intersectAreas(areas) { + var intersections, ret; + // If an area crosses the dateline, + // we split it into two parts, + // west and east of the dateline + areas = areas.map(splitArea); + ret = areas[0]; + function intersect(areaA, areaB) { + return areaA.sw.lat > areaB.ne.lat + || areaA.sw.lng > areaB.ne.lng + || areaA.ne.lat < areaB.sw.lat + || areaA.ne.lng < areaB.sw.lng + ? null : { + sw: { + lat: Math.max(areaA.sw.lat, areaB.sw.lat), + lng: Math.max(areaA.sw.lng, areaB.sw.lng) + }, + ne: { + lat: Math.min(areaA.ne.lat, areaB.ne.lat), + lng: Math.min(areaA.ne.lng, areaB.ne.lng) } - if (ret === null) { - return false; // break - } else { - ret = splitArea(ret); - } - }); - return ret ? Ox.joinAreas(ret) : null; - }; - - /*@ - Ox.joinAreas Joins an array of areas - - > Ox.joinAreas(Ox.test.areas) - {sw: {lat: -30, lng: 150}, ne: {lat: 30, lng: -150}} - @*/ - Ox.joinAreas = function(areas) { - // While the combined latitude is trivial (min to max), the combined longitude - // spans from the eastern to the western edge of the largest gap between areas - var ret = areas[0], - gaps = [{ - sw: {lat: -90, lng: ret.ne.lng}, - ne: {lat: 90, lng: ret.sw.lng} - }]; - function containsGaps(area) { - return Ox.getIndices(gaps, function(gap) { - return Ox.containsArea({ - sw: {lat: -90, lng: area.sw.lng}, - ne: {lat: 90, lng: area.ne.lng} - }, gap); - }); - } - function intersectsWithGaps(area) { - var ret = {}; - gaps.forEach(function(gap, i) { - var intersection = Ox.intersectAreas([area, gap]); - if (intersection) { - ret[i] = intersection; - } - }); - return ret; - } - function isContainedInGap(area) { - var ret = -1; - Ox.forEach(gaps, function(gap, i) { - if (Ox.containsArea(gap, area)) { - ret = i; - return false; // break - } - }); - return ret; - } - areas.slice(1).forEach(function(area) { - var index, indices, intersections; - if (area.sw.lat < ret.sw.lat) { - ret.sw.lat = area.sw.lat; - } - if (area.ne.lat > ret.ne.lat) { - ret.ne.lat = area.ne.lat; - } - // If the area is contained in a gap, split the gap in two - index = isContainedInGap(area); - if (index > -1) { - gaps.push({ - sw: gaps[index].sw, - ne: {lat: 90, lng: area.sw.lng} - }); - gaps.push({ - sw: {lat: -90, lng: area.ne.lng}, - ne: gaps[index].ne - }); - gaps.splice(index, 1); - } else { - // If the area contains gaps, remove them - indices = containsGaps(area); - Ox.reverse(indices).forEach(function(index) { - gaps.splice(index, 1); - }); - // If the area intersects with gaps, shrink them - intersections = intersectsWithGaps(area); - Ox.forEach(intersections, function(intersection, index) { - gaps[index] = { - sw: { - lat: -90, - lng: gaps[index].sw.lng == intersection.sw.lng - ? intersection.ne.lng : gaps[index].sw.lng - }, - ne: { - lat: 90, - lng: gaps[index].ne.lng == intersection.ne.lng - ? intersection.sw.lng : gaps[index].ne.lng - } - }; - }); - } - }); - if (gaps.length == 0) { - ret.sw.lng = -180; - ret.ne.lng = 180; + }; + } + Ox.forEach(areas.slice(1), function(parts) { + if (ret.length == 1 && parts.length == 1) { + ret = intersect(ret[0], parts[0]); } else { - gaps.sort(function(a, b) { - return ( - b.ne.lng - + (Ox.crossesDateline(b.sw, b.ne) ? 360 : 0) - - b.sw.lng - ) - ( - a.ne.lng - + (Ox.crossesDateline(a.sw, a.ne) ? 360 : 0) - - a.sw.lng - ); - }); - ret.sw.lng = gaps[0].ne.lng; - ret.ne.lng = gaps[0].sw.lng; + // intersect each part of the intersection + // with all parts of the next area + intersections = Ox.compact(ret.map(function(part) { + return Ox.intersectAreas(parts.concat(part)); + })); + ret = intersections.length == 0 ? null + : Ox.joinAreas(intersections); } + if (ret === null) { + return false; // break + } else { + ret = splitArea(ret); + } + }); + return ret ? Ox.joinAreas(ret) : null; +}; + +/*@ +Ox.joinAreas Joins an array of areas + + > Ox.joinAreas(Ox.test.areas) + {sw: {lat: -30, lng: 150}, ne: {lat: 30, lng: -150}} +@*/ +export function joinAreas(areas) { + // While the combined latitude is trivial (min to max), the combined longitude + // spans from the eastern to the western edge of the largest gap between areas + var ret = areas[0], + gaps = [{ + sw: {lat: -90, lng: ret.ne.lng}, + ne: {lat: 90, lng: ret.sw.lng} + }]; + function containsGaps(area) { + return Ox.getIndices(gaps, function(gap) { + return Ox.containsArea({ + sw: {lat: -90, lng: area.sw.lng}, + ne: {lat: 90, lng: area.ne.lng} + }, gap); + }); + } + function intersectsWithGaps(area) { + var ret = {}; + gaps.forEach(function(gap, i) { + var intersection = Ox.intersectAreas([area, gap]); + if (intersection) { + ret[i] = intersection; + } + }); return ret; - }; - -}()); + } + function isContainedInGap(area) { + var ret = -1; + Ox.forEach(gaps, function(gap, i) { + if (Ox.containsArea(gap, area)) { + ret = i; + return false; // break + } + }); + return ret; + } + areas.slice(1).forEach(function(area) { + var index, indices, intersections; + if (area.sw.lat < ret.sw.lat) { + ret.sw.lat = area.sw.lat; + } + if (area.ne.lat > ret.ne.lat) { + ret.ne.lat = area.ne.lat; + } + // If the area is contained in a gap, split the gap in two + index = isContainedInGap(area); + if (index > -1) { + gaps.push({ + sw: gaps[index].sw, + ne: {lat: 90, lng: area.sw.lng} + }); + gaps.push({ + sw: {lat: -90, lng: area.ne.lng}, + ne: gaps[index].ne + }); + gaps.splice(index, 1); + } else { + // If the area contains gaps, remove them + indices = containsGaps(area); + Ox.reverse(indices).forEach(function(index) { + gaps.splice(index, 1); + }); + // If the area intersects with gaps, shrink them + intersections = intersectsWithGaps(area); + Ox.forEach(intersections, function(intersection, index) { + gaps[index] = { + sw: { + lat: -90, + lng: gaps[index].sw.lng == intersection.sw.lng + ? intersection.ne.lng : gaps[index].sw.lng + }, + ne: { + lat: 90, + lng: gaps[index].ne.lng == intersection.ne.lng + ? intersection.sw.lng : gaps[index].ne.lng + } + }; + }); + } + }); + if (gaps.length == 0) { + ret.sw.lng = -180; + ret.ne.lng = 180; + } else { + gaps.sort(function(a, b) { + return ( + b.ne.lng + + (Ox.crossesDateline(b.sw, b.ne) ? 360 : 0) + - b.sw.lng + ) - ( + a.ne.lng + + (Ox.crossesDateline(a.sw, a.ne) ? 360 : 0) + - a.sw.lng + ); + }); + ret.sw.lng = gaps[0].ne.lng; + ret.ne.lng = gaps[0].sw.lng; + } + return ret; +}; diff --git a/source/Ox/js/HTML.js b/source/Ox/js/HTML.js index b5cf839b..ca1da093 100644 --- a/source/Ox/js/HTML.js +++ b/source/Ox/js/HTML.js @@ -1,668 +1,682 @@ 'use strict'; -(function() { +import * as OxArray from './Array.js'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; +import * as OxString from './String.js'; - var defaultTags = [ - // inline formatting - {'name': 'b'}, - {'name': 'bdi'}, - {'name': 'code'}, - {'name': 'em'}, - {'name': 'i'}, - {'name': 'q'}, - {'name': 's'}, - {'name': 'span'}, - {'name': 'strong'}, - {'name': 'sub'}, - {'name': 'sup'}, - {'name': 'u'}, - // block formatting - {'name': 'blockquote'}, - {'name': 'cite'}, - { - 'name': 'div', - 'optional': ['style'], - 'validate': { - 'style': /^direction: rtl$/ - } - }, - {'name': 'h1'}, - {'name': 'h2'}, - {'name': 'h3'}, - {'name': 'h4'}, - {'name': 'h5'}, - {'name': 'h6'}, - {'name': 'p'}, - {'name': 'pre'}, - // lists - {'name': 'li'}, - {'name': 'ol'}, - {'name': 'ul'}, - // definition lists - {'name': 'dl'}, - {'name': 'dt'}, - {'name': 'dd'}, - // tables - {'name': 'table'}, - {'name': 'tbody'}, - {'name': 'td'}, - {'name': 'tfoot'}, - {'name': 'th'}, - {'name': 'thead'}, - {'name': 'tr'}, - // other - {'name': '[]'}, - { - 'name': 'a', - 'required': ['href'], - 'optional': ['target'], - 'validate': { - 'href': /^((https?:\/\/|\/|mailto:).*?)/, - 'target': /^_blank$/ - } - }, - {'name': 'br'}, - { - 'name': 'iframe', - 'optional': ['width', 'height'], - 'required': ['src'], - 'validate': { - 'width': /^\d+$/, - 'height': /^\d+$/, - 'src': /^((https?:\/\/|\/).*?)/ - } - }, - { - 'name': 'img', - 'optional': ['width', 'height'], - 'required': ['src'], - 'validate': { - 'width': /^\d+$/, - 'height': /^\d+$/, - 'src': /^((https?:\/\/|\/).*?)/ - }, - }, - {'name': 'figure'}, - {'name': 'figcaption'} - ], - htmlEntities = { - '"': '"', '&': '&', "'": ''', '<': '<', '>': '>' - }, - regexp = { - entity: /&[^\s]+?;/g, - html: /[<&]/, - tag: new RegExp('<\\/?(' + [ - 'a', 'b', 'br', 'code', 'i', 's', 'span', 'u' - ].join('|') + ')\\/?>', 'gi') - }, - salt = Ox.range(2).map(function(){ - return Ox.range(16).map(function() { - return Ox.char(65 + Ox.random(26)); - }).join(''); - }); +const Ox = {}; - function addLinks(string, obfuscate) { - return string - .replace( - /\b((https?:\/\/|www\.).+?)([.,:;!?)\]]*?(\s|$))/gi, - function(match, url, prefix, end) { - prefix = prefix.toLowerCase() == 'www.' ? 'http://' : ''; - return Ox.formatString( - '{url}{end}', - {end: end, prefix: prefix, url: url} - ); - } - ) - .replace( - /\b([0-9A-Z.+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6})\b/gi, - obfuscate ? function(match, mail) { - return Ox.encodeEmailAddress(mail); - } : '$1' - ); - } +Object.assign(Ox, + OxArray, + OxObject, + OxConstants, + OxMath, + OxString, +); - function decodeHTMLEntities(string) { - return string - .replace( - new RegExp('(' + Ox.values(htmlEntities).join('|') + ')', 'g'), - function(match) { - return Ox.keyOf(htmlEntities, match); - } - ) - .replace( - /&#([0-9A-FX]+);/gi, - function(match, code) { - return Ox.char( - /^X/i.test(code) - ? parseInt(code.slice(1), 16) - : parseInt(code, 10) - ); - } - ); - } - // Splits a string into text (even indices) and tags (odd indices), ignoring - // tags with starting positions that are included in the ignore array - function splitHTMLTags(string, ignore) { - var isTag = false, ret = ['']; - ignore = ignore || []; - Ox.forEach(string, function(char, i) { - if (!isTag && char == '<' && ignore.indexOf(i) == -1) { - isTag = true; - ret.push(''); +var defaultTags = [ + // inline formatting + {'name': 'b'}, + {'name': 'bdi'}, + {'name': 'code'}, + {'name': 'em'}, + {'name': 'i'}, + {'name': 'q'}, + {'name': 's'}, + {'name': 'span'}, + {'name': 'strong'}, + {'name': 'sub'}, + {'name': 'sup'}, + {'name': 'u'}, + // block formatting + {'name': 'blockquote'}, + {'name': 'cite'}, + { + 'name': 'div', + 'optional': ['style'], + 'validate': { + 'style': /^direction: rtl$/ } - ret[ret.length - 1] += char; - if (isTag && char == '>') { - isTag = false; - ret.push(''); + }, + {'name': 'h1'}, + {'name': 'h2'}, + {'name': 'h3'}, + {'name': 'h4'}, + {'name': 'h5'}, + {'name': 'h6'}, + {'name': 'p'}, + {'name': 'pre'}, + // lists + {'name': 'li'}, + {'name': 'ol'}, + {'name': 'ul'}, + // definition lists + {'name': 'dl'}, + {'name': 'dt'}, + {'name': 'dd'}, + // tables + {'name': 'table'}, + {'name': 'tbody'}, + {'name': 'td'}, + {'name': 'tfoot'}, + {'name': 'th'}, + {'name': 'thead'}, + {'name': 'tr'}, + // other + {'name': '[]'}, + { + 'name': 'a', + 'required': ['href'], + 'optional': ['target'], + 'validate': { + 'href': /^((https?:\/\/|\/|mailto:).*?)/, + 'target': /^_blank$/ } - }); - return ret; - } - - /*@ - Ox.addLinks Takes a string and adds links for e-mail addresses and URLs - (string[, isHTML]) -> Formatted string - string String - isHTML If true, ignore matches in tags or enclosed by links - > Ox.addLinks('foo bar ') - 'foo bar <foo@bar.com>' - > Ox.addLinks('www.foo.com/bar#baz, etc.') - 'www.foo.com/bar#baz, etc.' - > Ox.addLinks('www.foo.com', true) - 'www.foo.com' - @*/ - Ox.addLinks = function(string, isHTML) { - var isLink = false; - return isHTML - ? splitHTMLTags(string).map(function(string, i) { - var isTag = i % 2; - if (isTag) { - if (/^ Returns obfuscated mailto: link - > Ox.encodeEmailAddress('mailto:foo@bar.com').indexOf(':') > -1 - true - @*/ - Ox.encodeEmailAddress = function(address, text) { - var parts = ['mailto:' + address, text || address].map(function(part) { - return Ox.map(part, function(char) { - var code = char.charCodeAt(0); - return char == ':' ? ':' - : '&#' - + (Math.random() < 0.5 ? code : 'x' + code.toString(16)) - + ';'; - }); - }); - return '' + parts[1] + ''; - }; - - /*@ - Ox.encodeHTMLEntities Encodes HTML entities - (string[, encodeAll]) -> String - string String - encodeAll If true, encode characters > 127 as numeric entities - > Ox.encodeHTMLEntities('<\'&"> äbçdê') - '<'&"> äbçdê' - > Ox.encodeHTMLEntities('<\'&"> äbçdê', true) - '<'&"> äbçdê' - @*/ - Ox.encodeHTMLEntities = function(string, encodeAll) { - return Ox.map(String(string), function(char) { - var code = char.charCodeAt(0); - if (code < 128) { - char = char in htmlEntities ? htmlEntities[char] : char; - } else if (encodeAll) { - char = '&#x' - + Ox.pad(code.toString(16).toUpperCase(), 'left', 4, '0') - + ';'; + }, + {'name': 'br'}, + { + 'name': 'iframe', + 'optional': ['width', 'height'], + 'required': ['src'], + 'validate': { + 'width': /^\d+$/, + 'height': /^\d+$/, + 'src': /^((https?:\/\/|\/).*?)/ } - return char; - }); - }; - - /*@ - Ox.decodeHTMLEntities Decodes HTML entities - (string[, decodeAll]) -> String - string String - decodeAll If true, decode named entities for characters > 127 - Note that `decodeAll` relies on `Ox.normalizeHTML`, which uses the - DOM and may transform the string - > Ox.decodeHTMLEntities('<'&">') - '<\'&">' - > Ox.decodeHTMLEntities('<'&">') - '<\'&">' - > Ox.decodeHTMLEntities('äbçdê') - 'äbçdê' - > Ox.decodeHTMLEntities('äbçdê') - 'äbçdê' - > Ox.decodeHTMLEntities('äbçdê', true) - 'äbçdê' - > Ox.decodeHTMLEntities('β') - 'β' - > Ox.decodeHTMLEntities('β', true) - 'β' - > Ox.decodeHTMLEntities('<b>') - '' - @*/ - Ox.decodeHTMLEntities = function(string, decodeAll) { - return decodeAll - ? Ox.decodeHTMLEntities(Ox.normalizeHTML(string)) - : decodeHTMLEntities(string); - }; - - /*@ - Ox.highlight Highlight matches in string - (string, query, classname[, isHTML]) -> Output string - string Input string - query Case-insentitive query string, or regular expression - classname Class name for matches - isHTML If true, the input string is treated as HTML - > Ox.highlight('', 'foo', 'c') - '<foo><bar>' - > Ox.highlight('&', '&', 'c') - '&amp;' - > Ox.highlight('&', '&', 'c') - '&' - > Ox.highlight('<foo> <foo>', '', 'c', true) - '<foo> <foo>' - > Ox.highlight('name', 'name', 'c', true) - 'name' - > Ox.highlight('amp & amp', 'amp', 'c', true) - 'amp & amp' - > Ox.highlight('amp & amp', 'amp & amp', 'c', true) - 'amp & amp' - > Ox.highlight('<b>', '', 'c', true) - '<b>' - > Ox.highlight('<b>', '<b>', 'c', true) - '<b>' - > Ox.highlight('foobarbaz', 'foobar', 'c', true) - 'foobarbaz' - > Ox.highlight('foo

bar

baz', 'foobar', 'c', true) - 'foo

bar

baz' - > Ox.highlight('foo
bar baz', 'foo bar', 'c', true) - 'foo
bar
baz' - @*/ - Ox.highlight = function(string, query, classname, isHTML) { - if (!query) { - return string; - } - var cursor = 0, - entities = [], - matches = [], - offset = 0, - re = Ox.isRegExp(query) ? query - : new RegExp(Ox.escapeRegExp(query), 'gi'), - span = ['', ''], - tags = []; - function insert(array) { - // for each replacement - array.forEach(function(v) { - // replace the modified value with the original value - string = Ox.splice(string, v.position, v.length, v.value); - // for each match - matches.forEach(function(match) { - if (v.position < match.position) { - // replacement is before match, update match position - match.position += v.value.length - v.length; - } else if ( - v.position < match.position + match.value.length - ) { - // replacement is inside match, update match value - match.value = Ox.splice( - match.value, v.position - match.position, v.length, - v.value - ); - } - }); - }); - } - if (isHTML && regexp.html.test(string)) { - string = string // Ox.normalizeHTML(string) - // remove inline tags - .replace(regexp.tag, function(value, tag, position) { - tags.push({ - length: 0, position: position, value: value - }); - return ''; - }) - // decode html entities - .replace(regexp.entity, function(value, position) { - var ret = Ox.decodeHTMLEntities(value, true); - entities.push({ - length: ret.length, position: position, value: value - }); - return ret; - }); - // if decoding entities has created new tags, ignore them - splitHTMLTags(string, entities.map(function(entity) { - var ret = entity.position + offset; - offset += entity.length - entity.value.length; - return ret; - })).forEach(function(v, i) { - if (i % 2 == 0) { - // outside tags, find matches and save position and value - v.replace(re, function(value, position) { - matches.push( - {position: cursor + position, value: value} - ); - }); - } - cursor += v.length; - }); - insert(entities); - insert(tags); - // for each match (in reverse order, so that positions are correct) - matches.reverse().forEach(function(match) { - // wrap it in a span - string = Ox.splice( - string, match.position, match.value.length, - span.join(match.value) - ); - }); - // we may have enclosed single opening or closing tags in a span - if (matches.length && tags.length) { - string = Ox.normalizeHTML(string); - } - } else { - string = Ox.encodeHTMLEntities( - string.replace(re, function(value) { - matches.push(span.join(Ox.encodeHTMLEntities(value))); - return salt.join(matches.length - 1); - }) - ); - matches.forEach(function(match, i) { - string = string.replace(new RegExp(salt.join(i)), match); - }); - } - return string; - }; - - /*@ - Ox.normalizeHTML Normalize HTML (using the DOM) - > Ox.normalizeHTML('foo') - 'foo' - > Ox.normalizeHTML('foo') - 'foo' - > Ox.normalizeHTML('<'&"> äbçdê') - '<\'&"> äbçdê' - @*/ - Ox.normalizeHTML = function(html) { - return regexp.html.test(html) ? Ox.$('
').html(html).html() : html; - }; - - /*@ - Ox.parseMarkdown Parses (a tiny subset of) Markdown. - Supports `*emphasis*`, `_emphasis_`, `**strong**`, `__strong__`, - `` `code` ``, ``` ``code with backtick (`)`` ```, - ```` ```classname\ngithub-style\ncode blocks\n``` ````, - ``, `` and - `[text](http://example.com "title")`. - > Ox.parseMarkdown('*foo* **bar** `baz` ``back`tick``') - 'foo bar baz back`tick' - > Ox.parseMarkdown('foo\n\nbar\n\nbaz') - 'foo

bar

baz' - > Ox.parseMarkdown('```foo\n\nbar\n\nbaz\n```') - '
bar\n\nbaz\n
' - > Ox.parseMarkdown('') - 'http://example.com' - > Ox.parseMarkdown('``') - '<http://example.com>' - > Ox.parseMarkdown('[example](http://example.com "example.com")') - 'example' - > Ox.parseMarkdown('[example](http://example.com?foo=bar&bar=baz)') - 'example' - > Ox(Ox.parseMarkdown('')).startsWith('mail@example.com' - */ - Ox.parseMarkdown = function(string) { - // see https://github.com/coreyti/showdown/blob/master/src/showdown.js - var array = []; - return string.replace(/\r\n/g, '\n').replace(/\r/g, '\n') - .replace( - /(?:^|\n)```(.*)\n([^`]+)\n```/g, - function(match, classname, code) { - array.push( - '
'
-                        + code.trim().replace(/
' - ); - return salt.join(array.length - 1); - } - ) - .replace( - /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, - function(match, prev, backticks, code, next) { - array.push( - prev + '' - + code.trim().replace(/' - ); - return salt.join(array.length - 1); - } - ) - .replace( - /(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, - '$2' - ) - .replace( - /(\*|_)(?=\S)([^\r]*?\S)\1/g, - '$2' - ) - .replace( - /(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, - function(match, all, text, id, url, rest, quote, title) { - return '' + text + ''; - } - ) - .replace( - /<((https?|ftp|dict):[^'">\s]+)>/gi, - '$1' - ) - .replace( - /<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, - function(match, mail) { - return Ox.encodeEmailAddress(mail); - } - ) - .replace(/\n\n/g, '

') - .replace( - new RegExp(salt.join('(\\d+)'), 'g'), - function(match, index) { - return array[parseInt(index)]; - } - ); - }; - - /*@ - Ox.sanitizeHTML Takes untrusted HTML and returns something trustworthy - > Ox.sanitizeHTML('http://foo.com, ...') - 'http://foo.com, ...' - > Ox.sanitizeHTML('http://foo.com/foo?bar&baz, ...') - 'http://foo.com/foo?bar&baz, ...' - > Ox.sanitizeHTML('(see: www.foo.com)') - '(see: www.foo.com)' - > Ox.sanitizeHTML('foo@bar.com') - 'foo@bar.com' - > Ox.sanitizeHTML('foo') - 'foo' - > Ox.sanitizeHTML('foo') - 'foo' - > Ox.sanitizeHTML('http://www.foo.com/') - 'http://www.foo.com/' - > Ox.sanitizeHTML('foo') - 'foo' - > Ox.sanitizeHTML('foo') - 'foo' - > Ox.sanitizeHTML('foo') - '<a href="javascript:alert()">foo</a>' - > Ox.sanitizeHTML('foo') - '<a href="foo">foo</a>' - > Ox.sanitizeHTML('foo') - 'foo' - > Ox.sanitizeHTML('foo') - 'foo' - > Ox.sanitizeHTML('[http://foo.com foo]') - 'foo' - > Ox.sanitizeHTML('
foo
') - '
foo
' - > Ox.sanitizeHTML('') - '<script>alert()</script>' - > Ox.sanitizeHTML('\'foo\' < \'bar\' && "foo" > "bar"') - '\'foo\' < \'bar\' && "foo" > "bar"' - > Ox.sanitizeHTML('foo') - 'foo' - > Ox.sanitizeHTML('foo') - 'foo' - > Ox.sanitizeHTML('&&') - '&&' - > Ox.sanitizeHTML('') - '<http://foo.com>' - > Ox.sanitizeHTML('') - '"<foo value="http://foo.com"></foo>"' - @*/ - Ox.sanitizeHTML = function(html, tags, globalAttributes) { - - tags = tags || defaultTags; - globalAttributes = globalAttributes || []; - - var escaped = {}, - level = 0, - matches = [], - selfClosingTags = ['img', 'br'], - validAttributes = {}, requiredAttributes = {}, validate = {}, - validTags = tags.map(function(tag) { - validAttributes[tag.name] = globalAttributes - .concat(tag.required || []) - .concat(tag.optional || []); - requiredAttributes[tag.name] = tag.required || []; - validate[tag.name] = tag.validate || {}; - return tag.name; - }); - - // html = Ox.clean(html); fixme: can this be a parameter? - if (Ox.contains(validTags, '[]')) { - html = html.replace( - /\[((\/|https?:\/\/|mailto:).+?) (.+?)\]/gi, - '$3' - ); - validTags = validTags.filter(function(tag) { - return tag != '[]'; - }); - } - - html = splitHTMLTags(html).map(function(string, i) { - - var attrs = {}, - attrMatch, - attrRegexp = /([^=\ ]+)="([^"]+)"/g, - attrString, - isClosing, - isTag = i % 2, - isValid = true, - tag, - tagMatch, - tagRegexp = /<(\/)?([^\ \/]+)(.*?)(\/)?>/g; - - if (isTag) { - tagMatch = tagRegexp.exec(string); - if (tagMatch) { - isClosing = !Ox.isUndefined(tagMatch[1]); - tag = tagMatch[2]; - attrString = tagMatch[3].trim(); - while (attrMatch = attrRegexp.exec(attrString)) { - if ( - validAttributes[tag] - && Ox.contains(validAttributes[tag], attrMatch[1]) - ) { - attrs[attrMatch[1]] = attrMatch[2]; - } - } - if (!isClosing && !Ox.contains(selfClosingTags, tag)) { - level++; - } - if ( - !Ox.contains(validTags, tag) - || (attrString.length && Ox.isEmpty(attrs)) - ) { - isValid = false; - } else if (!isClosing && requiredAttributes[tag]) { - requiredAttributes[tag].forEach(function(attr) { - if (Ox.isUndefined(attrs[attr])) { - isValid = false; - } - }); - } - if (isValid && !Ox.isEmpty(attrs)) { - Ox.forEach(attrs, function(value, key) { - if ( - !Ox.isUndefined(validate[tag][key]) - && !validate[tag][key].exec(value) - ) { - isValid = false; - return false; // break - } - }); - } - if (isValid && isClosing) { - isValid = !escaped[level]; - } else { - escaped[level] = !isValid; - } - if (isClosing) { - level--; - } - if (isValid) { - return '<' - + (isClosing ? '/' : '') - + tag - + (!isClosing && !Ox.isEmpty(attrs) - ? ' ' + Ox.values(Ox.map(attrs, function(value, key) { - return key + '="' + value + '"'; - })).join(' ') - : '') - + '>'; - } - } - } - - return Ox.encodeHTMLEntities(Ox.decodeHTMLEntities(string)); - + }, + { + 'name': 'img', + 'optional': ['width', 'height'], + 'required': ['src'], + 'validate': { + 'width': /^\d+$/, + 'height': /^\d+$/, + 'src': /^((https?:\/\/|\/).*?)/ + }, + }, + {'name': 'figure'}, + {'name': 'figcaption'} + ], + htmlEntities = { + '"': '"', '&': '&', "'": ''', '<': '<', '>': '>' + }, + regexp = { + entity: /&[^\s]+?;/g, + html: /[<&]/, + tag: new RegExp('<\\/?(' + [ + 'a', 'b', 'br', 'code', 'i', 's', 'span', 'u' + ].join('|') + ')\\/?>', 'gi') + }, + salt = Ox.range(2).map(function(){ + return Ox.range(16).map(function() { + return Ox.char(65 + Ox.random(26)); }).join(''); + }); - //FIXME: dont add links to urls inside of escaped tags - html = Ox.addLinks(html, true); - html = html.replace(/\n\n/g, '

'); - // Close extra opening and remove extra closing tags. - // Note: this converts ''' to "'" and '"' to '"' - return Ox.normalizeHTML(html); +function _addLinks(string, obfuscate) { + return string + .replace( + /\b((https?:\/\/|www\.).+?)([.,:;!?)\]]*?(\s|$))/gi, + function(match, url, prefix, end) { + prefix = prefix.toLowerCase() == 'www.' ? 'http://' : ''; + return Ox.formatString( + '{url}{end}', + {end: end, prefix: prefix, url: url} + ); + } + ) + .replace( + /\b([0-9A-Z.+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6})\b/gi, + obfuscate ? function(match, mail) { + return Ox.encodeEmailAddress(mail); + } : '$1' + ); +} - }; +function _decodeHTMLEntities(string) { + return string + .replace( + new RegExp('(' + Ox.values(htmlEntities).join('|') + ')', 'g'), + function(match) { + return Ox.keyOf(htmlEntities, match); + } + ) + .replace( + /&#([0-9A-FX]+);/gi, + function(match, code) { + return Ox.char( + /^X/i.test(code) + ? parseInt(code.slice(1), 16) + : parseInt(code, 10) + ); + } + ); +} - /*@ - Ox.stripTags Strips HTML tags from a string - > Ox.stripTags('foo') - 'foo' - @*/ - Ox.stripTags = function(string) { - return string.replace(/<.*?>/g, ''); - }; +// Splits a string into text (even indices) and tags (odd indices), ignoring +// tags with starting positions that are included in the ignore array +function splitHTMLTags(string, ignore) { + var isTag = false, ret = ['']; + ignore = ignore || []; + Ox.forEach(string, function(char, i) { + if (!isTag && char == '<' && ignore.indexOf(i) == -1) { + isTag = true; + ret.push(''); + } + ret[ret.length - 1] += char; + if (isTag && char == '>') { + isTag = false; + ret.push(''); + } + }); + return ret; +} + +/*@ +Ox.addLinks Takes a string and adds links for e-mail addresses and URLs + (string[, isHTML]) -> Formatted string + string String + isHTML If true, ignore matches in tags or enclosed by links + > Ox.addLinks('foo bar ') + 'foo bar <foo@bar.com>' + > Ox.addLinks('www.foo.com/bar#baz, etc.') + 'www.foo.com/bar#baz, etc.' + > Ox.addLinks('www.foo.com', true) + 'www.foo.com' +@*/ +export function addLinks(string, isHTML) { + var isLink = false; + return isHTML + ? splitHTMLTags(string).map(function(string, i) { + var isTag = i % 2; + if (isTag) { + if (/^ Returns obfuscated mailto: link + > Ox.encodeEmailAddress('mailto:foo@bar.com').indexOf(':') > -1 + true +@*/ +export function encodeEmailAddress(address, text) { + var parts = ['mailto:' + address, text || address].map(function(part) { + return Ox.map(part, function(char) { + var code = char.charCodeAt(0); + return char == ':' ? ':' + : '&#' + + (Math.random() < 0.5 ? code : 'x' + code.toString(16)) + + ';'; + }); + }); + return '' + parts[1] + ''; +}; + +/*@ +Ox.encodeHTMLEntities Encodes HTML entities + (string[, encodeAll]) -> String + string String + encodeAll If true, encode characters > 127 as numeric entities + > Ox.encodeHTMLEntities('<\'&"> äbçdê') + '<'&"> äbçdê' + > Ox.encodeHTMLEntities('<\'&"> äbçdê', true) + '<'&"> äbçdê' +@*/ +export function encodeHTMLEntities(string, encodeAll) { + return Ox.map(String(string), function(char) { + var code = char.charCodeAt(0); + if (code < 128) { + char = char in htmlEntities ? htmlEntities[char] : char; + } else if (encodeAll) { + char = '&#x' + + Ox.pad(code.toString(16).toUpperCase(), 'left', 4, '0') + + ';'; + } + return char; + }); +}; + +/*@ +Ox.decodeHTMLEntities Decodes HTML entities + (string[, decodeAll]) -> String + string String + decodeAll If true, decode named entities for characters > 127 + Note that `decodeAll` relies on `Ox.normalizeHTML`, which uses the + DOM and may transform the string + > Ox.decodeHTMLEntities('<'&">') + '<\'&">' + > Ox.decodeHTMLEntities('<'&">') + '<\'&">' + > Ox.decodeHTMLEntities('äbçdê') + 'äbçdê' + > Ox.decodeHTMLEntities('äbçdê') + 'äbçdê' + > Ox.decodeHTMLEntities('äbçdê', true) + 'äbçdê' + > Ox.decodeHTMLEntities('β') + 'β' + > Ox.decodeHTMLEntities('β', true) + 'β' + > Ox.decodeHTMLEntities('<b>') + '' +@*/ +export function decodeHTMLEntities(string, decodeAll) { + return decodeAll + ? decodeHTMLEntities(normalizeHTML(string)) + : _decodeHTMLEntities(string); +}; + +/*@ +Ox.highlight Highlight matches in string + (string, query, classname[, isHTML]) -> Output string + string Input string + query Case-insentitive query string, or regular expression + classname Class name for matches + isHTML If true, the input string is treated as HTML + > Ox.highlight('', 'foo', 'c') + '<foo><bar>' + > Ox.highlight('&', '&', 'c') + '&amp;' + > Ox.highlight('&', '&', 'c') + '&' + > Ox.highlight('<foo> <foo>', '', 'c', true) + '<foo> <foo>' + > Ox.highlight('name', 'name', 'c', true) + 'name' + > Ox.highlight('amp & amp', 'amp', 'c', true) + 'amp & amp' + > Ox.highlight('amp & amp', 'amp & amp', 'c', true) + 'amp & amp' + > Ox.highlight('<b>', '', 'c', true) + '<b>' + > Ox.highlight('<b>', '<b>', 'c', true) + '<b>' + > Ox.highlight('foobarbaz', 'foobar', 'c', true) + 'foobarbaz' + > Ox.highlight('foo

bar

baz', 'foobar', 'c', true) + 'foo

bar

baz' + > Ox.highlight('foo
bar baz', 'foo bar', 'c', true) + 'foo
bar
baz' +@*/ +export function highlight(string, query, classname, isHTML) { + if (!query) { + return string; + } + var cursor = 0, + entities = [], + matches = [], + offset = 0, + re = Ox.isRegExp(query) ? query + : new RegExp(Ox.escapeRegExp(query), 'gi'), + span = ['', ''], + tags = []; + function insert(array) { + // for each replacement + array.forEach(function(v) { + // replace the modified value with the original value + string = Ox.splice(string, v.position, v.length, v.value); + // for each match + matches.forEach(function(match) { + if (v.position < match.position) { + // replacement is before match, update match position + match.position += v.value.length - v.length; + } else if ( + v.position < match.position + match.value.length + ) { + // replacement is inside match, update match value + match.value = Ox.splice( + match.value, v.position - match.position, v.length, + v.value + ); + } + }); + }); + } + if (isHTML && regexp.html.test(string)) { + string = string // Ox.normalizeHTML(string) + // remove inline tags + .replace(regexp.tag, function(value, tag, position) { + tags.push({ + length: 0, position: position, value: value + }); + return ''; + }) + // decode html entities + .replace(regexp.entity, function(value, position) { + var ret = Ox.decodeHTMLEntities(value, true); + entities.push({ + length: ret.length, position: position, value: value + }); + return ret; + }); + // if decoding entities has created new tags, ignore them + splitHTMLTags(string, entities.map(function(entity) { + var ret = entity.position + offset; + offset += entity.length - entity.value.length; + return ret; + })).forEach(function(v, i) { + if (i % 2 == 0) { + // outside tags, find matches and save position and value + v.replace(re, function(value, position) { + matches.push( + {position: cursor + position, value: value} + ); + }); + } + cursor += v.length; + }); + insert(entities); + insert(tags); + // for each match (in reverse order, so that positions are correct) + matches.reverse().forEach(function(match) { + // wrap it in a span + string = Ox.splice( + string, match.position, match.value.length, + span.join(match.value) + ); + }); + // we may have enclosed single opening or closing tags in a span + if (matches.length && tags.length) { + string = normalizeHTML(string); + } + } else { + string = Ox.encodeHTMLEntities( + string.replace(re, function(value) { + matches.push(span.join(Ox.encodeHTMLEntities(value))); + return salt.join(matches.length - 1); + }) + ); + matches.forEach(function(match, i) { + string = string.replace(new RegExp(salt.join(i)), match); + }); + } + return string; +}; + +/*@ +Ox.normalizeHTML Normalize HTML (using the DOM) + > Ox.normalizeHTML('foo') + 'foo' + > Ox.normalizeHTML('foo') + 'foo' + > Ox.normalizeHTML('<'&"> äbçdê') + '<\'&"> äbçdê' +@*/ +export function normalizeHTML(html) { + return regexp.html.test(html) ? Ox.$('
').html(html).html() : html; +}; + +/*@ +Ox.parseMarkdown Parses (a tiny subset of) Markdown. + Supports `*emphasis*`, `_emphasis_`, `**strong**`, `__strong__`, + `` `code` ``, ``` ``code with backtick (`)`` ```, + ```` ```classname\ngithub-style\ncode blocks\n``` ````, + ``, `` and + `[text](http://example.com "title")`. + > Ox.parseMarkdown('*foo* **bar** `baz` ``back`tick``') + 'foo bar baz back`tick' + > Ox.parseMarkdown('foo\n\nbar\n\nbaz') + 'foo

bar

baz' + > Ox.parseMarkdown('```foo\n\nbar\n\nbaz\n```') + '
bar\n\nbaz\n
' + > Ox.parseMarkdown('') + 'http://example.com' + > Ox.parseMarkdown('``') + '<http://example.com>' + > Ox.parseMarkdown('[example](http://example.com "example.com")') + 'example' + > Ox.parseMarkdown('[example](http://example.com?foo=bar&bar=baz)') + 'example' + > Ox(Ox.parseMarkdown('')).startsWith('mail@example.com' +*/ +export function parseMarkdown(string) { + // see https://github.com/coreyti/showdown/blob/master/src/showdown.js + var array = []; + return string.replace(/\r\n/g, '\n').replace(/\r/g, '\n') + .replace( + /(?:^|\n)```(.*)\n([^`]+)\n```/g, + function(match, classname, code) { + array.push( + '
'
+                    + code.trim().replace(/
' + ); + return salt.join(array.length - 1); + } + ) + .replace( + /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, + function(match, prev, backticks, code, next) { + array.push( + prev + '' + + code.trim().replace(/' + ); + return salt.join(array.length - 1); + } + ) + .replace( + /(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, + '$2' + ) + .replace( + /(\*|_)(?=\S)([^\r]*?\S)\1/g, + '$2' + ) + .replace( + /(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, + function(match, all, text, id, url, rest, quote, title) { + return '' + text + ''; + } + ) + .replace( + /<((https?|ftp|dict):[^'">\s]+)>/gi, + '$1' + ) + .replace( + /<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, + function(match, mail) { + return Ox.encodeEmailAddress(mail); + } + ) + .replace(/\n\n/g, '

') + .replace( + new RegExp(salt.join('(\\d+)'), 'g'), + function(match, index) { + return array[parseInt(index)]; + } + ); +}; + +/*@ +Ox.sanitizeHTML Takes untrusted HTML and returns something trustworthy + > Ox.sanitizeHTML('http://foo.com, ...') + 'http://foo.com, ...' + > Ox.sanitizeHTML('http://foo.com/foo?bar&baz, ...') + 'http://foo.com/foo?bar&baz, ...' + > Ox.sanitizeHTML('(see: www.foo.com)') + '(see: www.foo.com)' + > Ox.sanitizeHTML('foo@bar.com') + 'foo@bar.com' + > Ox.sanitizeHTML('foo') + 'foo' + > Ox.sanitizeHTML('foo') + 'foo' + > Ox.sanitizeHTML('http://www.foo.com/') + 'http://www.foo.com/' + > Ox.sanitizeHTML('foo') + 'foo' + > Ox.sanitizeHTML('foo') + 'foo' + > Ox.sanitizeHTML('foo') + '<a href="javascript:alert()">foo</a>' + > Ox.sanitizeHTML('foo') + '<a href="foo">foo</a>' + > Ox.sanitizeHTML('foo') + 'foo' + > Ox.sanitizeHTML('foo') + 'foo' + > Ox.sanitizeHTML('[http://foo.com foo]') + 'foo' + > Ox.sanitizeHTML('
foo
') + '
foo
' + > Ox.sanitizeHTML('') + '<script>alert()</script>' + > Ox.sanitizeHTML('\'foo\' < \'bar\' && "foo" > "bar"') + '\'foo\' < \'bar\' && "foo" > "bar"' + > Ox.sanitizeHTML('foo') + 'foo' + > Ox.sanitizeHTML('foo') + 'foo' + > Ox.sanitizeHTML('&&') + '&&' + > Ox.sanitizeHTML('') + '<http://foo.com>' + > Ox.sanitizeHTML('') + '"<foo value="http://foo.com"></foo>"' +@*/ +export function sanitizeHTML(html, tags, globalAttributes) { + + tags = tags || defaultTags; + globalAttributes = globalAttributes || []; + + var escaped = {}, + level = 0, + matches = [], + selfClosingTags = ['img', 'br'], + validAttributes = {}, requiredAttributes = {}, validate = {}, + validTags = tags.map(function(tag) { + validAttributes[tag.name] = globalAttributes + .concat(tag.required || []) + .concat(tag.optional || []); + requiredAttributes[tag.name] = tag.required || []; + validate[tag.name] = tag.validate || {}; + return tag.name; + }); + + // html = Ox.clean(html); fixme: can this be a parameter? + if (Ox.contains(validTags, '[]')) { + html = html.replace( + /\[((\/|https?:\/\/|mailto:).+?) (.+?)\]/gi, + '$3' + ); + validTags = validTags.filter(function(tag) { + return tag != '[]'; + }); + } + + html = splitHTMLTags(html).map(function(string, i) { + + var attrs = {}, + attrMatch, + attrRegexp = /([^=\ ]+)="([^"]+)"/g, + attrString, + isClosing, + isTag = i % 2, + isValid = true, + tag, + tagMatch, + tagRegexp = /<(\/)?([^\ \/]+)(.*?)(\/)?>/g; + + if (isTag) { + tagMatch = tagRegexp.exec(string); + if (tagMatch) { + isClosing = !Ox.isUndefined(tagMatch[1]); + tag = tagMatch[2]; + attrString = tagMatch[3].trim(); + while (attrMatch = attrRegexp.exec(attrString)) { + if ( + validAttributes[tag] + && Ox.contains(validAttributes[tag], attrMatch[1]) + ) { + attrs[attrMatch[1]] = attrMatch[2]; + } + } + if (!isClosing && !Ox.contains(selfClosingTags, tag)) { + level++; + } + if ( + !Ox.contains(validTags, tag) + || (attrString.length && Ox.isEmpty(attrs)) + ) { + isValid = false; + } else if (!isClosing && requiredAttributes[tag]) { + requiredAttributes[tag].forEach(function(attr) { + if (Ox.isUndefined(attrs[attr])) { + isValid = false; + } + }); + } + if (isValid && !Ox.isEmpty(attrs)) { + Ox.forEach(attrs, function(value, key) { + if ( + !Ox.isUndefined(validate[tag][key]) + && !validate[tag][key].exec(value) + ) { + isValid = false; + return false; // break + } + }); + } + if (isValid && isClosing) { + isValid = !escaped[level]; + } else { + escaped[level] = !isValid; + } + if (isClosing) { + level--; + } + if (isValid) { + return '<' + + (isClosing ? '/' : '') + + tag + + (!isClosing && !Ox.isEmpty(attrs) + ? ' ' + Ox.values(Ox.map(attrs, function(value, key) { + return key + '="' + value + '"'; + })).join(' ') + : '') + + '>'; + } + } + } + + return Ox.encodeHTMLEntities(Ox.decodeHTMLEntities(string)); + + }).join(''); + + //FIXME: dont add links to urls inside of escaped tags + html = addLinks(html, true); + html = html.replace(/\n\n/g, '

'); + // Close extra opening and remove extra closing tags. + // Note: this converts ''' to "'" and '"' to '"' + return normalizeHTML(html); + +}; + +/*@ +Ox.stripTags Strips HTML tags from a string + > Ox.stripTags('foo') + 'foo' +@*/ +export function stripTags(string) { + return string.replace(/<.*?>/g, ''); +}; -}()); diff --git a/source/Ox/js/Hash.js b/source/Ox/js/Hash.js index 7471604d..11f5f41b 100644 --- a/source/Ox/js/Hash.js +++ b/source/Ox/js/Hash.js @@ -1,9 +1,21 @@ 'use strict'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; + +const Ox = {}; + +Object.assign(Ox, + OxObject, + OxConstants, + OxMath +); + /*@ Ox.oshash Calculates oshash for a given file or blob object. Async. @*/ -Ox.oshash = function(file, callback) { +export function oshash(file, callback) { // Needs to go via string to work for files > 2GB var hash = fromString(file.size.toString()); @@ -100,7 +112,7 @@ Ox.oshash = function(file, callback) { /*@ Ox.SHA1 Calculates SHA1 hash of the given string @*/ -Ox.SHA1 = function(msg) { +export function SHA1(msg) { function rotate_left(n,s) { var t4 = ( n<>>(32-s)); diff --git a/source/Ox/js/JavaScript.js b/source/Ox/js/JavaScript.js index 56514b14..2a6fea52 100644 --- a/source/Ox/js/JavaScript.js +++ b/source/Ox/js/JavaScript.js @@ -1,5 +1,21 @@ 'use strict'; +import * as OxArray from './Array.js'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; +import * as OxString from './String.js'; + +const Ox = {}; + +Object.assign(Ox, + OxArray, + OxObject, + OxConstants, + OxMath, + OxString, +); + /*@ Ox.doc Generates documentation for annotated JavaScript (source) -> <[o]> Array of doc objects diff --git a/source/Ox/js/Locale.js b/source/Ox/js/Locale.js index 324f8646..76c4e4b4 100644 --- a/source/Ox/js/Locale.js +++ b/source/Ox/js/Locale.js @@ -1,90 +1,103 @@ 'use strict'; +import * as OxArray from './Array.js'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; +import * as OxString from './String.js'; -(function() { +const Ox = {}; - var log, translations = {}; +Object.assign(Ox, + OxArray, + OxObject, + OxConstants, + OxMath, + OxString, +); - /*@ - Ox.getLocale Returns locale - () -> Locale (like 'de' or 'fr') - @*/ - Ox.getLocale = function() { - return Ox.LOCALE; - }; - /*@ - Ox.setLocale Sets locale - (locale[, url], callback) - locale Locale (like 'de' or 'fr') - url one or more URLs of JSON file with additional translations - callback Callback function - success If true, locale has been set - @*/ - Ox.setLocale = function(locale, url, callback) { - var isValidLocale = Ox.contains(Object.keys(Ox.LOCALE_NAMES), locale), - urls = []; - if (arguments.length == 2) { - callback = arguments[1]; - url = null; - } - if (isValidLocale) { - Ox.LOCALE = locale; - if (locale == 'en') { - translations = {}; - } else { - translations = {}; - Ox.forEach(Ox.LOCALES, function(locales, module) { - if ( - (module == 'Ox' || Ox.load[module]) - && Ox.contains(locales, locale) - ) { - urls.push([ - Ox.PATH + module + '/json/locale.' - + locale + '.json' - ]); - } - }); - } - url && Ox.makeArray(url).forEach(function(value) { - urls.push(Ox.makeArray(value)); - }); - if (urls.length) { - Ox.getJSON(urls, function(data) { - Ox.forEach(data, function(values, url) { - Ox.extend(translations, values); - }); - callback(true); - }) - } else { - callback(true); - } +var log, translations = {}; + +/*@ +Ox.getLocale Returns locale + () -> Locale (like 'de' or 'fr') +@*/ +export function getLocale() { + return Ox.LOCALE; +}; + +/*@ +Ox.setLocale Sets locale + (locale[, url], callback) + locale Locale (like 'de' or 'fr') + url one or more URLs of JSON file with additional translations + callback Callback function + success If true, locale has been set +@*/ +export function setLocale(locale, url, callback) { + var isValidLocale = Ox.contains(Object.keys(Ox.LOCALE_NAMES), locale), + urls = []; + if (arguments.length == 2) { + callback = arguments[1]; + url = null; + } + if (isValidLocale) { + Ox.LOCALE = locale; + if (locale == 'en') { + translations = {}; } else { - callback(false); + translations = {}; + Ox.forEach(Ox.LOCALES, function(locales, module) { + if ( + (module == 'Ox' || Ox.load[module]) + && Ox.contains(locales, locale) + ) { + urls.push([ + Ox.PATH + module + '/json/locale.' + + locale + '.json' + ]); + } + }); } - }; + url && Ox.makeArray(url).forEach(function(value) { + urls.push(Ox.makeArray(value)); + }); + if (urls.length) { + Ox.getJSON(urls, function(data) { + Ox.forEach(data, function(values, url) { + Ox.extend(translations, values); + }); + callback(true); + }) + } else { + callback(true); + } + } else { + callback(false); + } +}; - /*@ - Ox._ Localizes a string - (string[, options]) -> Localized string - string English string - options Options passed to Ox.formatString - @*/ - Ox._ = function(value, options) { - var translation = translations[value]; - log && log(value, translation); - translation = translation || value || ''; - return Ox.formatString(translation, options); - }; +/*@ +Ox._ Localizes a string + (string[, options]) -> Localized string + string English string + options Options passed to Ox.formatString +@*/ +export function _(value, options) { + var translation = translations[value]; + log && log(value, translation); + translation = translation || value || ''; + return Ox.formatString(translation, options); +}; - /*@ - Ox._.log Registers a logging function - (callback) -> undefined - callback Callback function - english English string - translation Translated string - @*/ - Ox._.log = function(callback) { - log = callback; - }; +/*@ +Ox._.log Registers a logging function + (callback) -> undefined + callback Callback function + english English string + translation Translated string +@*/ +_.log = function(callback) { + log = callback; +}; -})(); diff --git a/source/Ox/js/Math.js b/source/Ox/js/Math.js index 9e933c04..cb7fde2b 100644 --- a/source/Ox/js/Math.js +++ b/source/Ox/js/Math.js @@ -1,12 +1,20 @@ 'use strict'; +import * as OxArray from './Array.js'; + +const Ox = {}; + +Object.assign(Ox, + OxArray, +); + /*@ Ox.acosh Inverse hyperbolic cosine Missing from `Math`. > Ox.acosh(1) 0 @*/ -Ox.acosh = function(x) { +export function acosh(x) { return Math.log(x + Math.sqrt(x * x - 1)); }; @@ -16,7 +24,7 @@ Ox.asinh Inverse hyperbolic sine > Ox.asinh(0) 0 @*/ -Ox.asinh = function(x) { +export function asinh(x) { return Math.log(x + Math.sqrt(x * x + 1)); }; @@ -26,7 +34,7 @@ Ox.atanh Inverse hyperbolic tangent > Ox.atanh(0) 0 @*/ -Ox.atanh = function(x) { +export function atanh(x) { return 0.5 * Math.log((1 + x) / (1 - x)); }; @@ -36,7 +44,7 @@ Ox.cosh Hyperbolic cosine > Ox.cosh(0) 1 @*/ -Ox.cosh = function(x) { +export function cosh(x) { return (Math.exp(x) + Math.exp(-x)) / 2; }; @@ -46,7 +54,7 @@ Ox.deg Takes radians, returns degrees > Ox.deg(2 * Math.PI) 360 @*/ -Ox.deg = function(rad) { +export function deg(rad) { return rad * 180 / Math.PI; }; @@ -58,7 +66,7 @@ Ox.hypot Returns the square root of the sum of the squares of its arguments > Ox.hypot(1, 1, 1) Math.sqrt(3) @*/ -Ox.hypot = function() { +export function hypot() { return Math.sqrt(Ox.slice(arguments).reduce(function(sum, number) { return sum + number * number; }, 0)); @@ -83,7 +91,7 @@ Ox.limit Limits a number by a given mininum and maximum > Ox.limit(-1, -2) -2 @*/ -Ox.limit = function(/*number[[, min], max]*/) { +export function limit(/*number[[, min], max]*/) { var number = arguments[0], min = arguments.length == 3 ? arguments[1] : -Infinity, max = arguments[arguments.length - 1]; @@ -98,7 +106,7 @@ Ox.log Returns the logarithm of a given number to a given base > Ox.log(Math.E) 1 @*/ -Ox.log = function(number, base) { +export function log(number, base) { return Math.log(number) / Math.log(base || Math.E); }; @@ -110,7 +118,7 @@ Ox.mod Modulo function > Ox.mod(-11, 10) 9 @*/ -Ox.mod = function(number, by) { +export function mod(number, by) { return (number % by + by) % by; }; @@ -120,7 +128,7 @@ Ox.rad Takes degrees, returns radians > Ox.rad(360) 2 * Math.PI @*/ -Ox.rad = function(deg) { +export function rad(deg) { return deg * Math.PI / 180; }; @@ -136,7 +144,7 @@ Ox.random Returns a random integer within a given range > Ox.random(1, 2) == 1 true @*/ -Ox.random = function() { +export function random() { var min = arguments.length == 2 ? arguments[0] : 0, max = arguments.length ? Ox.last(arguments) : 2; return min + Math.floor(Math.random() * (max - min)); @@ -151,7 +159,7 @@ Ox.round Rounds a number with a given number of decimals > Ox.round(1 / 2) 1 @*/ -Ox.round = function(number, decimals) { +export function round(number, decimals) { var pow = Math.pow(10, decimals || 0); return Math.round(number * pow) / pow; }; @@ -169,7 +177,7 @@ Ox.sign Returns the sign of a number (-1, 0 or 1) > Ox.sign(Infinity) 1 @*/ -Ox.sign = function(x) { +export function sign(x) { x = +x; return x !== x || x === 0 ? x : x < 0 ? -1 : 1; }; @@ -180,7 +188,7 @@ Ox.sinh Hyperbolic sine > Ox.sinh(0) 0 @*/ -Ox.sinh = function(x) { +export function sinh(x) { return (Math.exp(x) - Math.exp(-x)) / 2; }; @@ -194,7 +202,7 @@ Ox.splitInt Splits an integer into an array of integers > Ox.splitInt(100, 6) [16, 16, 17, 17, 17, 17] @*/ -Ox.splitInt = function(number, by) { +export function splitInt(number, by) { var div = Math.floor(number / by), mod = number % by; return Ox.range(by).map(function(i) { @@ -208,7 +216,7 @@ Ox.tanh Hyperbolic tangent > Ox.tanh(0) 0 @*/ -Ox.tanh = function(x) { +export function tanh(x) { return (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x)); }; @@ -217,6 +225,6 @@ Ox.trunc Truncates a number > Ox.trunc(-1.5) -1 @*/ -Ox.trunc = function(x) { +export function trunc(x) { return ~~x; }; diff --git a/source/Ox/js/Object.js b/source/Ox/js/Object.js index 0534d820..b6c89bc8 100644 --- a/source/Ox/js/Object.js +++ b/source/Ox/js/Object.js @@ -1,5 +1,25 @@ 'use strict'; +import * as OxCore from './Core.js'; +import * as OxFunction from './Function.js'; +import * as OxConstants from './Constants.js'; +import * as OxCollection from './Collection.js'; +import * as OxMath from './Math.js'; +import * as OxType from './Type.js'; +import * as OxArray from './Array.js'; + +const Ox = {}; + +Object.assign(Ox, + OxCore, + OxFunction, + OxConstants, + OxCollection, + OxMath, + OxType, + OxArray, +); + /*@ Ox.extend Extends an object with one or more other objects > Ox.extend({a: 1, b: 1, c: 1}, {b: 2, c: 2}, {c: 3}) @@ -9,10 +29,10 @@ Ox.extend Extends an object with one or more other objects > Ox.extend({a: 1}, 'b') {a: 1, b: void 0} @*/ -Ox.extend = function(object) { +export function extend(object) { var args = Ox.slice(arguments, 1); if (!Ox.isObject(args[0])) { - args = [Ox.makeObject(args)]; + args = [makeObject(args)]; } Ox.forEach(args, function(arg) { Ox.forEach(arg, function(value, key) { @@ -62,7 +82,7 @@ Ox.getset Generic getter and setter function > Ox.test.object.options({foo: "foo", bar: "bar"}).options() {"key": "val", "foo": "foo", "bar": "bar"} @*/ -Ox.getset = function(object, args, callback, that) { +export function getset(object, args, callback, that) { var object_ = Ox.clone(object), ret; if (args.length == 0) { // [] @@ -72,7 +92,7 @@ Ox.getset = function(object, args, callback, that) { ret = Ox.clone(object[args[0]]); } else { // [key, val] or [{key: val, ...}] - args = Ox.makeObject(args); + args = makeObject(args); object = Ox.extend(object, args); Ox.forEach(args, function(value, key) { if (!object_ || !Ox.isEqual(object_[key], value)) { @@ -84,7 +104,7 @@ Ox.getset = function(object, args, callback, that) { return ret; }; -Ox.hasOwn = function(object, value) { +export function hasOwn(object, value) { return Object.prototype.hasOwnProperty.call(object, value); }; @@ -93,7 +113,7 @@ Ox.keyOf Equivalent of [].indexOf for objects > Ox.keyOf({a: 1, b: 2, c: 3}, 1) 'a' @*/ -Ox.keyOf = function(object, value) { +export function keyOf(object, value) { var key; Ox.forEach(object, function(v, k) { if (v === value) { @@ -117,7 +137,7 @@ Ox.makeObject Takes an array and returns an object > (function() { return Ox.makeObject(arguments); }()) {} @*/ -Ox.makeObject = function(array) { +export function makeObject(array) { var ret = {}; if (Ox.isObject(array[0])) { // [{foo: 'bar'}] @@ -134,7 +154,7 @@ Ox.methods Returns a sorted list of all method names of an object > Ox.methods({a: [], b: false, f: function() {}, n: 0, o: {}, s: ''}) ['f'] @*/ -Ox.methods = function(object, includePrototype) { +export function methods(object, includePrototype) { var key, keys; if (includePrototype) { keys = []; @@ -158,7 +178,7 @@ Ox.serialize Parses an object into query parameters > Ox.serialize({a: [1, 2], b: true, n: 1, o: {k: 'v'}, s: 'foo'}, true) 'a=[1,2]&b=true&n=1&o={"k":"v"}&s="foo"' @*/ -Ox.serialize = function(object, isJSON) { +export function serialize(object, isJSON) { var ret = []; Ox.forEach(object, function(value, key) { var value; @@ -184,7 +204,7 @@ Ox.unserialize Parses query parameters into an object {a: [1, 2], b: true, n: 1.2, o: {k: 'v'}, s1: 'foo', s2: 'bar'} @*/ -Ox.unserialize = function(string, isJSON) { +export function unserialize(string, isJSON) { var ret = {}; Ox.filter(string.split('&')).forEach(function(value) { var array = value.split('='); @@ -208,7 +228,7 @@ Ox.zipObject Takes a keys and a values array, returns a new object > Ox.zipObject(['a', 'b'], [1, 2]) {a: 1, b: 2} @*/ -Ox.zipObject = function(keys, values) { +export function zipObject(keys, values) { var object = {}; keys = Ox.makeArray(keys); values = Ox.makeArray(values); diff --git a/source/Ox/js/Polyfill.js b/source/Ox/js/Polyfill.js index 98cd94d3..85b42231 100644 --- a/source/Ox/js/Polyfill.js +++ b/source/Ox/js/Polyfill.js @@ -1,6 +1,6 @@ 'use strict'; -(function(window) { +export function loadPolyfill(window, Ox) { var canDefineProperty = !!Object.defineProperty && (function() { try { @@ -426,5 +426,4 @@ ); } } - -}(this)); +} diff --git a/source/Ox/js/RegExp.js b/source/Ox/js/RegExp.js index ac636e2f..6c5b0a02 100644 --- a/source/Ox/js/RegExp.js +++ b/source/Ox/js/RegExp.js @@ -1,3 +1,5 @@ +'use strict'; + /*@ Ox.escapeRegExp Escapes a string for use in a regular expression (str) -> Escaped string @@ -8,6 +10,6 @@ Ox.escapeRegExp Escapes a string for use in a regular expression true @*/ // see https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions -Ox.escapeRegExp = function(string) { +export function escapeRegExp(string) { return (string + '').replace(/([\/\\^$*+?.\-|(){}[\]])/g, '\\$1'); -}; \ No newline at end of file +}; diff --git a/source/Ox/js/Request.js b/source/Ox/js/Request.js index 6ea7f530..a8e10473 100644 --- a/source/Ox/js/Request.js +++ b/source/Ox/js/Request.js @@ -1,5 +1,25 @@ 'use strict'; +import * as OxArray from './Array.js'; +import * as OxObject from './Object.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; +import * as OxString from './String.js'; +import * as OxType from './Type.js'; +import * as OxCollection from './Collection.js'; + +const Ox = {}; + +Object.assign(Ox, + OxArray, + OxObject, + OxConstants, + OxMath, + OxString, + OxType, + OxCollection, +); + /*@ Ox.get Get a remote resource (url, callback) -> undefined @@ -10,7 +30,7 @@ Ox.get Get a remote resource code Status code text Status text @*/ -Ox.get = function(url, callback) { +export function get(url, callback) { var request = new XMLHttpRequest(); request.open('GET', url, true); request.onreadystatechange = function() { @@ -49,7 +69,7 @@ Ox.getAsync Runs an asynchonous loader for an array of URLs code Error code (like `404`) text Error text (like `'Not Found'`) @*/ -Ox.getAsync = function(urls, get, callback) { +export function getAsync(urls, get, callback) { urls = Ox.makeArray(urls); var errors = {}, i = 0, n = urls.length, results = {}; function done() { @@ -76,7 +96,7 @@ Ox.getAsync = function(urls, get, callback) { } function getSerial() { var url = urls.shift(); - Ox.getAsync(url, get, function(result, error) { + getAsync(url, get, function(result, error) { extend(results, result, url); extend(errors, error, url); urls.length ? getSerial() : done(); @@ -85,142 +105,139 @@ Ox.getAsync = function(urls, get, callback) { urls.some(Ox.isArray) ? getSerial() : getParallel(); }; -(function() { - var cache = {}, - head = document.head - || document.getElementsByTagName('head')[0] - || document.documentElement; +var cache = {}, + head = document.head + || document.getElementsByTagName('head')[0] + || document.documentElement; - function getFile(type, url, callback) { - var element, tagValue, typeValue, urlKey; - if (!cache[url]) { - if (!type) { - type = Ox.parseURL(url).pathname.split('.').pop(); - type = type == 'css' ? 'stylesheet' - : type == 'js' ? 'script' : 'image'; - } - if (type == 'image') { - element = new Image(); - element.onerror = onError; - element.onload = onLoad; - element.src = url; - } else { - tagValue = type == 'script' ? 'script' : 'link'; - typeValue = type == 'script' ? 'text/javascript' : 'text/css'; - urlKey = type == 'script' ? 'src' : 'href'; - if (Ox.some( - document.getElementsByTagName(tagValue), - function(element) { - return element[urlKey] == url; - } - )) { - onLoad(); - } else { - element = document.createElement(tagValue); - element.onerror = onError; - element.onload = element.onreadystatechange = onLoad; - element.type = typeValue; - element[urlKey] = url; - if (type == 'stylesheet') { - element.rel = 'stylesheet'; - } - head.appendChild(element); - } - if (type == 'stylesheet') { - //fixme only call if browser does not support onload - // Safari 5 does not fire onload - waitForCSS(); - } - } +function _getFile(type, url, callback) { + var element, tagValue, typeValue, urlKey; + if (!cache[url]) { + if (!type) { + type = Ox.parseURL(url).pathname.split('.').pop(); + type = type == 'css' ? 'stylesheet' + : type == 'js' ? 'script' : 'image'; + } + if (type == 'image') { + element = new Image(); + element.onerror = onError; + element.onload = onLoad; + element.src = url; } else { + tagValue = type == 'script' ? 'script' : 'link'; + typeValue = type == 'script' ? 'text/javascript' : 'text/css'; + urlKey = type == 'script' ? 'src' : 'href'; + if (Ox.some( + document.getElementsByTagName(tagValue), + function(element) { + return element[urlKey] == url; + } + )) { + onLoad(); + } else { + element = document.createElement(tagValue); + element.onerror = onError; + element.onload = element.onreadystatechange = onLoad; + element.type = typeValue; + element[urlKey] = url; + if (type == 'stylesheet') { + element.rel = 'stylesheet'; + } + head.appendChild(element); + } + if (type == 'stylesheet') { + //fixme only call if browser does not support onload + // Safari 5 does not fire onload + waitForCSS(); + } + } + } else { + callback(cache[url], null); + } + function onError() { + callback(null, {code: 404, text: 'Not Found'}); + } + function onLoad() { + if ( + !this || !this.readyState + || this.readyState == 'loaded' || this.readyState == 'complete' + ) { + // for an image, keep a reference to the element + // to keep the image in the browser cache + cache[url] = type == 'image' ? this : true; callback(cache[url], null); } - function onError() { - callback(null, {code: 404, text: 'Not Found'}); - } - function onLoad() { - if ( - !this || !this.readyState - || this.readyState == 'loaded' || this.readyState == 'complete' - ) { - // for an image, keep a reference to the element - // to keep the image in the browser cache - cache[url] = type == 'image' ? this : true; - callback(cache[url], null); - } - } - function waitForCSS() { - var error = false; - try { - element.sheet.cssRule; - } catch (e) { - error = true; - setTimeout(function() { - waitForCSS(); - }, 25); - } - !error && onLoad(); - } } - - function getFiles(type, urls, callback) { - Ox.getAsync(urls, function(url, callback) { - getFile(type, url, callback); - }, callback); + function waitForCSS() { + var error = false; + try { + element.sheet.cssRule; + } catch (e) { + error = true; + setTimeout(function() { + waitForCSS(); + }, 25); + } + !error && onLoad(); } +} - /*@ - Ox.getFile Loads a file (image, script or stylesheet) - (file, callback) -> undefined - file Local path or remote URL, or array of those, or array of such arrays - Multiple files in the same array will be processed simultaneously, - but multiple arrays of files will be processed in that order. - callback Callback function - image DOM element (if the file is an image) - @*/ - Ox.getFile = function(url, callback) { - getFiles(null, url, callback); - }; +function getFiles(type, urls, callback) { + getAsync(urls, function(url, callback) { + _getFile(type, url, callback); + }, callback); +} - /*@ - Ox.getImage Loads an image - (file, callback) -> undefined - file Local path or remote URL, or array of those, or array of such arrays - Multiple files in the same array will be processed simultaneously, - but multiple arrays of files will be processed in that order. - callback Callback function - image DOM element - @*/ - Ox.getImage = function(url, callback) { - getFiles('image', url, callback); - }; +/*@ +Ox.getFile Loads a file (image, script or stylesheet) + (file, callback) -> undefined + file Local path or remote URL, or array of those, or array of such arrays + Multiple files in the same array will be processed simultaneously, + but multiple arrays of files will be processed in that order. + callback Callback function + image DOM element (if the file is an image) +@*/ +export function getFile(url, callback) { + getFiles(null, url, callback); +}; - /*@ - Ox.getScript Loads a script - (file, callback) -> undefined - file Local path or remote URL, or array of those, or array of such arrays - Multiple files in the same array will be processed simultaneously, - but multiple arrays of files will be processed in that order. - callback Callback function - @*/ - Ox.getScript = function(url, callback) { - getFiles('script', url, callback); - }; +/*@ +Ox.getImage Loads an image + (file, callback) -> undefined + file Local path or remote URL, or array of those, or array of such arrays + Multiple files in the same array will be processed simultaneously, + but multiple arrays of files will be processed in that order. + callback Callback function + image DOM element +@*/ +export function getImage(url, callback) { + getFiles('image', url, callback); +}; - /*@ - Ox.getStylesheet Loads a stylesheet - (file, callback) -> undefined - file Local path or remote URL, or array of those, or array of such arrays - Multiple files in the same array will be processed simultaneously, - but multiple arrays of files will be processed in that order. - callback Callback function - @*/ - Ox.getStylesheet = function(url, callback) { - getFiles('stylesheet', url, callback); - }; +/*@ +Ox.getScript Loads a script + (file, callback) -> undefined + file Local path or remote URL, or array of those, or array of such arrays + Multiple files in the same array will be processed simultaneously, + but multiple arrays of files will be processed in that order. + callback Callback function +@*/ +export function getScript(url, callback) { + getFiles('script', url, callback); +}; -}()); +/*@ +Ox.getStylesheet Loads a stylesheet + (file, callback) -> undefined + file Local path or remote URL, or array of those, or array of such arrays + Multiple files in the same array will be processed simultaneously, + but multiple arrays of files will be processed in that order. + callback Callback function +@*/ +export function getStylesheet(url, callback) { + getFiles('stylesheet', url, callback); +}; /*@ Ox.getJSON Get and parse one or more remote JSON files @@ -234,10 +251,10 @@ Ox.getJSON Get and parse one or more remote JSON files code Error code (like `404`) text Error text (like `'Not Found'`) @*/ -Ox.getJSON = function(url, callback, isJSONC) { +export function getJSON(url, callback, isJSONC) { var urls = Ox.makeArray(url); - Ox.getAsync(urls, function(url, callback) { - Ox.get(url, function(data, error) { + getAsync(urls, function(url, callback) { + get(url, function(data, error) { callback(JSON.parse( isJSONC ? Ox.minify(data || '') : data ), error); @@ -258,7 +275,7 @@ Ox.getJSONC Get and parse a remote JSONC file code Error code (like `404`) text Error text (like `'Not Found'`) @*/ -Ox.getJSONC = function(url, callback) { +export function getJSONC(url, callback) { Ox.getJSON(url, callback, true); }; @@ -275,9 +292,9 @@ Ox.getJSONP Get and parse one or more remote JSONP files code Error code (like `404`) text Error text (like `'Not Found'`) @*/ -Ox.getJSONP = function(url, callback) { +export function getJSONP(url, callback) { var urls = Ox.makeArray(url); - Ox.getAsync(urls, function(url, callback) { + getAsync(urls, function(url, callback) { var id = 'callback' + Ox.uid(); Ox.getJSONP[id] = function(data) { delete Ox.getJSONP[id]; @@ -301,7 +318,7 @@ Ox.post post to a remote resource code Status code text Status text @*/ -Ox.post = function(url, data, callback) { +export function post(url, data, callback) { var request = new XMLHttpRequest(); request.open('post', url, true); request.onreadystatechange = function() { diff --git a/source/Ox/js/String.js b/source/Ox/js/String.js index 3f89e93d..6bd7515d 100644 --- a/source/Ox/js/String.js +++ b/source/Ox/js/String.js @@ -1,9 +1,21 @@ 'use strict'; +import * as OxCore from './Core.js'; +import * as OxBase from './Base.js'; +import * as OxCollection from './Collection.js'; + +const Ox = {}; + +Object.assign(Ox, + OxCore, + OxBase, + OxCollection, +); + /*@ Ox.char Alias for String.fromCharCode @*/ -Ox.char = String.fromCharCode; +export const char = String.fromCharCode; /*@ Ox.clean Remove leading, trailing and double whitespace from a string @@ -18,7 +30,7 @@ Ox.clean Remove leading, trailing and double whitespace from a string > Ox.clean(' foo\tbar ') 'foo bar' @*/ -Ox.clean = function(string) { +export function clean(string) { return Ox.filter(Ox.map(string.split('\n'), function(string) { return string.replace(/\s+/g, ' ').trim() || ''; })).join('\n'); @@ -30,7 +42,7 @@ Ox.codePointAt Returns the code point at a given index > Ox.codePointAt('\uD83D\uDCA9', 0) 0x1F4A9 @*/ -Ox.codePointAt = function(string, index) { +export function codePointAt(string, index) { var first, length = string.length, ret, second; if (index >= 0 && index < length) { first = string.charCodeAt(index); @@ -55,7 +67,7 @@ Ox.endsWith Tests if a string ends with a given substring > Ox.endsWith('foobar', 'foo') false @*/ -Ox.endsWith = function(string, substring) { +export function endsWith(string, substring) { string = string.toString(); substring = substring.toString(); return string.slice(string.length - substring.length) == substring; @@ -69,7 +81,7 @@ Ox.fromCodePoint Returns a string for one or more given code points > Ox.fromCodePoint(0x1F4A9) '\uD83D\uDCA9' @*/ -Ox.fromCodePoint = function() { +export function fromCodePoint() { var ret = ''; Ox.forEach(arguments, function(number) { if (number < 0 || number > 0x10FFFF || !Ox.isInt(number)) { @@ -99,7 +111,7 @@ Ox.isValidEmail Tests if a string is a valid e-mail address > Ox.isValidEmail('foo@bar..com') false @*/ -Ox.isValidEmail = function(string) { +export function isValidEmail(string) { return !!/^[0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,64}$/i.test(string); }; @@ -140,7 +152,7 @@ Ox.pad Pad a string to a given length > Ox.pad('foo', -1) '' @*/ -Ox.pad = function(string, position, length, padding) { +export function pad(string, position, length, padding) { var hasPosition = Ox.isString(arguments[1]), isNumber = Ox.isNumber(arguments[0]), last = Ox.last(arguments); @@ -166,7 +178,7 @@ Ox.parseDuration Takes a formatted duration, returns seconds > Ox.parseDuration('1::') 3600 @*/ -Ox.parseDuration = function(string) { +export function parseDuration(string) { return string.split(':').reverse().slice(0, 4).reduce(function(p, c, i) { return p + (parseFloat(c) || 0) * (i == 3 ? 86400 : Math.pow(60, i)); }, 0); @@ -187,7 +199,7 @@ Ox.parsePath Returns the components of a path > Ox.parsePath('.foo') {extension: '', filename: '.foo', pathname: ''} @*/ -Ox.parsePath = function(string) { +export function parsePath(string) { var matches = /^(.+\/)?(.+?(\..+)?)?$/.exec(string); return { pathname: matches[1] || '', @@ -206,7 +218,7 @@ Ox.parseSRT Parses an srt subtitle file > Ox.parseSRT('1\n01:02:00,000 --> 01:02:03,400\nHello World') [{'in': 3720, out: 3723.4, text: 'Hello World'}] @*/ -Ox.parseSRT = function(string, fps) { +export function parseSRT(string, fps) { return string.replace(/\r\n/g, '\n').trim().split('\n\n') .map(function(block) { var lines = block.split('\n'), points; @@ -264,22 +276,35 @@ Ox.parseURL Takes a URL, returns its components > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').search '?a=0&b=1' @*/ -Ox.parseURL = (function() { - var a = document.createElement('a'), - keys = ['hash', 'host', 'hostname', 'origin', - 'pathname', 'port', 'protocol', 'search']; - return function(string) { - var ret = {}; - a.href = string; - keys.forEach(function(key) { - ret[key] = a[key]; - }); - return ret; - }; +export const parseURL = (function() { + const keys = [ + 'hash', 'host', 'hostname', 'origin', + 'pathname', 'port', 'protocol', 'search' + ]; + if (typeof document == 'undefined') { + return function(string) { + const a = new URL(string); + var ret = {}; + keys.forEach(function(key) { + ret[key] = a[key]; + }); + return ret; + } + } else { + var a = document.createElement('a'); + return function(string) { + var ret = {}; + a.href = string; + keys.forEach(function(key) { + ret[key] = a[key]; + }); + return ret; + }; + } }()); // FIXME: can we get rid of this? -Ox.parseUserAgent = function(userAgent) { +export function parseUserAgent(userAgent) { var aliases = { browser: { 'Firefox': /(Fennec|Firebird|Iceweasel|Minefield|Namoroka|Phoenix|SeaMonkey|Shiretoko)/ @@ -440,7 +465,7 @@ Ox.repeat Repeat a value multiple times @*/ // FIXME: see https://github.com/paulmillr/es6-shim/blob/master/es6-shim.js // for a faster version -Ox.repeat = function(value, times) { +export function repeat(value, times) { var ret; if (Ox.isArray(value)) { ret = []; @@ -458,7 +483,7 @@ Ox.splice `[].splice` for strings, returns a new string > Ox.splice('12xxxxx89', 2, 5, 3, 4, 5, 6, 7) '123456789' @*/ -Ox.splice = function(string, index, remove) { +export function splice(string, index, remove) { var array = string.split(''); Array.prototype.splice.apply(array, Ox.slice(arguments, 1)); return array.join(''); @@ -474,7 +499,7 @@ Ox.startsWith Tests if a string ends with a given substring > Ox.startsWith('foobar', 'bar') false @*/ -Ox.startsWith = function(string, substring) { +export function startsWith(string, substring) { string = string.toString(); substring = substring.toString(); return string.slice(0, substring.length) == substring; @@ -489,7 +514,7 @@ Ox.toCamelCase Takes a string with '-', '/' or '_', returns a camelCase stri > Ox.toCamelCase('foo_bar_baz') 'fooBarBaz' @*/ -Ox.toCamelCase = function(string) { +export function toCamelCase(string) { return string.replace(/[\-\/_][a-z]/g, function(string) { return string[1].toUpperCase(); }); @@ -500,7 +525,7 @@ Ox.toDashes Takes a camelCase string, returns a string with dashes > Ox.toDashes('fooBarBaz') 'foo-bar-baz' @*/ -Ox.toDashes = function(string) { +export function toDashes(string) { return string.replace(/[A-Z]/g, function(string) { return '-' + string.toLowerCase(); }); @@ -511,7 +536,7 @@ Ox.toSlashes Takes a camelCase string, returns a string with slashes > Ox.toSlashes('fooBarBaz') 'foo/bar/baz' @*/ -Ox.toSlashes = function(string) { +export function toSlashes(string) { return string.replace(/[A-Z]/g, function(string) { return '/' + string.toLowerCase(); }); @@ -524,7 +549,7 @@ Ox.toTitleCase Returns a string with capitalized words > Ox.toTitleCase('Apple releases iPhone, IBM stock plummets') 'Apple Releases iPhone, IBM Stock Plummets' @*/ -Ox.toTitleCase = function(string) { +export function toTitleCase(string) { return string.split(' ').map(function(value) { var substring = value.slice(1), lowercase = substring.toLowerCase(); @@ -540,7 +565,7 @@ Ox.toUnderscores Takes a camelCase string, returns string with underscores > Ox.toUnderscores('fooBarBaz') 'foo_bar_baz' @*/ -Ox.toUnderscores = function(string) { +export function toUnderscores(string) { return string.replace(/[A-Z]/g, function(string) { return '_' + string.toLowerCase(); }); @@ -562,7 +587,7 @@ Ox.truncate Truncate a string to a given length > Ox.truncate('anticonstitutionellement', 'center', 16, '...') 'anticon...lement' @*/ -Ox.truncate = function(string, position, length, padding) { +export function truncate(string, position, length, padding) { var hasPosition = Ox.isString(arguments[1]), last = Ox.last(arguments); position = hasPosition ? arguments[1] : 'right'; length = hasPosition ? arguments[2] : arguments[1]; @@ -589,7 +614,7 @@ Ox.words Splits a string into words, removing punctuation > Ox.words('Let\'s "split" array-likes into key/value pairs--okay?') ['let\'s', 'split', 'array-likes', 'into', 'key', 'value', 'pairs', 'okay'] @*/ -Ox.words = function(string) { +export function words(string) { var array = string.toLowerCase().split(/\b/), length = array.length, startsWithWord = /\w/.test(array[0]); @@ -635,7 +660,7 @@ Ox.wordwrap Wrap a string at word boundaries > Ox.wordwrap('These are short words', 16, true) 'These are \nshort words' @*/ -Ox.wordwrap = function(string, length) { +export function wordwrap(string, length) { var balanced, lines, max, newline, words; string = String(string); length = length || 80; diff --git a/source/Ox/js/Type.js b/source/Ox/js/Type.js index a3f7843b..efda598b 100644 --- a/source/Ox/js/Type.js +++ b/source/Ox/js/Type.js @@ -1,13 +1,27 @@ 'use strict'; +import * as OxObject from './Object.js'; +import * as OxFunction from './Function.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; + +const Ox = {}; + +Object.assign(Ox, + OxObject, + OxFunction, + OxConstants, + OxMath +); + /*@ Ox.checkType Throws a TypeError if a value is not of a given type (val, type) -> undefined val <*> Any value type Type, or array of types @*/ -Ox.checkType = function(value, type) { - if (!Ox.contains(Ox.makeArray(type), Ox.typeOf(value))) { +export function checkType(value, type) { + if (!Ox.contains(Ox.makeArray(type), typeOf(value))) { throw new TypeError(); } }; @@ -19,8 +33,8 @@ Ox.isArguments Tests if a value is an arguments "array" > Ox.isArguments((function() { return arguments; }())) true @*/ -Ox.isArguments = function(value) { - return Ox.typeOf(value) == 'arguments'; +export function isArguments(value) { + return typeOf(value) == 'arguments'; }; /*@ @@ -36,8 +50,8 @@ Ox.isArray Tests if a value is an array > Ox.isArray({0: 0, length: 1}) false @*/ -Ox.isArray = function(value) { - return Ox.typeOf(value) == 'array'; +export function isArray(value) { + return typeOf(value) == 'array'; }; /*@ @@ -47,8 +61,8 @@ Ox.isBoolean Tests if a value is boolean > Ox.isBoolean(false) true @*/ -Ox.isBoolean = function(value) { - return Ox.typeOf(value) == 'boolean'; +export function isBoolean(value) { + return typeOf(value) == 'boolean'; }; /*@ @@ -58,8 +72,8 @@ Ox.isDate Tests if a value is a date > Ox.isDate(new Date()) true @*/ -Ox.isDate = function(value) { - return Ox.typeOf(value) == 'date'; +export function isDate(value) { + return typeOf(value) == 'date'; }; /*@ @@ -69,7 +83,7 @@ Ox.isElement Tests if a value is a DOM element > Ox.isElement(document.createElement('a')) true @*/ -Ox.isElement = function(value) { +export function isElement(value) { return Ox.endsWith(Ox.typeOf(value), 'element'); }; @@ -83,8 +97,8 @@ Ox.isEmpty Tests if a value is an empty array, object or string true > Ox.isEmpty('') true - > Ox.isEmpty(document.getElementsByTagName('')) - true + > Ox.isEmpty(document.getElementsByTagName('')) + true > Ox.isEmpty(function() {}) false > Ox.isEmpty(false) @@ -96,7 +110,7 @@ Ox.isEmpty Tests if a value is an empty array, object or string > Ox.isEmpty() false @*/ -Ox.isEmpty = function(value) { +export function isEmpty(value) { return Ox.len(value) === 0; }; @@ -157,12 +171,12 @@ Ox.isEqual Tests if two values are equal > Ox.isEqual(void 0, void 0) true @*/ -Ox.isEqual = function(a, b) { - var ret = false, type = Ox.typeOf(a); +export function isEqual(a, b) { + var ret = false, type = typeOf(a); if (a === b) { // 0 === -0, but not equal ret = a !== 0 || 1 / a === 1 / b; - } else if (type == Ox.typeOf(b)) { + } else if (type == typeOf(b)) { // NaN !== NaN, but equal if (a == b || a !== a) { ret = true; @@ -193,8 +207,8 @@ Ox.isError Tests if a value is an error > Ox.isError(new Error()) true @*/ -Ox.isError = function(value) { - return Ox.typeOf(value) == 'error'; +export function isError(value) { + return typeOf(value) == 'error'; }; /*@ @@ -206,8 +220,8 @@ Ox.isFunction Tests if a value is a function > Ox.isFunction(new RegExp()) false @*/ -Ox.isFunction = function(value) { - return Ox.typeOf(value) == 'function'; +export function isFunction(value) { + return typeOf(value) == 'function'; }; /*@ @@ -221,8 +235,8 @@ Ox.isInfinity Tests if a value is positive or negative Infinity > Ox.isInfinity(NaN) false @*/ -Ox.isInfinity = function(value) { - return Ox.typeOf(value) == 'number' && !isFinite(value) && !Ox.isNaN(value); +export function isInfinity(value) { + return typeOf(value) == 'number' && !isFinite(value) && !Ox.isNaN(value); }; /*@ @@ -236,7 +250,7 @@ Ox.isInt Tests if a value is an integer > Ox.isInt(Infinity) false @*/ -Ox.isInt = function(value) { +export function isInt(value) { return isFinite(value) && value === Math.floor(value); }; @@ -247,7 +261,7 @@ Ox.isInvalidDate Tests if a value is an invalid date > Ox.isInvalidDate(new Date('foo')) true @*/ -Ox.isInvalidDate = function(value) { +export function isInvalidDate(value) { return Ox.isDate(value) && Ox.isNaN(value.getTime()); }; @@ -258,7 +272,7 @@ Ox.isNaN Tests if a value is `NaN` > Ox.isNaN(NaN) true @*/ -Ox.isNaN = function(value) { +export function isNaN(value) { return value !== value; }; @@ -268,8 +282,8 @@ Ox.isNodeList Tests if a value is a nodelist > Ox.isNodeList(document.getElementsByTagName('a')) true @*/ -Ox.isNodeList = function(value) { - return Ox.typeOf(value) == 'nodelist'; +export function isNodeList(value) { + return typeOf(value) == 'nodelist'; }; /*@ @@ -279,8 +293,8 @@ Ox.isNull Tests if a value is `null` > Ox.isNull(null) true @*/ -Ox.isNull = function(value) { - return Ox.typeOf(value) == 'null'; +export function isNull(value) { + return typeOf(value) == 'null'; }; /*@ @@ -296,8 +310,8 @@ Ox.isNumber Tests if a value is a number > Ox.isNumber(NaN) true @*/ -Ox.isNumber = function(value) { - return Ox.typeOf(value) == 'number'; +export function isNumber(value) { + return typeOf(value) == 'number'; }; /*@ @@ -315,8 +329,8 @@ Ox.isObject Tests if a value is a an object > Ox.isObject(/ /) false @*/ -Ox.isObject = function(value) { - return Ox.typeOf(value) == 'object'; +export function isObject(value) { + return typeOf(value) == 'object'; }; /*@ @@ -338,9 +352,9 @@ Ox.isPrimitive Tests if a value is a primitive > Ox.isPrimitive({}) false @*/ -Ox.isPrimitive = function(value) { +export function isPrimitive(value) { return Ox.contains( - ['boolean', 'null', 'number', 'string', 'undefined'], Ox.typeOf(value) + ['boolean', 'null', 'number', 'string', 'undefined'], typeOf(value) ); }; @@ -351,8 +365,8 @@ Ox.isRegExp Tests if a value is a regular expression > Ox.isRegExp(new RegExp()) true @*/ -Ox.isRegExp = function(value) { - return Ox.typeOf(value) == 'regexp'; +export function isRegExp(value) { + return typeOf(value) == 'regexp'; }; /*@ @@ -362,8 +376,8 @@ Ox.isString Tests if a value is a string > Ox.isString('') true @*/ -Ox.isString = function(value) { - return Ox.typeOf(value) == 'string'; +export function isString(value) { + return typeOf(value) == 'string'; }; /*@ @@ -373,8 +387,8 @@ Ox.isUndefined Tests if a value is undefined > Ox.isUndefined() true @*/ -Ox.isUndefined = function(value) { - return Ox.typeOf(value) == 'undefined'; +export function isUndefined(value) { + return typeOf(value) == 'undefined'; }; /*@ @@ -384,7 +398,7 @@ Ox.isValidDate Tests if a value is a valid date > Ox.isValidDate(new Date()) true @*/ -Ox.isValidDate = function(value) { +export function isValidDate(value) { return Ox.isDate(value) && !Ox.isNaN(value.getTime()); }; @@ -433,7 +447,7 @@ Ox.typeOf Returns the type of a value > Ox.typeOf(JSON) 'json' @*/ -Ox.typeOf = function(value) { +export function typeOf(value) { return Object.prototype.toString.call(value).slice(8, -1).toLowerCase(); }; // Firefox 3.6 returns 'Object' for arguments, @@ -442,13 +456,13 @@ Ox.typeOf = function(value) { // Mobile Safari returns 'DOMWindow' for null and undefined // Firefox 30+ returns 'window' for window if ( - Ox.typeOf((function() { return arguments; }())) != 'arguments' - || Ox.typeOf(document.getElementsByTagName('a')) != 'nodelist' - || Ox.typeOf(null) != 'null' - || Ox.typeOf(window) != 'global' - || Ox.typeOf() != 'undefined' + typeOf((function() { return arguments; }())) != 'arguments' + || (typeof document !== 'undefined' && typeOf(document.getElementsByTagName('a')) != 'nodelist') + || typeof null != 'null' + || typeof window != 'global' + || typeOf() != 'undefined' ) { - Ox.typeOf = function(value) { + typeOf = function(value) { var type = Object.prototype.toString.call( value ).slice(8, -1).toLowerCase(); @@ -456,7 +470,7 @@ if ( type = 'null'; } else if (value === void 0) { type = 'undefined'; - } else if (value === window) { + } else if (typeof window !== 'undefined' && value === window) { type = 'global'; } else if (type == 'object' && typeof value.callee == 'function') { type = 'arguments'; diff --git a/source/Ox/js/Video.js b/source/Ox/js/Video.js index a8f2252a..53b04ae6 100644 --- a/source/Ox/js/Video.js +++ b/source/Ox/js/Video.js @@ -1,11 +1,35 @@ 'use strict'; +import * as OxCore from './Core.js'; +import * as OxType from './Type.js'; +import * as OxArray from './Array.js'; +import * as OxObject from './Object.js'; +import * as OxCollection from './Collection.js'; +import * as OxFunction from './Function.js'; +import * as OxConstants from './Constants.js'; +import * as OxMath from './Math.js'; +import * as OxString from './String.js'; + +const Ox = {}; + +Object.assign(Ox, + OxCore, + OxType, + OxArray, + OxObject, + OxFunction, + OxCollection, + OxConstants, + OxMath, + OxString, +); + /*@ Ox.getVideoFormat Get supported video format (formats) -> List of supported formats format First supported format form list @*/ -Ox.getVideoFormat = function(formats) { +export function getVideoFormat(formats) { var aliases = { mp4: 'h264', m4v: 'h264', diff --git a/source/UI/index.js b/source/UI/index.js new file mode 100644 index 00000000..2f39c960 --- /dev/null +++ b/source/UI/index.js @@ -0,0 +1,548 @@ +'use strict'; + +import * as AudioAudioElement from './js/Audio/AudioElement.js'; +import * as AudioAudioPlayer from './js/Audio/AudioPlayer.js'; +import * as BarBar from './js/Bar/Bar.js'; +import * as BarProgressbar from './js/Bar/Progressbar.js'; +import * as BarResizebar from './js/Bar/Resizebar.js'; +import * as BarTabbar from './js/Bar/Tabbar.js'; +import * as CalendarCalendarEditor from './js/Calendar/CalendarEditor.js'; +import * as CalendarCalendar from './js/Calendar/Calendar.js'; +import * as CodeDocPage from './js/Code/DocPage.js'; +import * as CodeDocPanel from './js/Code/DocPanel.js'; +import * as CodeExamplePage from './js/Code/ExamplePage.js'; +import * as CodeExamplePanel from './js/Code/ExamplePanel.js'; +import * as CodeSourceViewer from './js/Code/SourceViewer.js'; +import * as CodeSyntaxHighlighter from './js/Code/SyntaxHighlighter.js'; +import * as CoreAPI from './js/Core/API.js'; +import * as CoreApp from './js/Core/App.js'; +import * as CoreClipboard from './js/Core/Clipboard.js'; +import * as CoreContainer from './js/Core/Container.js'; +import * as CoreCookies from './js/Core/Cookies.js'; +import * as CoreElement from './js/Core/Element.js'; +import * as CoreEvent from './js/Core/Event.js'; +import * as CoreFocus from './js/Core/Focus.js'; +import * as CoreFullscreen from './js/Core/Fullscreen.js'; +import * as CoreGarbageCollection from './js/Core/GarbageCollection.js'; +import * as CoreHistory from './js/Core/History.js'; +import * as CoreLoadingIcon from './js/Core/LoadingIcon.js'; +import * as CoreLoadingScreen from './js/Core/LoadingScreen.js'; +import * as CoreRequest from './js/Core/Request.js'; +import * as CoreTheme from './js/Core/Theme.js'; +import * as CoreUI from './js/Core/UI.js'; +import * as CoreURL from './js/Core/URL.js'; +import * as FormArrayEditable from './js/Form/ArrayEditable.js'; +import * as FormArrayInput from './js/Form/ArrayInput.js'; +import * as FormButtonGroup from './js/Form/ButtonGroup.js'; +import * as FormButton from './js/Form/Button.js'; +import * as FormCheckboxGroup from './js/Form/CheckboxGroup.js'; +import * as FormCheckbox from './js/Form/Checkbox.js'; +import * as FormColorInput from './js/Form/ColorInput.js'; +import * as FormColorPicker from './js/Form/ColorPicker.js'; +import * as FormDateInput from './js/Form/DateInput.js'; +import * as FormDateTimeInput from './js/Form/DateTimeInput.js'; +import * as FormEditableContent from './js/Form/EditableContent.js'; +import * as FormEditable from './js/Form/Editable.js'; +import * as FormFileButton from './js/Form/FileButton.js'; +import * as FormFileInput from './js/Form/FileInput.js'; +import * as FormFilter from './js/Form/Filter.js'; +import * as FormFormElementGroup from './js/Form/FormElementGroup.js'; +import * as FormFormItem from './js/Form/FormItem.js'; +import * as FormForm from './js/Form/Form.js'; +import * as FormFormPanel from './js/Form/FormPanel.js'; +import * as FormInputGroup from './js/Form/InputGroup.js'; +import * as FormInput from './js/Form/Input.js'; +import * as FormInsertHTMLDialog from './js/Form/InsertHTMLDialog.js'; +import * as FormLabel from './js/Form/Label.js'; +import * as FormObjectArrayInput from './js/Form/ObjectArrayInput.js'; +import * as FormObjectInput from './js/Form/ObjectInput.js'; +import * as FormOptionGroup from './js/Form/OptionGroup.js'; +import * as FormPicker from './js/Form/Picker.js'; +import * as FormPlaceInput from './js/Form/PlaceInput.js'; +import * as FormPlacePicker from './js/Form/PlacePicker.js'; +import * as FormRange from './js/Form/Range.js'; +import * as FormSelectInput from './js/Form/SelectInput.js'; +import * as FormSelect from './js/Form/Select.js'; +import * as FormSpreadsheet from './js/Form/Spreadsheet.js'; +import * as FormTimeInput from './js/Form/TimeInput.js'; +import * as ImageImageElement from './js/Image/ImageElement.js'; +import * as ImageImageViewer from './js/Image/ImageViewer.js'; +import * as ListChart from './js/List/Chart.js'; +import * as ListColumnList from './js/List/ColumnList.js'; +import * as ListCustomList from './js/List/CustomList.js'; +import * as ListIconItem from './js/List/IconItem.js'; +import * as ListIconList from './js/List/IconList.js'; +import * as ListInfoList from './js/List/InfoList.js'; +import * as ListListItem from './js/List/ListItem.js'; +import * as ListList from './js/List/List.js'; +import * as ListSortList from './js/List/SortList.js'; +import * as ListTableList from './js/List/TableList.js'; +import * as ListTreeList from './js/List/TreeList.js'; +import * as MapMapEditor from './js/Map/MapEditor.js'; +import * as MapMapImage from './js/Map/MapImage.js'; +import * as MapMap from './js/Map/Map.js'; +import * as MapMapMarkerImage from './js/Map/MapMarkerImage.js'; +import * as MapMapMarker from './js/Map/MapMarker.js'; +import * as MapMapPlace from './js/Map/MapPlace.js'; +import * as MapMapRectangle from './js/Map/MapRectangle.js'; +import * as MapMapRectangleMarker from './js/Map/MapRectangleMarker.js'; +import * as MenuMainMenu from './js/Menu/MainMenu.js'; +import * as MenuMenuButton from './js/Menu/MenuButton.js'; +import * as MenuMenuItem from './js/Menu/MenuItem.js'; +import * as MenuMenu from './js/Menu/Menu.js'; +import * as PanelCollapsePanel from './js/Panel/CollapsePanel.js'; +import * as PanelSlidePanel from './js/Panel/SlidePanel.js'; +import * as PanelSplitPanel from './js/Panel/SplitPanel.js'; +import * as PanelTabPanel from './js/Panel/TabPanel.js'; +import * as VideoAnnotationFolder from './js/Video/AnnotationFolder.js'; +import * as VideoAnnotationPanel from './js/Video/AnnotationPanel.js'; +import * as VideoBlockVideoTimeline from './js/Video/BlockVideoTimeline.js'; +import * as VideoClipPanel from './js/Video/ClipPanel.js'; +import * as VideoLargeVideoTimeline from './js/Video/LargeVideoTimeline.js'; +import * as VideoSmallVideoTimelineImage from './js/Video/SmallVideoTimelineImage.js'; +import * as VideoSmallVideoTimeline from './js/Video/SmallVideoTimeline.js'; +import * as VideoVideoAnnotationPanel from './js/Video/VideoAnnotationPanel.js'; +import * as VideoVideoEditPanel from './js/Video/VideoEditPanel.js'; +import * as VideoVideoElement from './js/Video/VideoElement.js'; +import * as VideoVideoPlayer from './js/Video/VideoPlayer.js'; +import * as VideoVideoPlayerMenu from './js/Video/VideoPlayerMenu.js'; +import * as VideoVideoPlayerPanel from './js/Video/VideoPlayerPanel.js'; +import * as VideoVideoPreview from './js/Video/VideoPreview.js'; +import * as VideoVideoTimelinePanel from './js/Video/VideoTimelinePanel.js'; +import * as VideoVideoTimelinePlayer from './js/Video/VideoTimelinePlayer.js'; +import * as VideoYouTubeElement from './js/Video/YouTubeElement.js'; +import * as WindowDialog from './js/Window/Dialog.js'; +import * as WindowLayer from './js/Window/Layer.js'; +import * as WindowSortDialog from './js/Window/SortDialog.js'; +import * as WindowTooltip from './js/Window/Tooltip.js'; + + +const UI = {} + +Object.assign(UI, + AudioAudioElement, + AudioAudioPlayer, + BarBar, + BarProgressbar, + BarResizebar, + BarTabbar, + CalendarCalendarEditor, + CalendarCalendar, + CodeDocPage, + CodeDocPanel, + CodeExamplePage, + CodeExamplePanel, + CodeSourceViewer, + CodeSyntaxHighlighter, + CoreAPI, + CoreApp, + CoreClipboard, + CoreContainer, + CoreCookies, + CoreElement, + CoreEvent, + CoreFocus, + CoreFullscreen, + CoreGarbageCollection, + CoreHistory, + CoreLoadingIcon, + CoreLoadingScreen, + CoreRequest, + CoreTheme, + CoreUI, + CoreURL, + FormArrayEditable, + FormArrayInput, + FormButtonGroup, + FormButton, + FormCheckboxGroup, + FormCheckbox, + FormColorInput, + FormColorPicker, + FormDateInput, + FormDateTimeInput, + FormEditableContent, + FormEditable, + FormFileButton, + FormFileInput, + FormFilter, + FormFormElementGroup, + FormFormItem, + FormForm, + FormFormPanel, + FormInputGroup, + FormInput, + FormInsertHTMLDialog, + FormLabel, + FormObjectArrayInput, + FormObjectInput, + FormOptionGroup, + FormPicker, + FormPlaceInput, + FormPlacePicker, + FormRange, + FormSelectInput, + FormSelect, + FormSpreadsheet, + FormTimeInput, + ImageImageElement, + ImageImageViewer, + ListChart, + ListColumnList, + ListCustomList, + ListIconItem, + ListIconList, + ListInfoList, + ListListItem, + ListList, + ListSortList, + ListTableList, + ListTreeList, + MapMapEditor, + MapMapImage, + MapMap, + MapMapMarkerImage, + MapMapMarker, + MapMapPlace, + MapMapRectangle, + MapMapRectangleMarker, + MenuMainMenu, + MenuMenuButton, + MenuMenuItem, + MenuMenu, + PanelCollapsePanel, + PanelSlidePanel, + PanelSplitPanel, + PanelTabPanel, + VideoAnnotationFolder, + VideoAnnotationPanel, + VideoBlockVideoTimeline, + VideoClipPanel, + VideoLargeVideoTimeline, + VideoSmallVideoTimelineImage, + VideoSmallVideoTimeline, + VideoVideoAnnotationPanel, + VideoVideoEditPanel, + VideoVideoElement, + VideoVideoPlayer, + VideoVideoPlayerMenu, + VideoVideoPlayerPanel, + VideoVideoPreview, + VideoVideoTimelinePanel, + VideoVideoTimelinePlayer, + VideoYouTubeElement, + WindowDialog, + WindowLayer, + WindowSortDialog, + WindowTooltip, +); + +export default UI; +export { UI }; + +if (typeof window !== 'undefined') { + window.Ox.UI = UI + Ox.load.UI = function(options, callback) { + + options = Ox.extend({ + hideScreen: true, + loadCSS: true, + loadThemes: true, + showScreen: false, + theme: 'oxlight' + }, options); + + var browsers = [ + { + name: 'Chrome Frame', + url: 'http://www.google.com/chromeframe/' + }, + { + name: 'Chrome', + regexp: /Chrome\/(\d+)\./, + url: 'http://www.google.com/chrome/', + version: 10 + }, + { + name: 'Firefox', + regexp: /Firefox\/(\d+)\./, + url: 'http://www.mozilla.org/firefox/', + version: 4 + }, + { + name: 'Safari', + regexp: /Version\/(\d+).*? Safari/, + url: 'http://www.apple.com/safari/', + version: 5 + }, + { + name: 'WebKit', + regexp: /AppleWebKit\/(\d+)\./, + version: 534 + }, + { + name: 'Googlebot', + regexp: /Googlebot\/(\d+)\./, + version: 2 + }, + { + name: 'YandexBot', + regexp: /YandexBot\/(\d+)\./, + version: 3 + }, + { + name: 'YandexMobileBot', + regexp: /YandexMobileBot\/(\d+)\./, + version: 3 + }, + { + name: 'Internet Explorer', + url: 'http://windows.microsoft.com/en-US/internet-explorer/products/ie/home', + version: 9 + } + ], + browserSupported = false, + isInternetExplorer = /MSIE/.test(navigator.userAgent); + + browsers.forEach(function(browser) { + var match = browser.regexp && browser.regexp.exec(navigator.userAgent); + if (match && match[1] >= browser.version) { + browserSupported = true; + } + }); + + Ox.UI = {}; + + Ox.UI.LoadingScreen = (function() { + + var $body = Ox.$('body'), + $screen = Ox.$('
') + .addClass('OxLoadingScreen') + .css({ + position: 'absolute', + left: 0, + top: 0, + right: 0, + bottom: 0, + padding: '4px', + background: 'rgb(' + ( + options.theme == 'oxlight' ? '240, 240, 240' + : options.theme == 'oxmedium' ? '144, 144, 144' + : '16, 16, 16' + ) + ')', + opacity: 1, + zIndex: 1000 + }), + css = { + position: 'absolute', + left: 0, + top: 0, + right: 0, + bottom: 0, + margin: 'auto', + MozUserSelect: 'none', + WebkitUserSelect: 'none' + }, + loadingInterval, + $icon, + deg = 0; + + browserSupported ? showIcon() : showWarning(); + + function showIcon() { + /* + // SVG transform performs worse than CSS transform + var src = Ox.PATH + 'UI/themes/' + options.theme + '/svg/symbolLoadingAnimated.svg' + Ox.getFile(src, function() { + Ox.$('') + .attr({ + src: src + }) + .css(Ox.extend({ + width: '32px', + height: '32px' + }, css)) + .on({ + mousedown: function(e) { + e.preventDefault(); + } + }) + .appendTo(div); + }); + */ + var src = Ox.PATH + 'UI/themes/' + options.theme + '/svg/symbolLoading.svg' + Ox.getFile(src, function() { + $icon = Ox.$('') + .attr({ + src: src + }) + .css(Ox.extend({ + width: '32px', + height: '32px' + }, css)) + .on({ + mousedown: function(e) { + e.preventDefault() + } + }) + .appendTo($screen); + }); + } + + function showWarning() { + var counter = 0; + browsers = browsers.filter(function(browser) { + return browser.url; + }); + isInternetExplorer ? browsers.pop() : browsers.shift(); + browsers.forEach(function(browser) { + browser.src = Ox.PATH + 'UI/png/browser' + browser.name.replace(' ', '') + '128.png'; + Ox.getFile(browser.src, function() { + ++counter == browsers.length && showIcons(); + }); + }); + function showIcons() { + var $box = Ox.$('
') + .css(Ox.extend({ + width: (browsers.length * 72) + 'px', + height: '72px' + }, css)) + .appendTo($screen); + browsers.forEach(function(browser, i) { + Ox.$('') + .attr({ + href: browser.url, + title: ( + browser.name == 'Chrome Frame' + ? Ox._('Install') : Ox._('Download') + ) + ' ' + browser.name + }) + .css({ + position: 'absolute', + left: (i * 72) + 'px', + width: '72px', + height: '72px' + }) + .append( + Ox.$('') + .attr({ + src: browser.src + }) + .css(Ox.extend({ + width: '64px', + height: '64px', + border: 0, + cursor: 'pointer' + }, css)) + .on({ + mousedown: function(e) { + e.preventDefault(); + } + }) + ) + .appendTo($box); + }); + } + } + + return { + hide: function() { + $('.OxLoadingScreen').animate({ + opacity: browserSupported ? 0 : 0.9 + }, 1000, function() { + if (browserSupported) { + clearInterval(loadingInterval); + loadingInterval = null; + $screen.remove(); + } else { + $screen.on({ + click: function() { + $screen.remove(); + } + }); + } + }); + }, + show: function() { + if (!loadingInterval) { + loadingInterval = setInterval(function() { + if ($icon) { + deg = (deg + 30) % 360; + $icon.css({ + MozTransform: 'rotate(' + deg + 'deg)', + OTransform: 'rotate(' + deg + 'deg)', + WebkitTransform: 'rotate(' + deg + 'deg)', + transform: 'rotate(' + deg + 'deg)' + }); + } + }, 83); + } + $screen.appendTo($body); + } + }; + + }()); + + Ox.documentReady(function() { + Ox.$('body').addClass('OxTheme' + Ox.toTitleCase(options.theme)); + options.showScreen && Ox.UI.LoadingScreen.show(); + }); + + loadUI(); + + function loadUI() { + + var path = Ox.PATH + 'UI/jquery/jquery.js?' + Ox.VERSION; + Ox.getFile(path, function() { + path = Ox.PATH + 'UI/json/UI.json?' + Ox.VERSION; + Ox.getJSON(path, function(data) { + var counter = 0, length; + if (!options.loadCSS) { + data.files = data.files.filter(function(file) { + return !Ox.endsWith(file, '.css'); + }); + } + if (!options.loadThemes) { + data.files = data.files.filter(function(file) { + return !Ox.contains(file, '/themes/'); + }); + } + length = data.files.length; + Ox.UI.IMAGES = data.images; + Ox.UI.THEMES = {}; + data.files.forEach(function(file) { + path = Ox.PATH + file + '?' + Ox.VERSION; + if (/\.jsonc$/.test(file)) { + Ox.getJSONC(path, function(data) { + var theme = /\/themes\/(\w+)\/json\//.exec(file)[1]; + Ox.UI.THEMES[theme] = data; + ++counter == length && initUI(); + }); + } else { + Ox.getFile(path, function() { + ++counter == length && initUI(); + }); + } + }); + }); + }); + + } + + function initUI() { + + Ox.documentReady(function() { + // fixme: is this the right place to do this? + $.browser.mozilla && Ox.$document.on('dragstart', function() { + return false; + }); + if (options.showScreen && options.hideScreen) { + Ox.UI.LoadingScreen.hide(); + } + callback(browserSupported); + }); + + } + + }; +}