Compare commits
No commits in common. "b2056d4e2b9f94f1c4e0c6bb4bdd85e270085ea9" and "a8a7dc9445626726fad3b4a65d4fb1fa65680dfc" have entirely different histories.
b2056d4e2b
...
a8a7dc9445
27 changed files with 334 additions and 3764 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -3,13 +3,3 @@ node_modules/
|
|||
dist/
|
||||
.vite/
|
||||
coverage/
|
||||
|
||||
# Build artifacts
|
||||
dev/
|
||||
min/
|
||||
|
||||
# Generated test files
|
||||
test/extracted/
|
||||
|
||||
# Temporary test files
|
||||
test-build.html
|
||||
|
|
|
|||
312
min/Ox.js
312
min/Ox.js
File diff suppressed because one or more lines are too long
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -17,7 +17,6 @@
|
|||
"postcss-import": "^16.0.0",
|
||||
"postcss-nesting": "^12.0.2",
|
||||
"rollup": "^4.9.5",
|
||||
"terser": "^5.26.0",
|
||||
"vite": "^5.0.11",
|
||||
"vitest": "^1.2.1"
|
||||
}
|
||||
|
|
@ -2242,6 +2241,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
|
|
@ -2998,7 +2998,8 @@
|
|||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
|
|
@ -3106,7 +3107,8 @@
|
|||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
|
|
@ -4970,6 +4972,7 @@
|
|||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -4988,6 +4991,7 @@
|
|||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
|
|
@ -5178,6 +5182,7 @@
|
|||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
|
||||
"integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "node scripts/build.js",
|
||||
"build:vite": "vite build --config vite.config.build.js",
|
||||
"build": "vite build",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"extract-tests": "node scripts/extract-tests.js",
|
||||
|
|
@ -46,7 +45,6 @@
|
|||
"postcss-import": "^16.0.0",
|
||||
"postcss-nesting": "^12.0.2",
|
||||
"rollup": "^4.9.5",
|
||||
"terser": "^5.26.0",
|
||||
"vite": "^5.0.11",
|
||||
"vitest": "^1.2.1"
|
||||
}
|
||||
|
|
|
|||
105
scripts/build.js
105
scripts/build.js
|
|
@ -1,105 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Build script for OxJS
|
||||
* Generates ESM, UMD, and minified builds
|
||||
*/
|
||||
|
||||
const { build } = require('vite');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { minify } = require('terser');
|
||||
|
||||
async function buildOx() {
|
||||
console.log('Building OxJS...\n');
|
||||
|
||||
// Step 1: Build ESM and UMD formats using Vite
|
||||
console.log('1. Building ES modules and UMD...');
|
||||
await build({
|
||||
configFile: path.resolve(__dirname, '../vite.config.build.js')
|
||||
});
|
||||
|
||||
// Step 2: Create minified version for script tag usage (min/Ox.js)
|
||||
console.log('\n2. Creating minified build...');
|
||||
|
||||
// Read the UMD build
|
||||
const umdPath = path.resolve(__dirname, '../dist/ox.umd.js');
|
||||
const umdCode = fs.readFileSync(umdPath, 'utf-8');
|
||||
|
||||
// Minify with Terser
|
||||
const minified = await minify(umdCode, {
|
||||
compress: {
|
||||
drop_console: false, // Keep console for debugging
|
||||
drop_debugger: true,
|
||||
pure_funcs: ['console.log']
|
||||
},
|
||||
mangle: {
|
||||
reserved: ['Ox'] // Don't mangle the main Ox object
|
||||
},
|
||||
format: {
|
||||
comments: false,
|
||||
preamble: '/* OxJS v0.2.0 | (c) 2024 0x2620 | MIT License | oxjs.org */'
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure min directory exists
|
||||
const minDir = path.resolve(__dirname, '../min');
|
||||
if (!fs.existsSync(minDir)) {
|
||||
fs.mkdirSync(minDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write minified file
|
||||
fs.writeFileSync(path.join(minDir, 'Ox.js'), minified.code);
|
||||
|
||||
// Step 3: Copy the minified file to be compatible with old path structure
|
||||
console.log('\n3. Creating backward compatible structure...');
|
||||
|
||||
// Create dev symlink if it doesn't exist
|
||||
const devPath = path.resolve(__dirname, '../dev');
|
||||
if (!fs.existsSync(devPath)) {
|
||||
fs.symlinkSync('source', devPath, 'dir');
|
||||
}
|
||||
|
||||
// Step 4: Generate build info
|
||||
const buildInfo = {
|
||||
version: '0.2.0',
|
||||
date: new Date().toISOString(),
|
||||
files: {
|
||||
'dist/ox.esm.js': getFileSize('../dist/ox.esm.js'),
|
||||
'dist/ox.umd.js': getFileSize('../dist/ox.umd.js'),
|
||||
'min/Ox.js': getFileSize('../min/Ox.js')
|
||||
}
|
||||
};
|
||||
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, '../dist/build-info.json'),
|
||||
JSON.stringify(buildInfo, null, 2)
|
||||
);
|
||||
|
||||
console.log('\n✅ Build complete!\n');
|
||||
console.log('Generated files:');
|
||||
console.log(` dist/ox.esm.js (${buildInfo.files['dist/ox.esm.js']})`);
|
||||
console.log(` dist/ox.umd.js (${buildInfo.files['dist/ox.umd.js']})`);
|
||||
console.log(` min/Ox.js (${buildInfo.files['min/Ox.js']})`);
|
||||
}
|
||||
|
||||
function getFileSize(relativePath) {
|
||||
const filePath = path.resolve(__dirname, relativePath);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const stats = fs.statSync(filePath);
|
||||
return formatBytes(stats.size);
|
||||
}
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
// Run build
|
||||
buildOx().catch(error => {
|
||||
console.error('Build failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Test with a known file that has tests
|
||||
const testFile = path.join(__dirname, '../source/Ox/js/Array.js');
|
||||
const source = fs.readFileSync(testFile, 'utf-8');
|
||||
|
||||
// Regular expressions from Ox.doc
|
||||
const re = {
|
||||
multiline: /\/\*@([\w\W]+?)@?\*\//g,
|
||||
singleline: /\/\/@\s*(.*?)\s*$/gm,
|
||||
test: /^\s*>\s+(.+)$/,
|
||||
expected: /^\s*([^>].*)$/,
|
||||
item: /^(.+?)\s+<(.+?)>\s+(.+?)$/,
|
||||
};
|
||||
|
||||
// Find all multiline comments
|
||||
let match;
|
||||
let found = 0;
|
||||
console.log('Searching for /*@ comments in Array.js...\n');
|
||||
|
||||
while ((match = re.multiline.exec(source)) !== null) {
|
||||
found++;
|
||||
const content = match[1];
|
||||
const lines = content.split('\n');
|
||||
const firstLine = lines[0].trim();
|
||||
|
||||
console.log(`Found comment #${found}:`);
|
||||
console.log('First line:', firstLine);
|
||||
|
||||
// Look for tests
|
||||
let testCount = 0;
|
||||
for (const line of lines) {
|
||||
if (line.match(re.test)) {
|
||||
testCount++;
|
||||
console.log(' Test:', line);
|
||||
}
|
||||
}
|
||||
console.log(` Total tests in this block: ${testCount}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
console.log(`\nTotal doc blocks found: ${found}`);
|
||||
|
|
@ -47,26 +47,10 @@ function parseDocComments(source, filename) {
|
|||
*/
|
||||
function parseDocContent(content, filename) {
|
||||
const lines = content.split('\n');
|
||||
const firstLine = lines[0].trim();
|
||||
const itemMatch = firstLine.match(re.item);
|
||||
|
||||
// Find the first non-empty line that matches the item pattern
|
||||
let itemMatch = null;
|
||||
let itemName = 'Unknown';
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed) {
|
||||
itemMatch = trimmed.match(re.item);
|
||||
if (itemMatch) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!itemMatch) {
|
||||
// If no item match, still try to extract tests with a generic name
|
||||
// This handles cases where tests are in script blocks or without proper headers
|
||||
itemMatch = ['', filename.replace(/.*\//, '').replace('.js', ''), 'tests', ''];
|
||||
}
|
||||
if (!itemMatch) return null;
|
||||
|
||||
const doc = {
|
||||
name: itemMatch[1],
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@
|
|||
*/
|
||||
|
||||
import { isArray, isBoolean, isEqual, isFunction, isNumber, isObject, isString, isUndefined, typeOf } from './Type.js';
|
||||
import { extend, filter, forEach, len, map, slice, some, max, min } from './Collection.js';
|
||||
import { clone, contains, getset, isEmpty, makeObject, values, keyOf } from './Object.js';
|
||||
import { extend, filter, forEach, len, map, some } from './Collection.js';
|
||||
import { clone, getset, isEmpty, makeObject } from './Object.js';
|
||||
import { cache, identity } from './Function.js';
|
||||
import { loop } from './Core.js';
|
||||
import { MAX_LATITUDE } from './Constants.js';
|
||||
import { deg, mod, random } from './Math.js';
|
||||
import { char, splice, clean, repeat } from './String.js';
|
||||
import { deg, mod } from './Math.js';
|
||||
|
||||
/**
|
||||
* Turns an array into a list API
|
||||
|
|
@ -384,90 +382,6 @@ function getSortValues(array) {
|
|||
return values;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.last <f> Gets or sets the last element of an array
|
||||
Unlike `arrayWithALongName[arrayWithALongName.length - 1]`,
|
||||
`Ox.last(arrayWithALongName)` is short.
|
||||
<script>
|
||||
Ox.test.array = [1, 2, 3];
|
||||
</script>
|
||||
> Ox.last(Ox.test.array)
|
||||
3
|
||||
> Ox.last(Ox.test.array, 4)
|
||||
[1, 2, 4]
|
||||
> Ox.test.array
|
||||
[1, 2, 4]
|
||||
> Ox.last('123')
|
||||
'3'
|
||||
@*/
|
||||
export function last(array, value) {
|
||||
var ret;
|
||||
if (arguments.length == 1) {
|
||||
ret = array[array.length - 1];
|
||||
} else {
|
||||
array[array.length - 1] = value;
|
||||
ret = array;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.makeArray <f> Wraps any non-array in an array.
|
||||
(value) -> <a> Array
|
||||
value <*> Any value
|
||||
> Ox.makeArray('foo')
|
||||
['foo']
|
||||
> Ox.makeArray(['foo'])
|
||||
['foo']
|
||||
@*/
|
||||
// FIXME: rename to toArray
|
||||
export function makeArray(value) {
|
||||
var ret, type = typeOf(value);
|
||||
if (type == 'arguments' || type == 'nodelist') {
|
||||
ret = slice(value);
|
||||
} else if (type == 'array') {
|
||||
ret = value;
|
||||
} else {
|
||||
ret = [value];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.range <f> Python-style range
|
||||
(stop) -> <[n]> range
|
||||
Returns an array of integers from `0` (inclusive) to `stop` (exclusive).
|
||||
(start, stop) -> <[n]> range
|
||||
Returns an array of integers from `start` (inclusive) to `stop`
|
||||
(exclusive).
|
||||
(start, stop, step) -> <[n]> range
|
||||
Returns an array of numbers from `start` (inclusive) to `stop`
|
||||
(exclusive), incrementing by `step`.
|
||||
start <n> Start value
|
||||
stop <n> Stop value
|
||||
step <n> Step value
|
||||
> Ox.range(3)
|
||||
[0, 1, 2]
|
||||
> Ox.range(1, 4)
|
||||
[1, 2, 3]
|
||||
> Ox.range(3, 0)
|
||||
[3, 2, 1]
|
||||
> Ox.range(1, 2, 0.5)
|
||||
[1, 1.5]
|
||||
> Ox.range(-1, -2, -0.5)
|
||||
[-1, -1.5]
|
||||
@*/
|
||||
export function range() {
|
||||
var array = [];
|
||||
loop.apply(null, slice(arguments).concat(function(index) {
|
||||
array.push(index);
|
||||
}));
|
||||
return array;
|
||||
}
|
||||
|
||||
// Re-export functions from other modules to maintain original hierarchy
|
||||
export { slice, max, min, forEach, len, map, filter, values, keyOf, isEmpty, contains, random, char, splice, clean, repeat, loop };
|
||||
|
||||
// Export all functions
|
||||
export default {
|
||||
api,
|
||||
|
|
@ -475,26 +389,5 @@ export default {
|
|||
count,
|
||||
sort,
|
||||
unique,
|
||||
zip,
|
||||
last,
|
||||
makeArray,
|
||||
range,
|
||||
// Re-exported functions
|
||||
slice,
|
||||
max,
|
||||
min,
|
||||
forEach,
|
||||
len,
|
||||
map,
|
||||
filter,
|
||||
values,
|
||||
keyOf,
|
||||
isEmpty,
|
||||
contains,
|
||||
random,
|
||||
char,
|
||||
splice,
|
||||
clean,
|
||||
repeat,
|
||||
loop
|
||||
zip
|
||||
};
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
import { typeOf, isFunction } from './Type.js';
|
||||
import { last, makeArray, slice, forEach, len, range } from './Array.js';
|
||||
|
||||
function asyncMap(forEachFn, collection, iterator, that, callback) {
|
||||
var type = typeOf(collection),
|
||||
results = type == 'object' ? {} : [];
|
||||
callback = last(arguments);
|
||||
that = arguments.length == 5 ? that : null;
|
||||
forEachFn(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 asyncMapFn(array, iterator, that, callback) {
|
||||
array = makeArray(array);
|
||||
callback = last(arguments);
|
||||
that = arguments.length == 4 ? that : null;
|
||||
if (array.some(Array.isArray)) {
|
||||
serialMap(array, function(value, key, array, callback) {
|
||||
parallelMap(makeArray(value), iterator, callback);
|
||||
}, callback);
|
||||
} else {
|
||||
parallelMap(array, iterator, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.nonblockingForEach <f> Non-blocking `forEach` with synchronous iterator
|
||||
(col, iterator[, that], callback[, ms]) -> <u> undefined
|
||||
collection <a|o|s> Collection
|
||||
iterator <f> Iterator function
|
||||
value <*> Value
|
||||
key <n|s> Key
|
||||
collection <a|o|s> The collection
|
||||
that <o> The iterator's `this` binding
|
||||
callback <f> Callback function
|
||||
ms <n> Number of milliseconds after which to insert a `setTimeout` call
|
||||
@*/
|
||||
export function nonblockingForEach(collection, iterator, that, callback, ms) {
|
||||
var i = 0, keys, lastArg = last(arguments),
|
||||
n, time, type = typeOf(collection);
|
||||
callback = isFunction(lastArg) ? lastArg : arguments[arguments.length - 2];
|
||||
collection = type == 'array' || type == 'object'
|
||||
? collection : slice(collection);
|
||||
keys = type == 'object'
|
||||
? Object.keys(collection) : range(collection.length);
|
||||
ms = ms || 1000;
|
||||
n = len(collection);
|
||||
that = arguments.length == 5 || (
|
||||
arguments.length == 4 && isFunction(lastArg)
|
||||
) ? that : null;
|
||||
time = +new Date();
|
||||
iterate();
|
||||
function iterate() {
|
||||
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 <f> Non-blocking `map` with synchronous iterator
|
||||
(collection, iterator[, that], callback[, ms]) -> <u> undefined
|
||||
collection <a|o|s> Collection
|
||||
iterator <f> Iterator function
|
||||
that <o> The iterator's `this` binding
|
||||
callback <f> Callback function
|
||||
ms <n> Number of milliseconds after which to insert a `setTimeout` call
|
||||
<script>
|
||||
// var time = +new Date();
|
||||
// Ox.nonblockingMap(
|
||||
// Ox.range(1000000),
|
||||
// function (value, index, array) {
|
||||
// return +new Date() - time;
|
||||
// },
|
||||
// function(results) {
|
||||
// Ox.print(results.length);
|
||||
// },
|
||||
// 1000
|
||||
// );
|
||||
</script>
|
||||
> Ox.nonblockingMap(Ox.range(100000), Ox.identity, function(r) { Ox.test(r.length, 100000); })
|
||||
undefined
|
||||
@*/
|
||||
export function nonblockingMap(collection, iterator, that, callback, ms) {
|
||||
var lastArg = last(arguments),
|
||||
type = typeOf(collection),
|
||||
results = type == 'object' ? {} : [];
|
||||
callback = isFunction(lastArg) ? lastArg : arguments[arguments.length - 2];
|
||||
that = arguments.length == 5 || (
|
||||
arguments.length == 4 && isFunction(lastArg)
|
||||
) ? that : null;
|
||||
nonblockingForEach(collection, function(value, key, collection) {
|
||||
results[key] = iterator.call(that, value, key, collection);
|
||||
}, function() {
|
||||
callback(type == 'string' ? results.join('') : results);
|
||||
}, ms);
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.parallelForEach <f> `forEach` with asynchronous iterator, running in parallel
|
||||
(collection, iterator[, that], callback) -> <u> undefined
|
||||
collection <a|o|s> Collection
|
||||
iterator <f> Iterator function
|
||||
value <*> Value
|
||||
key <n|s> Key
|
||||
collection <a|o|s> The collection
|
||||
callback <f> Callback function
|
||||
that <o> The iterator's this binding
|
||||
callback <f> Callback function
|
||||
<script>
|
||||
Ox.test.pfeNumber = 0;
|
||||
Ox.test.pfeIterator = function(value, index, array, callback) {
|
||||
if (index < 5) {
|
||||
Ox.test.pfeNumber++;
|
||||
}
|
||||
setTimeout(callback);
|
||||
};
|
||||
</script>
|
||||
> 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 = typeOf(collection);
|
||||
callback = callback || (arguments.length == 3 ? arguments[2] : function() {});
|
||||
collection = type == 'array' || type == 'object'
|
||||
? collection : slice(collection);
|
||||
n = len(collection);
|
||||
that = arguments.length == 4 ? that : null;
|
||||
forEach(collection, function(value, key, collection) {
|
||||
iterator.call(that, value, key, collection, function() {
|
||||
++i == n && callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.parallelMap <f> Parallel `map` with asynchronous iterator
|
||||
(collection, iterator[, that], callback) -> <u> undefined
|
||||
collection <a|o|s> Collection
|
||||
iterator <f> Iterator function
|
||||
value <*> Value
|
||||
key <n|s> Key
|
||||
collection <a|o|s> The collection
|
||||
callback <f> Callback function
|
||||
that <o> The iterator's this binding
|
||||
callback <f> Callback function
|
||||
results <a|o|s> Results
|
||||
<script>
|
||||
// var time = +new Date();
|
||||
// Ox.parallelMap(
|
||||
// Ox.range(10),
|
||||
// function (value, index, array, callback) {
|
||||
// setTimeout(function() {
|
||||
// callback(+new Date() - time);
|
||||
// }, Ox.random(1000));
|
||||
// },
|
||||
// function(results) {
|
||||
// Ox.print(results);
|
||||
// }
|
||||
// );
|
||||
Ox.test.pmIterator = function(value, index, array, callback) {
|
||||
setTimeout(callback(value - index));
|
||||
};
|
||||
</script>
|
||||
> Ox.parallelMap(Ox.range(10), Ox.test.pmIterator, function(r) { Ox.test(Ox.sum(r), 0); })
|
||||
undefined
|
||||
@*/
|
||||
export function parallelMap() {
|
||||
asyncMap.apply(null, [parallelForEach].concat(slice(arguments)));
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.serialForEach <f> `forEach` with asynchronous iterator, run serially
|
||||
(collection, iterator[, that], callback) -> <u> undefined
|
||||
collection <a|o|s> Collection
|
||||
iterator <f> Iterator function
|
||||
value <*> Value
|
||||
key <n|s> Key
|
||||
collection <a|o|s> The collection
|
||||
callback <f> Callback function
|
||||
that <o> The iterator's this binding
|
||||
callback <f> Callback function
|
||||
<script>
|
||||
Ox.test.sfeNumber = 0;
|
||||
Ox.test.sfeIterator = function(value, index, array, callback) {
|
||||
Ox.test.sfeNumber++;
|
||||
setTimeout(function() {
|
||||
callback(index < 4);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
> 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 = typeOf(collection);
|
||||
callback = callback || (arguments.length == 3 ? arguments[2] : function() {});
|
||||
collection = type == 'array' || type == 'object'
|
||||
? collection : slice(collection);
|
||||
keys = type == 'object'
|
||||
? Object.keys(collection) : range(collection.length);
|
||||
n = 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 <f> Serial `map` with asynchronous iterator
|
||||
(collection, iterator[, that], callback) -> <u> undefined
|
||||
collection <a|o|s> Collection
|
||||
iterator <f> Iterator function
|
||||
value <*> Value
|
||||
key <n|s> Key
|
||||
collection <a|o|s> The collection
|
||||
callback <f> Callback function
|
||||
that <o> The iterator's this binding
|
||||
callback <f> Callback function
|
||||
results <a|o|s> Results
|
||||
<script>
|
||||
// var time = +new Date();
|
||||
// Ox.serialMap(
|
||||
// Ox.range(10),
|
||||
// function (value, index, array, callback) {
|
||||
// setTimeout(function() {
|
||||
// callback(+new Date() - time);
|
||||
// }, Ox.random(1000));
|
||||
// },
|
||||
// function(results) {
|
||||
// Ox.print(results);
|
||||
// }
|
||||
// );
|
||||
Ox.test.smIterator = function(value, index, array, callback) {
|
||||
setTimeout(callback(value - index));
|
||||
};
|
||||
</script>
|
||||
> Ox.serialMap(Ox.range(10), Ox.test.smIterator, function(r) { Ox.test(Ox.sum(r), 0); })
|
||||
undefined
|
||||
@*/
|
||||
export function serialMap(collection, iterator, that, callback) {
|
||||
asyncMap.apply(null, [serialForEach].concat(slice(arguments)));
|
||||
}
|
||||
// FIXME: The above test with 10000 iterations blows the stack
|
||||
|
|
@ -208,104 +208,6 @@ export function values(collection) {
|
|||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
const STACK_LENGTH = 50000;
|
||||
|
||||
/*@
|
||||
Ox.slice <f> Alias for `Array.prototype.slice.call`
|
||||
(collection[, start[, stop]]) -> <a> Array
|
||||
collection <a|o|s> Array-like
|
||||
start <n> Start position
|
||||
stop <n> 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) {
|
||||
// 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.
|
||||
|
||||
// Try the simple approach first
|
||||
try {
|
||||
const result = Array.prototype.slice.call(collection, start, stop);
|
||||
// Test for broken implementations
|
||||
if (result.length === 0 && collection.length > 0) {
|
||||
throw new Error('Broken slice implementation');
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
// Fallback for broken implementations
|
||||
const args = stop === void 0 ? [start] : [start, stop];
|
||||
const array = [];
|
||||
let index, length;
|
||||
|
||||
if (typeOf(collection) === 'string') {
|
||||
collection = collection.split('');
|
||||
}
|
||||
|
||||
length = collection.length;
|
||||
for (index = 0; index < length; index++) {
|
||||
array[index] = collection[index];
|
||||
}
|
||||
|
||||
return Array.prototype.slice.apply(array, args);
|
||||
}
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.max <f> Returns the maximum value of a collection
|
||||
> Ox.max([1, 2, 3])
|
||||
3
|
||||
> Ox.max({a: 1, b: 2, c: 3})
|
||||
3
|
||||
> Ox.max('123')
|
||||
3
|
||||
> Ox.max([])
|
||||
-Infinity
|
||||
@*/
|
||||
export function max(collection) {
|
||||
var ret, collectionValues = values(collection);
|
||||
if (collectionValues.length < STACK_LENGTH) {
|
||||
ret = Math.max.apply(null, collectionValues);
|
||||
} else {
|
||||
ret = collectionValues.reduce(function(previousValue, currentValue) {
|
||||
return Math.max(previousValue, currentValue);
|
||||
}, -Infinity);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.min <f> Returns the minimum value of a collection
|
||||
> Ox.min([1, 2, 3])
|
||||
1
|
||||
> Ox.min({a: 1, b: 2, c: 3})
|
||||
1
|
||||
> Ox.min('123')
|
||||
1
|
||||
> Ox.min([])
|
||||
Infinity
|
||||
@*/
|
||||
export function min(collection) {
|
||||
var ret, collectionValues = values(collection);
|
||||
if (collectionValues.length < STACK_LENGTH) {
|
||||
ret = Math.min.apply(null, collectionValues);
|
||||
} else {
|
||||
ret = collectionValues.reduce(function(previousValue, currentValue) {
|
||||
return Math.min(previousValue, currentValue);
|
||||
}, Infinity);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Export all functions
|
||||
export default {
|
||||
forEach,
|
||||
|
|
@ -317,8 +219,5 @@ export default {
|
|||
every,
|
||||
some,
|
||||
keys,
|
||||
values,
|
||||
slice,
|
||||
max,
|
||||
min
|
||||
values
|
||||
};
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
import { slice, max, min, range } from './Array.js';
|
||||
import { clone } from './Object.js';
|
||||
import { pad } from './String.js';
|
||||
|
||||
/*@
|
||||
Ox.hsl <f> Takes RGB values and returns HSL values
|
||||
(rgb) <[n]> HSL values
|
||||
(r, g, b) <[n]> HSL values
|
||||
rgb <[n]> RGB values
|
||||
r <n> red
|
||||
g <n> green
|
||||
b <n> blue
|
||||
> Ox.hsl([0, 0, 0])
|
||||
[0, 0, 0]
|
||||
> Ox.hsl([255, 255, 255])
|
||||
[0, 0, 1]
|
||||
> Ox.hsl(0, 255, 0)
|
||||
[120, 1, 0.5]
|
||||
@*/
|
||||
export function hsl(rgb) {
|
||||
var hsl = [0, 0, 0], maxVal, minVal;
|
||||
if (arguments.length == 3) {
|
||||
rgb = slice(arguments);
|
||||
}
|
||||
rgb = clone(rgb).map(function(value) {
|
||||
return value / 255;
|
||||
});
|
||||
maxVal = max(rgb);
|
||||
minVal = min(rgb);
|
||||
hsl[2] = 0.5 * (maxVal + minVal);
|
||||
if (maxVal == minVal) {
|
||||
hsl[0] = 0;
|
||||
hsl[1] = 0;
|
||||
} else {
|
||||
if (maxVal == rgb[0]) {
|
||||
hsl[0] = (60 * (rgb[1] - rgb[2]) / (maxVal - minVal) + 360) % 360;
|
||||
} else if (maxVal == rgb[1]) {
|
||||
hsl[0] = 60 * (rgb[2] - rgb[0]) / (maxVal - minVal) + 120;
|
||||
} else if (maxVal == rgb[2]) {
|
||||
hsl[0] = 60 * (rgb[0] - rgb[1]) / (maxVal - minVal) + 240;
|
||||
}
|
||||
if (hsl[2] <= 0.5) {
|
||||
hsl[1] = (maxVal - minVal) / (2 * hsl[2]);
|
||||
} else {
|
||||
hsl[1] = (maxVal - minVal) / (2 - 2 * hsl[2]);
|
||||
}
|
||||
}
|
||||
return hsl;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.rgb <f> Takes HSL values and returns RGB values
|
||||
(hsl) <[n]> RGB values
|
||||
(h, s, l) <[n]> RGB values
|
||||
hsl <[n]> HSL values
|
||||
h <n> hue
|
||||
s <n> saturation
|
||||
l <n> lightness
|
||||
> Ox.rgb([0, 0, 0])
|
||||
[0, 0, 0]
|
||||
> Ox.rgb([0, 0, 1])
|
||||
[255, 255, 255]
|
||||
> Ox.rgb(120, 1, 0.5)
|
||||
[0, 255, 0]
|
||||
@*/
|
||||
export function rgb(hsl) {
|
||||
var rgb = [0, 0, 0], v1, v2, v3;
|
||||
if (arguments.length == 3) {
|
||||
hsl = slice(arguments);
|
||||
}
|
||||
hsl = clone(hsl);
|
||||
hsl[0] /= 360;
|
||||
if (hsl[1] == 0) {
|
||||
rgb = [hsl[2], hsl[2], hsl[2]];
|
||||
} else {
|
||||
if (hsl[2] < 0.5) {
|
||||
v2 = hsl[2] * (1 + hsl[1]);
|
||||
} else {
|
||||
v2 = hsl[1] + hsl[2] - (hsl[1] * hsl[2]);
|
||||
}
|
||||
v1 = 2 * hsl[2] - v2;
|
||||
rgb.forEach(function(v, i) {
|
||||
v3 = hsl[0] + (1 - i) * 1/3;
|
||||
if (v3 < 0) {
|
||||
v3++;
|
||||
} else if (v3 > 1) {
|
||||
v3--;
|
||||
}
|
||||
if (v3 < 1/6) {
|
||||
rgb[i] = v1 + ((v2 - v1) * 6 * v3);
|
||||
} else if (v3 < 0.5) {
|
||||
rgb[i] = v2;
|
||||
} else if (v3 < 2/3) {
|
||||
rgb[i] = v1 + ((v2 - v1) * 6 * (2/3 - v3));
|
||||
} else {
|
||||
rgb[i] = v1;
|
||||
}
|
||||
});
|
||||
}
|
||||
return rgb.map(function(value) {
|
||||
return Math.round(value * 255);
|
||||
});
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.toHex <f> Format RGB array as hex value
|
||||
> Ox.toHex([192, 128, 64])
|
||||
'C08040'
|
||||
@*/
|
||||
export function toHex(rgb) {
|
||||
return rgb.map(function(value) {
|
||||
return pad(value.toString(16).toUpperCase(), 'left', 2, '0');
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.toRGB <f> Format hex value as RGB array
|
||||
> Ox.toRGB('C08040')
|
||||
[192, 128, 64]
|
||||
@*/
|
||||
export function toRGB(hex) {
|
||||
return range(3).map(function(index) {
|
||||
return parseInt(hex.substr(index * 2, 2), 16);
|
||||
});
|
||||
}
|
||||
|
|
@ -17,10 +17,6 @@ export const EARTH_RADIUS = 6371000; // in meters
|
|||
export const MAX_LATITUDE = 85.05112878; // Web Mercator max latitude
|
||||
export const MIN_LATITUDE = -85.05112878; // Web Mercator min latitude
|
||||
|
||||
// Base32 encoding constants
|
||||
export const BASE_32_ALIASES = {'I': '1', 'L': '1', 'O': '0', 'U': 'V'};
|
||||
export const BASE_32_DIGITS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
||||
|
||||
// Time constants
|
||||
export const SECONDS_PER_MINUTE = 60;
|
||||
export const SECONDS_PER_HOUR = 3600;
|
||||
|
|
@ -258,8 +254,6 @@ export default {
|
|||
EARTH_RADIUS,
|
||||
MAX_LATITUDE,
|
||||
MIN_LATITUDE,
|
||||
BASE_32_ALIASES,
|
||||
BASE_32_DIGITS,
|
||||
SECONDS_PER_MINUTE,
|
||||
SECONDS_PER_HOUR,
|
||||
SECONDS_PER_DAY,
|
||||
|
|
|
|||
|
|
@ -1,452 +0,0 @@
|
|||
/**
|
||||
* Date utilities - ES Module Version
|
||||
*/
|
||||
|
||||
import { isDate, isNumber, isString, isUndefined } from './Type.js';
|
||||
import { mod } from './Math.js';
|
||||
|
||||
/**
|
||||
* Get the name of the day of the week for a given date
|
||||
*/
|
||||
export function getDayName(date, utc) {
|
||||
const names = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
return names[getDayOfWeek(date, utc)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the day of the week (0-6) for a given date
|
||||
*/
|
||||
export function getDayOfWeek(date, utc) {
|
||||
date = makeDate(date);
|
||||
return utc ? date.getUTCDay() : date.getDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the day of the year (1-366) for a given date
|
||||
*/
|
||||
export function getDayOfYear(date, utc) {
|
||||
date = makeDate(date);
|
||||
const startOfYear = new Date(Date.UTC(
|
||||
getFullYear(date, utc),
|
||||
0, 1
|
||||
));
|
||||
const diff = date - startOfYear;
|
||||
return Math.floor(diff / 86400000) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of days in a month
|
||||
*/
|
||||
export function getDaysInMonth(year, month) {
|
||||
return new Date(year, month + 1, 0).getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of days in a year
|
||||
*/
|
||||
export function getDaysInYear(year) {
|
||||
return isLeapYear(year) ? 366 : 365;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first day of the week for a given date
|
||||
*/
|
||||
export function getFirstDayOfWeek(date, utc) {
|
||||
date = makeDate(date);
|
||||
const day = getDayOfWeek(date, utc);
|
||||
return new Date(date.getTime() - day * 86400000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full year from a date
|
||||
*/
|
||||
export function getFullYear(date, utc) {
|
||||
date = makeDate(date);
|
||||
return utc ? date.getUTCFullYear() : date.getFullYear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hours from date
|
||||
*/
|
||||
export function getHours(date, utc) {
|
||||
date = makeDate(date);
|
||||
return utc ? date.getUTCHours() : date.getHours();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ISO date string (YYYY-MM-DD)
|
||||
*/
|
||||
export function getISODate(date, utc) {
|
||||
return formatDate(date, '%Y-%m-%d', utc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ISO week number
|
||||
*/
|
||||
export function getISOWeek(date, utc) {
|
||||
date = makeDate(date);
|
||||
const year = getFullYear(date, utc);
|
||||
const firstThursday = getFirstThursday(year, utc);
|
||||
const week = Math.floor((date - firstThursday) / 604800000) + 1;
|
||||
|
||||
if (week < 1) {
|
||||
return getISOWeek(new Date(year - 1, 11, 31), utc);
|
||||
} else if (week > 52) {
|
||||
const nextFirstThursday = getFirstThursday(year + 1, utc);
|
||||
if (date >= nextFirstThursday) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return week;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ISO year
|
||||
*/
|
||||
export function getISOYear(date, utc) {
|
||||
date = makeDate(date);
|
||||
const year = getFullYear(date, utc);
|
||||
const week = getISOWeek(date, utc);
|
||||
|
||||
if (week === 1 && getMonth(date, utc) === 11) {
|
||||
return year + 1;
|
||||
} else if (week >= 52 && getMonth(date, utc) === 0) {
|
||||
return year - 1;
|
||||
}
|
||||
|
||||
return year;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minutes from date
|
||||
*/
|
||||
export function getMinutes(date, utc) {
|
||||
date = makeDate(date);
|
||||
return utc ? date.getUTCMinutes() : date.getMinutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month from date (0-11)
|
||||
*/
|
||||
export function getMonth(date, utc) {
|
||||
date = makeDate(date);
|
||||
return utc ? date.getUTCMonth() : date.getMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month name
|
||||
*/
|
||||
export function getMonthName(date, utc) {
|
||||
const names = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
];
|
||||
return names[getMonth(date, utc)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get seconds from date
|
||||
*/
|
||||
export function getSeconds(date, utc) {
|
||||
date = makeDate(date);
|
||||
return utc ? date.getUTCSeconds() : date.getSeconds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get milliseconds from date
|
||||
*/
|
||||
export function getMilliseconds(date, utc) {
|
||||
date = makeDate(date);
|
||||
return utc ? date.getUTCMilliseconds() : date.getMilliseconds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone offset in minutes
|
||||
*/
|
||||
export function getTimezoneOffset(date) {
|
||||
return makeDate(date).getTimezoneOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone offset string (+HH:MM or -HH:MM)
|
||||
*/
|
||||
export function getTimezoneOffsetString(date) {
|
||||
const offset = getTimezoneOffset(date);
|
||||
const sign = offset <= 0 ? '+' : '-';
|
||||
const hours = Math.floor(Math.abs(offset) / 60);
|
||||
const minutes = Math.abs(offset) % 60;
|
||||
return sign + pad(hours, 2) + ':' + pad(minutes, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Unix timestamp (seconds since epoch)
|
||||
*/
|
||||
export function getUnixTime(date) {
|
||||
return Math.floor(makeDate(date).getTime() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get week number (1-53)
|
||||
*/
|
||||
export function getWeek(date, utc) {
|
||||
date = makeDate(date);
|
||||
const firstDayOfYear = new Date(Date.UTC(
|
||||
getFullYear(date, utc), 0, 1
|
||||
));
|
||||
const days = Math.floor((date - firstDayOfYear) / 86400000);
|
||||
return Math.ceil((days + getDayOfWeek(firstDayOfYear, utc) + 1) / 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a year is a leap year
|
||||
*/
|
||||
export function isLeapYear(year) {
|
||||
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a date is valid
|
||||
*/
|
||||
export function isValidDate(date) {
|
||||
date = makeDate(date);
|
||||
return !isNaN(date.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a date object from various inputs
|
||||
*/
|
||||
export function makeDate(date) {
|
||||
if (isDate(date)) {
|
||||
return date;
|
||||
} else if (isString(date)) {
|
||||
return new Date(date);
|
||||
} else if (isNumber(date)) {
|
||||
return new Date(date);
|
||||
} else if (isUndefined(date)) {
|
||||
return new Date();
|
||||
}
|
||||
return new Date(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date according to a format string
|
||||
*/
|
||||
export function formatDate(date, format, utc) {
|
||||
date = makeDate(date);
|
||||
format = format || '%Y-%m-%d %H:%M:%S';
|
||||
|
||||
const replacements = {
|
||||
'%a': () => getDayName(date, utc).substr(0, 3),
|
||||
'%A': () => getDayName(date, utc),
|
||||
'%b': () => getMonthName(date, utc).substr(0, 3),
|
||||
'%B': () => getMonthName(date, utc),
|
||||
'%c': () => date.toLocaleString(),
|
||||
'%d': () => pad(getDate(date, utc), 2),
|
||||
'%e': () => pad(getDate(date, utc), 2, ' '),
|
||||
'%H': () => pad(getHours(date, utc), 2),
|
||||
'%I': () => pad(((getHours(date, utc) + 11) % 12) + 1, 2),
|
||||
'%j': () => pad(getDayOfYear(date, utc), 3),
|
||||
'%k': () => pad(getHours(date, utc), 2, ' '),
|
||||
'%l': () => pad(((getHours(date, utc) + 11) % 12) + 1, 2, ' '),
|
||||
'%m': () => pad(getMonth(date, utc) + 1, 2),
|
||||
'%M': () => pad(getMinutes(date, utc), 2),
|
||||
'%p': () => getHours(date, utc) < 12 ? 'AM' : 'PM',
|
||||
'%S': () => pad(getSeconds(date, utc), 2),
|
||||
'%u': () => getDayOfWeek(date, utc) || 7,
|
||||
'%U': () => pad(getWeek(date, utc), 2),
|
||||
'%V': () => pad(getISOWeek(date, utc), 2),
|
||||
'%w': () => getDayOfWeek(date, utc),
|
||||
'%W': () => pad(getWeek(date, utc), 2),
|
||||
'%x': () => date.toLocaleDateString(),
|
||||
'%X': () => date.toLocaleTimeString(),
|
||||
'%y': () => pad(getFullYear(date, utc) % 100, 2),
|
||||
'%Y': () => getFullYear(date, utc),
|
||||
'%z': () => getTimezoneOffsetString(date),
|
||||
'%Z': () => '', // Timezone abbreviation not easily available
|
||||
'%%': () => '%'
|
||||
};
|
||||
|
||||
return format.replace(/%[a-zA-Z%]/g, (match) => {
|
||||
return replacements[match] ? replacements[match]() : match;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a date string
|
||||
*/
|
||||
export function parseDate(string, format, utc) {
|
||||
// Basic implementation - can be enhanced
|
||||
return new Date(string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date (day of month)
|
||||
*/
|
||||
export function getDate(date, utc) {
|
||||
date = makeDate(date);
|
||||
return utc ? date.getUTCDate() : date.getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day (alias for getDayOfWeek)
|
||||
*/
|
||||
export function getDay(date, utc) {
|
||||
return getDayOfWeek(date, utc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ISO day (Monday=1, Sunday=7)
|
||||
*/
|
||||
export function getISODay(date, utc) {
|
||||
const day = getDayOfWeek(date, utc);
|
||||
return day === 0 ? 7 : day;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day of the year (alias for getDayOfYear)
|
||||
*/
|
||||
export function getDayOfTheYear(date, utc) {
|
||||
return getDayOfYear(date, utc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first day of the year
|
||||
*/
|
||||
export function getFirstDayOfTheYear(date, utc) {
|
||||
date = makeDate(date);
|
||||
const year = getFullYear(date, utc);
|
||||
return new Date(Date.UTC(year, 0, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set date (day of month)
|
||||
*/
|
||||
export function setDate(date, day, utc) {
|
||||
date = makeDate(date);
|
||||
if (utc) {
|
||||
date.setUTCDate(day);
|
||||
} else {
|
||||
date.setDate(day);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set full year
|
||||
*/
|
||||
export function setFullYear(date, year, utc) {
|
||||
date = makeDate(date);
|
||||
if (utc) {
|
||||
date.setUTCFullYear(year);
|
||||
} else {
|
||||
date.setFullYear(year);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set month
|
||||
*/
|
||||
export function setMonth(date, month, utc) {
|
||||
date = makeDate(date);
|
||||
if (utc) {
|
||||
date.setUTCMonth(month);
|
||||
} else {
|
||||
date.setMonth(month);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hours
|
||||
*/
|
||||
export function setHours(date, hours, utc) {
|
||||
date = makeDate(date);
|
||||
if (utc) {
|
||||
date.setUTCHours(hours);
|
||||
} else {
|
||||
date.setHours(hours);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set minutes
|
||||
*/
|
||||
export function setMinutes(date, minutes, utc) {
|
||||
date = makeDate(date);
|
||||
if (utc) {
|
||||
date.setUTCMinutes(minutes);
|
||||
} else {
|
||||
date.setMinutes(minutes);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set seconds
|
||||
*/
|
||||
export function setSeconds(date, seconds, utc) {
|
||||
date = makeDate(date);
|
||||
if (utc) {
|
||||
date.setUTCSeconds(seconds);
|
||||
} else {
|
||||
date.setSeconds(seconds);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function getFirstThursday(year, utc) {
|
||||
const jan1 = new Date(Date.UTC(year, 0, 1));
|
||||
const dayOfWeek = getDayOfWeek(jan1, utc);
|
||||
const daysToThursday = (11 - dayOfWeek) % 7;
|
||||
return new Date(jan1.getTime() + daysToThursday * 86400000);
|
||||
}
|
||||
|
||||
function pad(number, length, padding) {
|
||||
padding = padding || '0';
|
||||
const str = String(number);
|
||||
return padding.repeat(Math.max(0, length - str.length)) + str;
|
||||
}
|
||||
|
||||
// Export all functions
|
||||
export default {
|
||||
getDayName,
|
||||
getDayOfWeek,
|
||||
getDayOfYear,
|
||||
getDaysInMonth,
|
||||
getDaysInYear,
|
||||
getFirstDayOfWeek,
|
||||
getFullYear,
|
||||
getHours,
|
||||
getISODate,
|
||||
getISOWeek,
|
||||
getISOYear,
|
||||
getMinutes,
|
||||
getMonth,
|
||||
getMonthName,
|
||||
getSeconds,
|
||||
getMilliseconds,
|
||||
getTimezoneOffset,
|
||||
getTimezoneOffsetString,
|
||||
getUnixTime,
|
||||
getWeek,
|
||||
isLeapYear,
|
||||
isValidDate,
|
||||
makeDate,
|
||||
formatDate,
|
||||
parseDate,
|
||||
getDate,
|
||||
getDay,
|
||||
getISODay,
|
||||
getDayOfTheYear,
|
||||
getFirstDayOfTheYear,
|
||||
setDate,
|
||||
setFullYear,
|
||||
setMonth,
|
||||
setHours,
|
||||
setMinutes,
|
||||
setSeconds
|
||||
};
|
||||
|
|
@ -1,426 +0,0 @@
|
|||
import { map, char, forEach, range, slice, repeat, loop } from './Array.js';
|
||||
import { BASE_32_DIGITS, BASE_32_ALIASES } from './Constants.js';
|
||||
|
||||
/*@
|
||||
Ox.encodeBase26 <b> Encode a number as bijective base26
|
||||
See <a href="http://en.wikipedia.org/wiki/Bijective_numeration">
|
||||
Bijective numeration</a>.
|
||||
> Ox.encodeBase26(0)
|
||||
''
|
||||
> Ox.encodeBase26(1)
|
||||
'A'
|
||||
> Ox.encodeBase26(26)
|
||||
'Z'
|
||||
> Ox.encodeBase26(27)
|
||||
'AA'
|
||||
> Ox.encodeBase26(4461)
|
||||
'FOO'
|
||||
@*/
|
||||
export function encodeBase26(number) {
|
||||
var string = '';
|
||||
while (number) {
|
||||
string = String.fromCharCode(65 + (number - 1) % 26) + string;
|
||||
number = Math.floor((number - 1) / 26);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeBase26 <f> Decodes a bijective base26-encoded number
|
||||
See <a href="http://en.wikipedia.org/wiki/Bijective_numeration">
|
||||
Bijective numeration</a>.
|
||||
> Ox.decodeBase26('foo')
|
||||
4461
|
||||
@*/
|
||||
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);
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.encodeBase32 <b> Encode a number as base32
|
||||
See <a href="http://www.crockford.com/wrmg/base32.html">Base 32</a>.
|
||||
> Ox.encodeBase32(15360)
|
||||
'F00'
|
||||
> Ox.encodeBase32(33819)
|
||||
'110V'
|
||||
@*/
|
||||
export function encodeBase32(number) {
|
||||
return map(number.toString(32), function(char) {
|
||||
return BASE_32_DIGITS[parseInt(char, 32)];
|
||||
});
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeBase32 <f> Decodes a base32-encoded number
|
||||
See <a href="http://www.crockford.com/wrmg/base32.html">Base 32</a>.
|
||||
> Ox.decodeBase32('foo')
|
||||
15360
|
||||
> Ox.decodeBase32('ILOU')
|
||||
33819
|
||||
> Ox.decodeBase32('?').toString()
|
||||
'NaN'
|
||||
@*/
|
||||
export function decodeBase32(string) {
|
||||
return parseInt(map(string.toUpperCase(), function(char) {
|
||||
var index = BASE_32_DIGITS.indexOf(
|
||||
BASE_32_ALIASES[char] || char
|
||||
);
|
||||
return index == -1 ? ' ' : index.toString(32);
|
||||
}), 32);
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.encodeBase64 <f> Encode a number as base64
|
||||
> Ox.encodeBase64(32394)
|
||||
'foo'
|
||||
@*/
|
||||
export function encodeBase64(number) {
|
||||
return btoa(encodeBase256(number)).replace(/=/g, '');
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeBase64 <f> Decodes a base64-encoded number
|
||||
> Ox.decodeBase64('foo')
|
||||
32394
|
||||
@*/
|
||||
export function decodeBase64(string) {
|
||||
return decodeBase256(atob(string));
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.encodeBase128 <f> Encode a number as base128
|
||||
> Ox.encodeBase128(1685487)
|
||||
'foo'
|
||||
@*/
|
||||
export function encodeBase128(number) {
|
||||
var string = '';
|
||||
while (number) {
|
||||
string = char(number & 127) + string;
|
||||
number >>= 7;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeBase128 <f> Decode a base128-encoded number
|
||||
> Ox.decodeBase128('foo')
|
||||
1685487
|
||||
@*/
|
||||
export function decodeBase128(string) {
|
||||
return string.split('').reverse().reduce(function(p, c, i) {
|
||||
return p + (c.charCodeAt(0) << i * 7);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.encodeBase256 <f> Encode a number as base256
|
||||
> Ox.encodeBase256(6713199)
|
||||
'foo'
|
||||
@*/
|
||||
export function encodeBase256(number) {
|
||||
var string = '';
|
||||
while (number) {
|
||||
string = char(number & 255) + string;
|
||||
number >>= 8;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeBase256 <f> Decode a base256-encoded number
|
||||
> Ox.decodeBase256('foo')
|
||||
6713199
|
||||
@*/
|
||||
export function decodeBase256(string) {
|
||||
return string.split('').reverse().reduce(function(p, c, i) {
|
||||
return p + (c.charCodeAt(0) << i * 8);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.encodeDeflate <f> Encodes a string, using deflate
|
||||
Since PNGs are deflate-encoded, the `canvas` object's `toDataURL` method
|
||||
provides an efficient implementation. The string is encoded as UTF-8 and
|
||||
written to the RGB channels of a canvas element, then the PNG dataURL is
|
||||
decoded from base64, and some head, tail and chunk names are removed.
|
||||
(str) -> <s> The encoded string
|
||||
str <s> The string to be encoded
|
||||
> Ox.decodeDeflate(Ox.encodeDeflate('foo'), function(str) { Ox.test(str, 'foo'); })
|
||||
undefined
|
||||
@*/
|
||||
export function encodeDeflate(string, callback) {
|
||||
// Make sure we can encode the full unicode range of characters.
|
||||
string = encodeUTF8(string);
|
||||
// We can only safely write to RGB, so we need 1 pixel for 3 bytes.
|
||||
// The string length may not be a multiple of 3, so we need to encode
|
||||
// the number of padding bytes (1 byte), the string, and non-0-bytes
|
||||
// as padding, so that the combined length becomes a multiple of 3.
|
||||
var length = 1 + string.length, c = canvas(Math.ceil(length / 3), 1),
|
||||
data, idat, pad = (3 - length % 3) % 3;
|
||||
string = char(pad) + string + repeat('\u00FF', pad);
|
||||
loop(c.data.length, function(i) {
|
||||
// Write character codes into RGB, and 255 into ALPHA
|
||||
c.data[i] = i % 4 < 3 ? string.charCodeAt(i - parseInt(i / 4)) : 255;
|
||||
});
|
||||
c.context.putImageData(c.imageData, 0, 0);
|
||||
// Get the PNG data from the data URL and decode it from base64.
|
||||
string = atob(c.canvas.toDataURL().split(',')[1]);
|
||||
// Discard bytes 0 to 15 (8 bytes PNG signature, 4 bytes IHDR length, 4
|
||||
// bytes IHDR name), keep bytes 16 to 19 (width), discard bytes 20 to 29
|
||||
// (4 bytes height, 5 bytes flags), keep bytes 29 to 32 (IHDR checksum),
|
||||
// keep the rest (IDAT chunks), discard the last 12 bytes (IEND chunk).
|
||||
data = string.slice(16, 20) + string.slice(29, 33);
|
||||
idat = string.slice(33, -12);
|
||||
while (idat) {
|
||||
// Each IDAT chunk is 4 bytes length, 4 bytes name, length bytes
|
||||
// data and 4 bytes checksum. We can discard the name parts.
|
||||
length = idat.slice(0, 4);
|
||||
data += length + idat.slice(8, 12 + (
|
||||
length = decodeBase256(length)
|
||||
));
|
||||
idat = idat.slice(12 + length);
|
||||
}
|
||||
// Allow for async use, symmetrical to Ox.decodeDeflate
|
||||
callback && callback(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeDeflate <f> Decodes an deflate-encoded string
|
||||
Since PNGs are deflate-encoded, the `canvas` object's `drawImage` method
|
||||
provides an efficient implementation. The string will be wrapped as a PNG
|
||||
dataURL, encoded as base64, and drawn onto a canvas element, then the RGB
|
||||
channels will be read, and the result will be decoded from UTF8.
|
||||
(str) -> <u> undefined
|
||||
str <s> The string to be decoded
|
||||
callback <f> Callback function
|
||||
str <s> The decoded string
|
||||
@*/
|
||||
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'
|
||||
+ string.slice(0, 4) + '\u0000\u0000\u0000\u0001'
|
||||
+ '\u0008\u0006\u0000\u0000\u0000' + string.slice(4, 8),
|
||||
// IDAT chunks
|
||||
idat = string.slice(8), length;
|
||||
function error() {
|
||||
throw new RangeError('Deflate codec can\'t decode data.');
|
||||
}
|
||||
while (idat) {
|
||||
// Reinsert the IDAT chunk names
|
||||
length = idat.slice(0, 4);
|
||||
data += length + 'IDAT' + idat.slice(4, 8 + (
|
||||
length = decodeBase256(length)
|
||||
));
|
||||
idat = idat.slice(8 + length);
|
||||
}
|
||||
// IEND chunk
|
||||
data += '\u0000\u0000\u0000\u0000IEND\u00AE\u0042\u0060\u0082';
|
||||
// Unfortunately, we can't synchronously set the source of an image,
|
||||
// draw it onto a canvas, and read its data.
|
||||
image.onload = function() {
|
||||
string = slice(canvas(image).data).map(function(value, index) {
|
||||
// Read one character per RGB byte, ignore ALPHA.
|
||||
return index % 4 < 3 ? char(value) : '';
|
||||
}).join('');
|
||||
try {
|
||||
// Parse the first byte as number of bytes to chop at the end,
|
||||
// and the rest, without these bytes, as an UTF8-encoded string.
|
||||
string = decodeUTF8(
|
||||
string.slice(1, -string.charCodeAt(0) || void 0)
|
||||
);
|
||||
} catch (e) {
|
||||
error();
|
||||
}
|
||||
callback(string);
|
||||
};
|
||||
image.onerror = error;
|
||||
image.src = 'data:image/png;base64,' + btoa(data);
|
||||
}
|
||||
|
||||
(function() {
|
||||
|
||||
function replace(string) {
|
||||
return string.toString().replace(/%(?![0-9A-Fa-f]{2})/g, '%25')
|
||||
.replace(/(%[0-9A-Fa-f]{2})+/g, function(match) {
|
||||
var hex = match.split('%').slice(1), ret;
|
||||
forEach(range(1, hex.length + 1), function(length) {
|
||||
var string = range(length).map(function(i) {
|
||||
return char(parseInt(hex[i], 16));
|
||||
}).join('');
|
||||
try {
|
||||
decodeUTF8(string);
|
||||
ret = match.slice(0, length * 3)
|
||||
+ replace(match.slice(length * 3));
|
||||
return false;
|
||||
} catch(e) {}
|
||||
});
|
||||
return ret || '%25' + hex[0] + replace(match.slice(3));
|
||||
});
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeURI <f> Decodes URI
|
||||
Unlike window.decodeURI, this doesn't throw on trailing '%'.
|
||||
(string) -> <s> Decoded string
|
||||
@*/
|
||||
decodeURIFn = function(string) {
|
||||
return decodeURI(replace(string));
|
||||
};
|
||||
|
||||
/*@
|
||||
Ox.decodeURIComponent <f> Decodes URI component
|
||||
Unlike window.decodeURIComponent, this doesn't throw on trailing '%'.
|
||||
(string) -> <s> Decoded string
|
||||
@*/
|
||||
decodeURIComponentFn = function(string) {
|
||||
return decodeURIComponent(replace(string));
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
/*@
|
||||
Ox.decodeURI <f> Decodes URI
|
||||
Unlike window.decodeURI, this doesn't throw on trailing '%'.
|
||||
(string) -> <s> Decoded string
|
||||
@*/
|
||||
export function decodeURICompat(string) {
|
||||
function replace(str) {
|
||||
return str.toString().replace(/%(?![0-9A-Fa-f]{2})/g, '%25')
|
||||
.replace(/(%[0-9A-Fa-f]{2})+/g, function(match) {
|
||||
var hex = match.split('%').slice(1), ret;
|
||||
try {
|
||||
ret = decodeURI('%' + hex.join('%'));
|
||||
} catch (e) {}
|
||||
return ret || '%25' + hex[0] + replace(match.slice(3));
|
||||
});
|
||||
}
|
||||
return decodeURI(replace(string));
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeURIComponent <f> Decodes URI component
|
||||
Unlike window.decodeURIComponent, this doesn't throw on trailing '%'.
|
||||
(string) -> <s> Decoded string
|
||||
@*/
|
||||
export function decodeURIComponentCompat(string) {
|
||||
function replace(str) {
|
||||
return str.toString().replace(/%(?![0-9A-Fa-f]{2})/g, '%25')
|
||||
.replace(/(%[0-9A-Fa-f]{2})+/g, function(match) {
|
||||
var hex = match.split('%').slice(1), ret;
|
||||
try {
|
||||
ret = decodeURIComponent('%' + hex.join('%'));
|
||||
} catch (e) {}
|
||||
return ret || '%25' + hex[0] + replace(match.slice(3));
|
||||
});
|
||||
}
|
||||
return decodeURIComponent(replace(string));
|
||||
}
|
||||
|
||||
// Export with proper names
|
||||
export { decodeURICompat as decodeURI, decodeURIComponentCompat as decodeURIComponent };
|
||||
|
||||
/*@
|
||||
Ox.encodeUTF8 <f> Encodes a string as UTF-8
|
||||
see http://en.wikipedia.org/wiki/UTF-8
|
||||
(string) -> <s> UTF-8 encoded string
|
||||
string <s> Any string
|
||||
> Ox.encodeUTF8("YES")
|
||||
"YES"
|
||||
> Ox.encodeUTF8("¥€$")
|
||||
"\u00C2\u00A5\u00E2\u0082\u00AC\u0024"
|
||||
@*/
|
||||
export function encodeUTF8(string) {
|
||||
return map(string, function(char) {
|
||||
var code = char.charCodeAt(0),
|
||||
string = '';
|
||||
if (code < 128) {
|
||||
string = char;
|
||||
} else if (code < 2048) {
|
||||
string = String.fromCharCode(code >> 6 | 192)
|
||||
+ String.fromCharCode(code & 63 | 128);
|
||||
} else {
|
||||
string = String.fromCharCode(code >> 12 | 224)
|
||||
+ String.fromCharCode(code >> 6 & 63 | 128)
|
||||
+ String.fromCharCode(code & 63 | 128);
|
||||
}
|
||||
return string;
|
||||
});
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeUTF8 <f> Decodes an UTF-8-encoded string
|
||||
see http://en.wikipedia.org/wiki/UTF-8
|
||||
(utf8) -> <s> string
|
||||
utf8 <s> Any UTF-8-encoded string
|
||||
> Ox.decodeUTF8('YES')
|
||||
'YES'
|
||||
> Ox.decodeUTF8('\u00C2\u00A5\u00E2\u0082\u00AC\u0024')
|
||||
'¥€$'
|
||||
@*/
|
||||
export function decodeUTF8(string) {
|
||||
var code, i = 0, length = string.length, ret = '';
|
||||
function error(byte, position) {
|
||||
throw new RangeError(
|
||||
'UTF-8 codec can\'t decode byte 0x' +
|
||||
byte.toString(16).toUpperCase() + ' at position ' + position
|
||||
);
|
||||
}
|
||||
while (i < length) {
|
||||
code = [
|
||||
string.charCodeAt(i),
|
||||
string.charCodeAt(i + 1),
|
||||
string.charCodeAt(i + 2)
|
||||
];
|
||||
if (code[0] < 128) {
|
||||
ret += string[i];
|
||||
i++;
|
||||
} else if (
|
||||
code[0] >= 192 && code[0] < 240
|
||||
&& i < length - (code[0] < 224 ? 1 : 2)
|
||||
) {
|
||||
if (code[1] >= 128 && code[1] < 192) {
|
||||
if (code[0] < 224) {
|
||||
ret += String.fromCharCode(
|
||||
(code[0] & 31) << 6 | code[1] & 63
|
||||
);
|
||||
i += 2;
|
||||
} else if (code[2] >= 128 && code[2] < 192) {
|
||||
ret += String.fromCharCode(
|
||||
(code[0] & 15) << 12 | (code[1] & 63) << 6
|
||||
| code[2] & 63
|
||||
);
|
||||
i += 3;
|
||||
} else {
|
||||
error(code[2], i + 2);
|
||||
}
|
||||
} else {
|
||||
error(code[1], i + 1);
|
||||
}
|
||||
} else {
|
||||
error(code[0], i);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.encodeEmailAddress <f> 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 map(part, function(char) {
|
||||
var code = char.charCodeAt(0);
|
||||
return char == ':' ? ':'
|
||||
: '&#'
|
||||
+ (Math.random() < 0.5 ? code : 'x' + code.toString(16))
|
||||
+ ';';
|
||||
});
|
||||
});
|
||||
return '<a href="' + parts[0] + '">' + parts[1] + '</a>';
|
||||
}
|
||||
|
|
@ -1,969 +0,0 @@
|
|||
/**
|
||||
* Format utilities - ES Module Version
|
||||
* Converted from OxJS Format module while preserving original logic and documentation
|
||||
*/
|
||||
|
||||
import { isUndefined, isNumber } from './Type.js';
|
||||
import { mod, round, sum } from './Math.js';
|
||||
import {
|
||||
getDay, getDayOfTheYear, getDate, getFullYear, getMonth, getHours,
|
||||
getMinutes, getSeconds, getISOYear, getISOWeek, getISODay,
|
||||
getWeek, getTimezoneOffset, getTimezoneOffsetString, makeDate,
|
||||
setDate, setFullYear, setMonth, setHours, setMinutes, setSeconds,
|
||||
getDaysInMonth, getFirstDayOfTheYear
|
||||
} from './Date.js';
|
||||
import { filter, map, forEach } from './Collection.js';
|
||||
import { compact } from './Array.js';
|
||||
import { contains } from './Object.js';
|
||||
import { loop } from './Core.js';
|
||||
import { pad } from './String.js';
|
||||
import { _ } from './Locale.js';
|
||||
|
||||
// Helper functions not yet available in other modules
|
||||
function last(array) {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
|
||||
function sortBy(array, property) {
|
||||
return array.slice().sort(function(a, b) {
|
||||
var aVal = Array.isArray(property) ? property.map(p => a[p]) : a[property];
|
||||
var bVal = Array.isArray(property) ? property.map(p => b[p]) : b[property];
|
||||
return aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Constants that would normally be in Ox object
|
||||
const WEEKDAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
const SHORT_WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
const SHORT_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const AMPM = ['AM', 'PM'];
|
||||
const BCAD = ['BC', 'AD'];
|
||||
const PREFIXES = ['', 'k', 'M', 'G', 'T', 'P'];
|
||||
|
||||
/*@
|
||||
formatArea <f> Formats a number of meters as square meters or kilometers
|
||||
> formatArea(1000)
|
||||
'1,000 m²'
|
||||
> formatArea(1000000)
|
||||
'1 km²'
|
||||
@*/
|
||||
export function formatArea(number, decimals) {
|
||||
var k = number >= 1000000 ? 'k' : '';
|
||||
decimals = isUndefined(decimals) ? 8 : decimals;
|
||||
return formatNumber(
|
||||
(k ? number / 1000000 : number).toPrecision(decimals)
|
||||
) + ' ' + k + 'm\u00B2';
|
||||
}
|
||||
|
||||
/*@
|
||||
formatCount <f> Returns a string like "2 items", "1 item" or "no items".
|
||||
> formatCount(0, 'item')
|
||||
'no items'
|
||||
> formatCount(1, 'item')
|
||||
'1 item'
|
||||
> formatCount(1000, 'city', 'cities')
|
||||
'1,000 cities'
|
||||
@*/
|
||||
export function formatCount(number, singular, plural) {
|
||||
plural = (plural || singular + 's') + (number === 2 ? '{2}' : '');
|
||||
return (number === 0 ? _('no') : formatNumber(number))
|
||||
+ ' ' + _(number === 1 ? singular : plural);
|
||||
}
|
||||
|
||||
/*@
|
||||
formatCurrency <f> Formats a number with a currency symbol
|
||||
> formatCurrency(1000, '$', 2)
|
||||
'$1,000.00'
|
||||
@*/
|
||||
export function formatCurrency(number, string, decimals) {
|
||||
return string + formatNumber(number, decimals);
|
||||
}
|
||||
|
||||
/*@
|
||||
formatDate <f> Formats a date according to a format string
|
||||
See
|
||||
<a href="http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/strftime.3.html">strftime</a>
|
||||
and <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>.
|
||||
'E*%' (localized date and time), %Q' (quarter) and '%X'/'%x'
|
||||
(year with 'BC'/'AD') are non-standard.
|
||||
(string) -> <s> formatted date
|
||||
(date, string) -> <s> formatted date
|
||||
(date, string, utc) -> <s> formatted date
|
||||
string <s> format string
|
||||
date <d|n|s> date
|
||||
utc <b> date is utc
|
||||
<script>
|
||||
test.date = new Date('2005/01/02 00:03:04');
|
||||
test.epoch = new Date('1970/01/01 00:00:00');
|
||||
</script>
|
||||
> formatDate(test.date, '%A') // Full weekday
|
||||
'Sunday'
|
||||
> formatDate(test.date, '%a') // Abbreviated weekday
|
||||
'Sun'
|
||||
> formatDate(test.date, '%B') // Full month
|
||||
'January'
|
||||
> formatDate(test.date, '%b') // Abbreviated month
|
||||
'Jan'
|
||||
> formatDate(test.date, '%C') // Century
|
||||
'20'
|
||||
> formatDate(test.date, '%c') // US time and date
|
||||
'01/02/05 12:03:04 AM'
|
||||
> formatDate(test.date, '%D') // US date
|
||||
'01/02/05'
|
||||
> formatDate(test.date, '%d') // Zero-padded day of the month
|
||||
'02'
|
||||
> formatDate(test.date, '%ED') // Localized date and time with seconds
|
||||
'01/02/2005 00:03:04'
|
||||
> formatDate(test.date, '%Ed') // Localized date and time without seconds
|
||||
'01/02/2005 00:03'
|
||||
> formatDate(test.date, '%EL') // Long localized date with weekday
|
||||
'Sunday, January 2, 2005'
|
||||
> formatDate(test.date, '%El') // Long localized date without weekday
|
||||
'January 2, 2005'
|
||||
> formatDate(test.date, '%EM') // Medium localized date with weekday
|
||||
'Sun, Jan 2, 2005'
|
||||
> formatDate(test.date, '%Em') // Medium localized date without weekday
|
||||
'Jan 2, 2005'
|
||||
> formatDate(test.date, '%ES') // Short localized date with century
|
||||
'01/02/2005'
|
||||
> formatDate(test.date, '%Es') // Short localized date without century
|
||||
'01/02/05'
|
||||
> formatDate(test.date, '%ET') // Localized time with seconds
|
||||
'12:03:04 AM'
|
||||
> formatDate(test.date, '%Et') // Localized time without seconds
|
||||
'12:03 AM'
|
||||
> formatDate(test.date, '%e') // Space-padded day of the month
|
||||
' 2'
|
||||
> formatDate(test.date, '%F') // Date
|
||||
'2005-01-02'
|
||||
> formatDate(test.date, '%G') // Full ISO-8601 year
|
||||
'2004'
|
||||
> formatDate(test.date, '%g') // Abbreviated ISO-8601 year
|
||||
'04'
|
||||
> formatDate(test.date, '%H') // Zero-padded hour (24-hour clock)
|
||||
'00'
|
||||
> formatDate(test.date, '%h') // Abbreviated month
|
||||
'Jan'
|
||||
> formatDate(test.date, '%I') // Zero-padded hour (12-hour clock)
|
||||
'12'
|
||||
> formatDate(test.date, '%j') // Zero-padded day of the year
|
||||
'002'
|
||||
> formatDate(test.date, '%k') // Space-padded hour (24-hour clock)
|
||||
' 0'
|
||||
> formatDate(test.date, '%l') // Space-padded hour (12-hour clock)
|
||||
'12'
|
||||
> formatDate(test.date, '%M') // Zero-padded minute
|
||||
'03'
|
||||
> formatDate(test.date, '%m') // Zero-padded month
|
||||
'01'
|
||||
> formatDate(test.date, '%n') // Newline
|
||||
'\n'
|
||||
> formatDate(test.date, '%p') // AM or PM
|
||||
'AM'
|
||||
> formatDate(test.date, '%Q') // Quarter of the year
|
||||
'1'
|
||||
> formatDate(test.date, '%R') // Zero-padded hour and minute
|
||||
'00:03'
|
||||
> formatDate(test.date, '%r') // US time
|
||||
'12:03:04 AM'
|
||||
> formatDate(test.date, '%S') // Zero-padded second
|
||||
'04'
|
||||
> formatDate(test.epoch, '%s', true) // Number of seconds since the Epoch
|
||||
'0'
|
||||
> formatDate(test.date, '%T') // Time
|
||||
'00:03:04'
|
||||
> formatDate(test.date, '%t') // Tab
|
||||
'\t'
|
||||
> formatDate(test.date, '%U') // Zero-padded week of the year (00-53, Sunday as first day)
|
||||
'01'
|
||||
> formatDate(test.date, '%u') // Decimal weekday (1-7, Monday as first day)
|
||||
'7'
|
||||
> formatDate(test.date, '%V') // Zero-padded ISO-8601 week of the year
|
||||
'53'
|
||||
> formatDate(test.date, '%v') // Formatted date
|
||||
' 2-Jan-2005'
|
||||
> formatDate(test.date, '%W') // Zero-padded week of the year (00-53, Monday as first day)
|
||||
'00'
|
||||
> formatDate(test.date, '%w') // Decimal weekday (0-6, Sunday as first day)
|
||||
'0'
|
||||
> formatDate(test.date, '%X') // Full year with BC or AD
|
||||
'2005 AD'
|
||||
> formatDate(test.date, '%x') // Full year with BC or AD if year < 1000
|
||||
'2005'
|
||||
> formatDate(test.date, '%Y') // Full year
|
||||
'2005'
|
||||
> formatDate(test.date, '%y') // Abbreviated year
|
||||
'05'
|
||||
> formatDate(test.date, '%Z', true) // Time zone name
|
||||
'UTC'
|
||||
> formatDate(test.date, '%z', true) // Time zone offset
|
||||
'+0000'
|
||||
> formatDate(test.date, '%+').replace(/ [A-Z]+ /, ' XYZ ') // Formatted date and time
|
||||
'Sun Jan 2 00:03:04 XYZ 2005'
|
||||
> formatDate(test.date, '%%')
|
||||
'%'
|
||||
@*/
|
||||
|
||||
// Format patterns for formatDate function
|
||||
var formatPatterns = [
|
||||
['%', function() {
|
||||
return '%{%}';
|
||||
}],
|
||||
['c', function() {
|
||||
return '%D %r';
|
||||
}],
|
||||
['D', function() {
|
||||
return '%m/%d/%y';
|
||||
}],
|
||||
['ED', function() {
|
||||
return '%ES %T';
|
||||
}],
|
||||
['Ed', function() {
|
||||
return '%ES %R';
|
||||
}],
|
||||
['EL', function() {
|
||||
return _('%A, %B %e, %Y');
|
||||
}],
|
||||
['El', function() {
|
||||
return _('%B %e, %Y');
|
||||
}],
|
||||
['EM', function() {
|
||||
return _('%a, %b %e, %Y');
|
||||
}],
|
||||
['Em', function() {
|
||||
return _('%b %e, %Y');
|
||||
}],
|
||||
['ES', function() {
|
||||
return _('%m/%d/%Y');
|
||||
}],
|
||||
['Es', function() {
|
||||
return _('%m/%d/%y');
|
||||
}],
|
||||
['ET', function() {
|
||||
return _('%I:%M:%S %p');
|
||||
}],
|
||||
['Et', function() {
|
||||
return _('%I:%M %p');
|
||||
}],
|
||||
['F', function() {
|
||||
return '%Y-%m-%d';
|
||||
}],
|
||||
['h', function() {
|
||||
return '%b';
|
||||
}],
|
||||
['R', function() {
|
||||
return '%H:%M';
|
||||
}],
|
||||
['r', function() {
|
||||
return '%I:%M:%S %p';
|
||||
}],
|
||||
['T', function() {
|
||||
return '%H:%M:%S';
|
||||
}],
|
||||
['v', function() {
|
||||
return '%e-%b-%Y';
|
||||
}],
|
||||
['\\+', function() {
|
||||
return '%a %b %e %H:%M:%S %Z %Y';
|
||||
}],
|
||||
['A', function(date, utc) {
|
||||
return _(WEEKDAYS[(getDay(date, utc) + 6) % 7]);
|
||||
}],
|
||||
['a', function(date, utc) {
|
||||
return _(SHORT_WEEKDAYS[(getDay(date, utc) + 6) % 7]);
|
||||
}],
|
||||
['B', function(date, utc) {
|
||||
return _(MONTHS[getMonth(date, utc)]);
|
||||
}],
|
||||
['b', function(date, utc) {
|
||||
return _(SHORT_MONTHS[getMonth(date, utc)]);
|
||||
}],
|
||||
['C', function(date, utc) {
|
||||
return Math.floor(getFullYear(date, utc) / 100).toString();
|
||||
}],
|
||||
['d', function(date, utc) {
|
||||
return pad(getDate(date, utc), 2);
|
||||
}],
|
||||
['e', function(date, utc) {
|
||||
return pad(getDate(date, utc), 2, ' ');
|
||||
}],
|
||||
['G', function(date, utc) {
|
||||
return getISOYear(date, utc);
|
||||
}],
|
||||
['g', function(date, utc) {
|
||||
return getISOYear(date, utc).toString().slice(-2);
|
||||
}],
|
||||
['H', function(date, utc) {
|
||||
return pad(getHours(date, utc), 2);
|
||||
}],
|
||||
['I', function(date, utc) {
|
||||
return pad((getHours(date, utc) + 11) % 12 + 1, 2);
|
||||
}],
|
||||
['j', function(date, utc) {
|
||||
return pad(getDayOfTheYear(date, utc), 3);
|
||||
}],
|
||||
['k', function(date, utc) {
|
||||
return pad(getHours(date, utc), 2, ' ');
|
||||
}],
|
||||
['l', function(date, utc) {
|
||||
return pad(((getHours(date, utc) + 11) % 12 + 1), 2, ' ');
|
||||
}],
|
||||
['M', function(date, utc) {
|
||||
return pad(getMinutes(date, utc), 2);
|
||||
}],
|
||||
['m', function(date, utc) {
|
||||
return pad((getMonth(date, utc) + 1), 2);
|
||||
}],
|
||||
['p', function(date, utc) {
|
||||
return _(AMPM[Math.floor(getHours(date, utc) / 12)]);
|
||||
}],
|
||||
['Q', function(date, utc) {
|
||||
return Math.floor(getMonth(date, utc) / 4) + 1;
|
||||
}],
|
||||
['S', function(date, utc) {
|
||||
return pad(getSeconds(date, utc), 2);
|
||||
}],
|
||||
['s', function(date, utc) {
|
||||
return Math.floor((+date - (
|
||||
utc ? getTimezoneOffset(date) : 0
|
||||
)) / 1000);
|
||||
}],
|
||||
['U', function(date, utc) {
|
||||
return pad(getWeek(date, utc), 2);
|
||||
}],
|
||||
['u', function(date, utc) {
|
||||
return getISODay(date, utc);
|
||||
}],
|
||||
['V', function(date, utc) {
|
||||
return pad(getISOWeek(date, utc), 2);
|
||||
}],
|
||||
['W', function(date, utc) {
|
||||
return pad(Math.floor((getDayOfTheYear(date, utc)
|
||||
+ (getFirstDayOfTheYear(date, utc) || 7) - 2) / 7), 2);
|
||||
}],
|
||||
['w', function(date, utc) {
|
||||
return getDay(date, utc);
|
||||
}],
|
||||
['X', function(date, utc) {
|
||||
var y = getFullYear(date, utc);
|
||||
return Math.abs(y) + ' ' + _(BCAD[y < 0 ? 0 : 1]);
|
||||
}],
|
||||
['x', function(date, utc) {
|
||||
var y = getFullYear(date, utc);
|
||||
return Math.abs(y) + (
|
||||
y < 1000 ? ' ' + _(BCAD[y < 0 ? 0 : 1]) : ''
|
||||
);
|
||||
}],
|
||||
['Y', function(date, utc) {
|
||||
return getFullYear(date, utc);
|
||||
}],
|
||||
['y', function(date, utc) {
|
||||
return getFullYear(date, utc).toString().slice(-2);
|
||||
}],
|
||||
['Z', function(date, utc) {
|
||||
return utc ? 'UTC'
|
||||
: (date.toString().split('(')[1] || '').replace(')', '');
|
||||
}],
|
||||
['z', function(date, utc) {
|
||||
return utc ? '+0000' : getTimezoneOffsetString(date);
|
||||
}],
|
||||
['n', function() {
|
||||
return '\n';
|
||||
}],
|
||||
['t', function() {
|
||||
return '\t';
|
||||
}],
|
||||
['\\{%\\}', function() {
|
||||
return '%';
|
||||
}]
|
||||
].map(function(value) {
|
||||
return [new RegExp('%' + value[0], 'g'), value[1]];
|
||||
});
|
||||
|
||||
export function formatDate(date, string, utc) {
|
||||
if (date === '') {
|
||||
return '';
|
||||
}
|
||||
date = makeDate(date);
|
||||
formatPatterns.forEach(function(value) {
|
||||
string = string.replace(value[0], function() {
|
||||
return value[1](date, utc);
|
||||
});
|
||||
});
|
||||
return string;
|
||||
}
|
||||
|
||||
/*@
|
||||
formatDateRange <f> Formats a date range as a string
|
||||
A date range is a pair of arbitrary-presicion date strings
|
||||
> formatDateRange('2000', '2001')
|
||||
'2000'
|
||||
> formatDateRange('2000', '2002')
|
||||
'2000 - 2002'
|
||||
> formatDateRange('2000-01', '2000-02')
|
||||
'January 2000'
|
||||
> formatDateRange('2000-01', '2000-03')
|
||||
'January - March 2000'
|
||||
> formatDateRange('2000-01-01', '2000-01-02')
|
||||
'Sat, Jan 1, 2000'
|
||||
> formatDateRange('2000-01-01', '2000-01-03')
|
||||
'Sat, Jan 1 - Mon, Jan 3, 2000'
|
||||
> formatDateRange('2000-01-01 00', '2000-01-01 01')
|
||||
'Sat, Jan 1, 2000, 00:00'
|
||||
> formatDateRange('2000-01-01 00', '2000-01-01 02')
|
||||
'Sat, Jan 1, 2000, 00:00 - 02:00'
|
||||
> formatDateRange('2000-01-01 00:00', '2000-01-01 00:01')
|
||||
'Sat, Jan 1, 2000, 00:00'
|
||||
> formatDateRange('2000-01-01 00:00', '2000-01-01 00:02')
|
||||
'Sat, Jan 1, 2000, 00:00 - 00:02'
|
||||
> formatDateRange('2000-01-01 00:00:00', '2000-01-01 00:00:01')
|
||||
'Sat, Jan 1, 2000, 00:00:00'
|
||||
> formatDateRange('2000-01-01 00:00:00', '2000-01-01 00:00:02')
|
||||
'Sat, Jan 1, 2000, 00:00:00 - 00:00:02'
|
||||
> formatDateRange('1999-12', '2000-01')
|
||||
'December 1999'
|
||||
> formatDateRange('1999-12-31', '2000-01-01')
|
||||
'Fri, Dec 31, 1999'
|
||||
> formatDateRange('1999-12-31 23:59', '2000-01-01 00:00')
|
||||
'Fri, Dec 31, 1999, 23:59'
|
||||
> formatDateRange('-50', '50')
|
||||
'50 BC - 50 AD'
|
||||
> formatDateRange('-50-01-01', '-50-12-31')
|
||||
'Sun, Jan 1 - Sun, Dec 31, 50 BC'
|
||||
> formatDateRange('-50-01-01 00:00:00', '-50-01-01 23:59:59')
|
||||
'Sun, Jan 1, 50 BC, 00:00:00 - 23:59:59'
|
||||
@*/
|
||||
export function formatDateRange(start, end, utc) {
|
||||
end = end || formatDate(new Date(), '%Y-%m-%d');
|
||||
var isOneUnit = false,
|
||||
range = [start, end],
|
||||
strings,
|
||||
dates = range.map(function(str){
|
||||
return parseDate(str, utc);
|
||||
}),
|
||||
parts = range.map(function(str) {
|
||||
var parts = compact(
|
||||
/(-?\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?/.exec(str)
|
||||
);
|
||||
parts.shift();
|
||||
return parts.map(function(part) {
|
||||
return parseInt(part, 10);
|
||||
});
|
||||
}),
|
||||
precision = parts.map(function(parts) {
|
||||
return parts.length;
|
||||
}),
|
||||
y = parts[0][0] < 0 ? '%X' : '%Y',
|
||||
formats = [
|
||||
y,
|
||||
'%B ' + y,
|
||||
'%a, %b %e, ' + y,
|
||||
'%a, %b %e, ' + y + ', %H:%M',
|
||||
'%a, %b %e, ' + y + ', %H:%M',
|
||||
'%a, %b %e, ' + y + ', %H:%M:%S',
|
||||
];
|
||||
if (precision[0] == precision[1]) {
|
||||
isOneUnit = true;
|
||||
loop(precision[0], function(i) {
|
||||
if (
|
||||
(i < precision[0] - 1 && parts[0][i] != parts[1][i])
|
||||
|| (i == precision[0] - 1 && parts[0][i] != parts[1][i] - 1)
|
||||
) {
|
||||
isOneUnit = false;
|
||||
return false; // break
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isOneUnit) {
|
||||
strings = [formatDate(dates[0], formats[precision[0] - 1], utc)];
|
||||
} else {
|
||||
strings = [
|
||||
formatDate(dates[0], formats[precision[0] - 1], utc),
|
||||
formatDate(dates[1], formats[precision[1] - 1], utc)
|
||||
];
|
||||
// if same year, and neither date is more precise than day,
|
||||
// then omit first year
|
||||
if (
|
||||
parts[0][0] == parts[1][0]
|
||||
&& precision[0] <= 3
|
||||
&& precision[1] <= 3
|
||||
) {
|
||||
strings[0] = formatDate(
|
||||
dates[0], formats[precision[0] - 1].replace(
|
||||
new RegExp(',? ' + y), ''
|
||||
), utc
|
||||
);
|
||||
}
|
||||
// if same day then omit second day
|
||||
if (
|
||||
parts[0][0] == parts[1][0]
|
||||
&& parts[0][1] == parts[1][1]
|
||||
&& parts[0][2] == parts[1][2]
|
||||
) {
|
||||
strings[1] = strings[1].split(', ').pop();
|
||||
}
|
||||
}
|
||||
// %e is a space-padded day
|
||||
return strings.join(' - ').replace(/ /g, ' ');
|
||||
}
|
||||
|
||||
/*@
|
||||
formatDateRangeDuration <f> Formats the duration of a date range as a string
|
||||
A date range is a pair of arbitrary-presicion date strings
|
||||
> formatDateRangeDuration('2000-01-01 00:00:00', '2001-03-04 04:05:06')
|
||||
'1 year 2 months 3 days 4 hours 5 minutes 6 seconds'
|
||||
> formatDateRangeDuration('2000', '2001-01-01 00:00:01')
|
||||
'1 year 1 second'
|
||||
> formatDateRangeDuration('1999', '2000', true)
|
||||
'1 year'
|
||||
> formatDateRangeDuration('2000', '2001', true)
|
||||
'1 year'
|
||||
> formatDateRangeDuration('1999-02', '1999-03', true)
|
||||
'1 month'
|
||||
> formatDateRangeDuration('2000-02', '2000-03', true)
|
||||
'1 month'
|
||||
@*/
|
||||
export function formatDateRangeDuration(start, end, utc) {
|
||||
end = end || formatDate(new Date(), '%Y-%m-%d');
|
||||
var date = parseDate(start, utc),
|
||||
dates = [start, end].map(function(string) {
|
||||
return parseDate(string, utc);
|
||||
}),
|
||||
keys = ['year', 'month', 'day', 'hour', 'minute', 'second'],
|
||||
parts = ['FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds'],
|
||||
values = [];
|
||||
date && keys.forEach(function(key, i) {
|
||||
while (true) {
|
||||
if (key == 'month') {
|
||||
// set the day to the same day in the next month,
|
||||
// or to its last day if the next month is shorter
|
||||
var day = getDate(date, utc);
|
||||
setDate(date, Math.min(
|
||||
day,
|
||||
getDaysInMonth(
|
||||
getFullYear(date, utc),
|
||||
getMonth(date, utc) + 2,
|
||||
utc
|
||||
)
|
||||
), utc);
|
||||
}
|
||||
// advance the date by one unit
|
||||
if (key == 'year') {
|
||||
setFullYear(date, getFullYear(date, utc) + 1, utc);
|
||||
} else if (key == 'month') {
|
||||
setMonth(date, getMonth(date, utc) + 1, utc);
|
||||
} else if (key == 'day') {
|
||||
setDate(date, getDate(date, utc) + 1, utc);
|
||||
} else if (key == 'hour') {
|
||||
setHours(date, getHours(date, utc) + 1, utc);
|
||||
} else if (key == 'minute') {
|
||||
setMinutes(date, getMinutes(date, utc) + 1, utc);
|
||||
} else if (key == 'second') {
|
||||
setSeconds(date, getSeconds(date, utc) + 1, utc);
|
||||
}
|
||||
if (date <= dates[1]) {
|
||||
// still within the range, add one unit
|
||||
values[i] = (values[i] || 0) + 1;
|
||||
} else {
|
||||
// outside the range, rewind the date by one unit
|
||||
if (key == 'year') {
|
||||
setFullYear(date, getFullYear(date, utc) - 1, utc);
|
||||
} else if (key == 'month') {
|
||||
setMonth(date, getMonth(date, utc) - 1, utc);
|
||||
} else if (key == 'day') {
|
||||
setDate(date, getDate(date, utc) - 1, utc);
|
||||
} else if (key == 'hour') {
|
||||
setHours(date, getHours(date, utc) - 1, utc);
|
||||
} else if (key == 'minute') {
|
||||
setMinutes(date, getMinutes(date, utc) - 1, utc);
|
||||
} else if (key == 'second') {
|
||||
setSeconds(date, getSeconds(date, utc) - 1, utc);
|
||||
}
|
||||
// and revert to original day
|
||||
key == 'month' && setDate(date, day, utc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return filter(map(values, function(value, i) {
|
||||
return value ? value + ' ' + keys[i] + (value > 1 ? 's' : '') : '';
|
||||
})).join(' ');
|
||||
}
|
||||
|
||||
/*@
|
||||
formatDegrees <f> Formats degrees as D°MM'SS"
|
||||
> formatDegrees(-111.11, 'lng')
|
||||
"111°06'36\"W"
|
||||
@*/
|
||||
export function formatDegrees(degrees, mode) {
|
||||
var days = 0,
|
||||
seconds = Math.round(Math.abs(degrees) * 3600),
|
||||
sign = degrees < 0 ? '-' : '',
|
||||
array = formatDuration(seconds).split(':');
|
||||
if (array.length == 4) {
|
||||
days = parseInt(array.shift(), 10);
|
||||
}
|
||||
array[0] = days * 24 + parseInt(array[0], 10);
|
||||
return (!mode ? sign : '')
|
||||
+ array[0] + '°' + array[1] + "'" + array[2] + '"'
|
||||
+ (
|
||||
mode == 'lat' ? (degrees < 0 ? 'S' : 'N')
|
||||
: mode == 'lng' ? (degrees < 0 ? 'W' : 'E')
|
||||
: ''
|
||||
);
|
||||
}
|
||||
|
||||
/*@
|
||||
formatDimensions <f> Formats valus as dimension
|
||||
> formatDimensions([1920, 1080], 'px')
|
||||
"1,920 × 1,080 px"
|
||||
@*/
|
||||
export function formatDimensions(array, string) {
|
||||
return array.map(function(value) {
|
||||
return formatNumber(value);
|
||||
}).join(' × ') + (string ? ' ' + string : '');
|
||||
}
|
||||
|
||||
export const formatResolution = formatDimensions;
|
||||
|
||||
/*@
|
||||
formatDuration <f> Formats a duration as a string
|
||||
> formatDuration(3599.999)
|
||||
'01:00:00'
|
||||
> formatDuration(3599.999, 2)
|
||||
'01:00:00.00'
|
||||
> formatDuration(3599.999, 3)
|
||||
'00:59:59.999'
|
||||
> formatDuration(3599.999, 'short')
|
||||
'1h'
|
||||
> formatDuration(3599.999, 3, 'short')
|
||||
'59m 59.999s'
|
||||
> formatDuration(3599.999, 'long')
|
||||
'1 hour'
|
||||
> formatDuration(3599.999, 3, 'long')
|
||||
'59 minutes 59.999 seconds'
|
||||
> formatDuration(1640673)
|
||||
'18:23:44:33'
|
||||
> formatDuration(86520, 2)
|
||||
'1:00:02:00.00'
|
||||
> formatDuration(86520, 'long')
|
||||
'1 day 2 minutes'
|
||||
> formatDuration(31543203, 2)
|
||||
'1:000:02:00:03.00'
|
||||
> formatDuration(31543203, 'long')
|
||||
'1 year 2 hours 3 seconds'
|
||||
> formatDuration(0, 2)
|
||||
'00:00:00.00'
|
||||
> formatDuration(0, 'long')
|
||||
''
|
||||
@*/
|
||||
export function formatDuration(seconds/*, decimals, format*/) {
|
||||
var lastArg = last(arguments),
|
||||
format = lastArg == 'short' || lastArg == 'long' ? lastArg : 'none',
|
||||
decimals = isNumber(arguments[1]) ? arguments[1] : 0,
|
||||
seconds = round(Math.abs(seconds), decimals),
|
||||
values = [
|
||||
Math.floor(seconds / 31536000),
|
||||
Math.floor(seconds % 31536000 / 86400),
|
||||
Math.floor(seconds % 86400 / 3600),
|
||||
Math.floor(seconds % 3600 / 60),
|
||||
formatNumber(seconds % 60, decimals)
|
||||
],
|
||||
string = format == 'short' ? ['y', 'd', 'h', 'm', 's']
|
||||
: format == 'long' ? ['year', 'day', 'hour', 'minute', 'second']
|
||||
: [],
|
||||
padArray = [
|
||||
values[0].toString().length,
|
||||
values[0] ? 3 : values[1].toString().length,
|
||||
2,
|
||||
2,
|
||||
decimals ? decimals + 3 : 2
|
||||
];
|
||||
while (!values[0] && values.length > (format == 'none' ? 3 : 1)) {
|
||||
values.shift();
|
||||
string.shift();
|
||||
padArray.shift();
|
||||
}
|
||||
return filter(map(values, function(value, index) {
|
||||
var ret;
|
||||
if (format == 'none') {
|
||||
ret = pad(value, 'left', padArray[index], '0');
|
||||
} else if (isNumber(value) ? value : parseFloat(value)) {
|
||||
ret = value + (format == 'long' ? ' ' : '') + _(string[index] + (
|
||||
format == 'long'
|
||||
? (value == 1 ? '' : value == 2 ? 's{2}' : 's')
|
||||
: ''
|
||||
));
|
||||
} else {
|
||||
ret = '';
|
||||
}
|
||||
return ret;
|
||||
})).join(format == 'none' ? ':' : ' ');
|
||||
}
|
||||
|
||||
/*@
|
||||
formatISBN <f> Formats a string as an ISBN of a given length (10 or 13)
|
||||
(isbn, length) -> <s> ISBN
|
||||
isbn <s> ISBN
|
||||
length <n> length (10 or 13)
|
||||
> formatISBN('0-306-40615-2', 13, true)
|
||||
'978-0-306-40615-7'
|
||||
> formatISBN('978-0-306-40615-7', 10)
|
||||
'0306406152'
|
||||
@*/
|
||||
export function formatISBN(isbn, length, dashes) {
|
||||
var ret = '';
|
||||
function getCheckDigit(isbn) {
|
||||
var modValue = isbn.length == 10 ? 11 : 10
|
||||
return (mod(modValue - sum(
|
||||
isbn.slice(0, -1).split('').map(function(digit, index) {
|
||||
return isbn.length == 10
|
||||
? parseInt(digit) * (10 - index)
|
||||
: parseInt(digit) * (index % 2 == 0 ? 1 : 3);
|
||||
})
|
||||
), modValue) + '').replace('10', 'X');
|
||||
}
|
||||
isbn = isbn.toUpperCase().replace(/[^\dX]/g, '');
|
||||
if (isbn.length == 10) {
|
||||
isbn = isbn.slice(0, -1).replace(/\D/g, '') + isbn.slice(-1);
|
||||
}
|
||||
if (
|
||||
(isbn.length == 10 || isbn.length == 13)
|
||||
&& isbn.slice(-1) == getCheckDigit(isbn)
|
||||
) {
|
||||
if (isbn.length == length) {
|
||||
ret = isbn
|
||||
} else if (isbn.length == 10 || isbn.slice(0, 3) == '978') {
|
||||
isbn = isbn.length == 10 ? '978' + isbn : isbn.slice(3);
|
||||
ret = isbn.slice(0, -1) + getCheckDigit(isbn);
|
||||
}
|
||||
}
|
||||
return dashes ? [
|
||||
ret.slice(-13, -10),
|
||||
ret.slice(-10, -9),
|
||||
ret.slice(-9, -6),
|
||||
ret.slice(-6, -1),
|
||||
ret.slice(-1)
|
||||
].join('-').replace(/^-+/, '') : ret;
|
||||
}
|
||||
|
||||
/*@
|
||||
formatNumber <f> Formats a number with thousands separators
|
||||
(num, dec) -> <s> format number to string
|
||||
num <n> number
|
||||
dec <n|0> number of decimals
|
||||
> formatNumber(123456789, 3)
|
||||
"123,456,789.000"
|
||||
> formatNumber(-2000000 / 3, 3)
|
||||
"-666,666.667"
|
||||
> formatNumber(666666.666)
|
||||
"666,667"
|
||||
@*/
|
||||
export function formatNumber(number, decimals) {
|
||||
var array = [],
|
||||
abs = Math.abs(number),
|
||||
split = abs.toFixed(decimals).split('.');
|
||||
while (split[0]) {
|
||||
array.unshift(split[0].slice(-3));
|
||||
split[0] = split[0].slice(0, -3);
|
||||
}
|
||||
split[0] = array.join(_(','));
|
||||
return (number < 0 ? '-' : '') + split.join(_('.'));
|
||||
}
|
||||
|
||||
/*@
|
||||
formatOrdinal <f> Formats a number as an ordinal
|
||||
> formatOrdinal(1)
|
||||
"1st"
|
||||
> formatOrdinal(2)
|
||||
"2nd"
|
||||
> formatOrdinal(3)
|
||||
"3rd"
|
||||
> formatOrdinal(4)
|
||||
"4th"
|
||||
> formatOrdinal(11)
|
||||
"11th"
|
||||
> formatOrdinal(12)
|
||||
"12th"
|
||||
> formatOrdinal(13)
|
||||
"13th"
|
||||
@*/
|
||||
export function formatOrdinal(number) {
|
||||
var string = formatNumber(number),
|
||||
length = string.length,
|
||||
lastChar = string[length - 1],
|
||||
ten = length > 1 && string[length - 2] == '1',
|
||||
twenty = length > 1 && !ten;
|
||||
if (lastChar == '1' && !ten) {
|
||||
string += _('st' + (twenty ? '{21}' : ''));
|
||||
} else if (lastChar == '2' && !ten) {
|
||||
string += _('nd' + (twenty ? '{22}' : ''));
|
||||
} else if (lastChar == '3' && !ten) {
|
||||
string += _('rd' + (twenty ? '{23}' : ''));
|
||||
} else {
|
||||
string += _(
|
||||
'th' + (contains('123', lastChar) && ten ? '{1' + lastChar + '}' : '')
|
||||
);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/*@
|
||||
formatPercent <f> Formats the relation of two numbers as a percentage
|
||||
> formatPercent(1, 1000, 2)
|
||||
"0.10%"
|
||||
@*/
|
||||
export function formatPercent(number, total, decimals) {
|
||||
return formatNumber(number / total * 100, decimals) + _('%');
|
||||
}
|
||||
|
||||
/*@
|
||||
formatRoman <f> Formats a number as a roman numeral
|
||||
> formatRoman(1888)
|
||||
'MDCCCLXXXVIII'
|
||||
> formatRoman(1999)
|
||||
'MCMXCIX'
|
||||
> formatRoman(2000)
|
||||
'MM'
|
||||
> formatRoman(-1)
|
||||
''
|
||||
> formatRoman(0)
|
||||
''
|
||||
> formatRoman(9.9)
|
||||
'IX'
|
||||
> formatRoman(10000)
|
||||
'MMMMMMMMMM'
|
||||
@*/
|
||||
export function formatRoman(number) {
|
||||
var string = '';
|
||||
forEach({
|
||||
M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90,
|
||||
L: 50, XL: 40, X: 10, IX: 9, V: 5, IV: 4, I: 1
|
||||
}, function(value, roman) {
|
||||
while (number >= value) {
|
||||
string += roman;
|
||||
number -= value;
|
||||
}
|
||||
});
|
||||
return string;
|
||||
}
|
||||
|
||||
/*@
|
||||
formatSRT <f> Formats subtitles as SRT
|
||||
@*/
|
||||
export function formatSRT(subtitles) {
|
||||
return '\ufeff' + sortBy(subtitles, ['in', 'out']).map(function(subtitle, index) {
|
||||
return [
|
||||
index + 1,
|
||||
['in', 'out'].map(function(key) {
|
||||
return formatDuration(subtitle[key], 3).replace('.', ',');
|
||||
}).join(' --> '),
|
||||
subtitle['text']
|
||||
].join('\r\n')
|
||||
}).join('\r\n\r\n') + '\r\n\r\n';
|
||||
}
|
||||
|
||||
/*@
|
||||
formatString <f> Basic string formatting
|
||||
> formatString('{0}{1}', ['foo', 'bar'])
|
||||
'foobar'
|
||||
> formatString('{a}{b}', {a: 'foo', b: 'bar'})
|
||||
'foobar'
|
||||
> formatString('{a.x}{a.y}', {a: {x: 'foo', y: 'bar'}})
|
||||
'foobar'
|
||||
> formatString('{a\\.b}', {'a.b': 'foobar'})
|
||||
'foobar'
|
||||
> formatString('{1}', ['foobar'])
|
||||
''
|
||||
> formatString('{b}', {a: 'foobar'}, true)
|
||||
'{b}'
|
||||
@*/
|
||||
export function formatString(string, collection, keepUnmatched) {
|
||||
return string.replace(/\{([^}]+)\}/g, function(string, match) {
|
||||
// make sure to not split at escaped dots ('\.')
|
||||
var key,
|
||||
keys = match.replace(/\\\./g, '\n').split('.').map(function(key) {
|
||||
return key.replace(/\n/g, '.');
|
||||
}),
|
||||
value = collection || {};
|
||||
while (keys.length) {
|
||||
key = keys.shift();
|
||||
if (value[key]) {
|
||||
value = value[key];
|
||||
} else {
|
||||
value = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value !== null ? value : keepUnmatched ? '{' + match + '}' : '';
|
||||
});
|
||||
}
|
||||
|
||||
/*@
|
||||
formatUnit <f> Formats a number with a unit
|
||||
> formatUnit(100/3, 'm', 2)
|
||||
'33.33 m'
|
||||
> formatUnit(100/3, '%')
|
||||
'33%'
|
||||
@*/
|
||||
export function formatUnit(number, string, decimals) {
|
||||
return formatNumber(number, decimals)
|
||||
+ (/^[:%]/.test(string) ? '' : ' ') + string;
|
||||
}
|
||||
|
||||
/*@
|
||||
formatValue <f> Formats a numerical value
|
||||
> formatValue(0, "B")
|
||||
"0 B"
|
||||
> formatValue(123456789, "B")
|
||||
"123.5 MB"
|
||||
> formatValue(1234567890, "B", true)
|
||||
"1.15 GiB"
|
||||
@*/
|
||||
// fixme: is this the best name?
|
||||
export function formatValue(number, string, bin) {
|
||||
var base = bin ? 1024 : 1000,
|
||||
length = PREFIXES.length,
|
||||
ret;
|
||||
forEach(PREFIXES, function(prefix, index) {
|
||||
if (number < Math.pow(base, index + 1) || index == length - 1) {
|
||||
ret = formatNumber(
|
||||
number / Math.pow(base, index), index ? index - 1 : 0
|
||||
) + ' ' + prefix + (prefix && bin ? 'i' : '') + string;
|
||||
return false; // break
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a date string - simplified implementation for now
|
||||
*/
|
||||
export function parseDate(string, utc) {
|
||||
return new Date(string);
|
||||
}
|
||||
|
||||
// Export all functions
|
||||
export default {
|
||||
formatArea,
|
||||
formatCount,
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
parseDate,
|
||||
formatDateRange,
|
||||
formatDateRangeDuration,
|
||||
formatDegrees,
|
||||
formatDimensions,
|
||||
formatResolution,
|
||||
formatDuration,
|
||||
formatISBN,
|
||||
formatNumber,
|
||||
formatOrdinal,
|
||||
formatPercent,
|
||||
formatRoman,
|
||||
formatSRT,
|
||||
formatString,
|
||||
formatUnit,
|
||||
formatValue
|
||||
};
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// Geo module - geographic utilities
|
||||
// This will be migrated in a future iteration
|
||||
// For now, we provide basic stubs
|
||||
|
||||
export function parseGeoname() {
|
||||
throw new Error('Geo.parseGeoname not yet migrated to ES modules');
|
||||
}
|
||||
|
|
@ -1,678 +0,0 @@
|
|||
import { range, char, random, values, keyOf, forEach, map, splice, contains, isEmpty, clean } from './Array.js';
|
||||
import { isRegExp, isUndefined } from './Type.js';
|
||||
import { escapeRegExp } from './RegExp.js';
|
||||
import { formatString } from './Format.js';
|
||||
import { pad } 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 = range(2).map(function(){
|
||||
return range(16).map(function() {
|
||||
return char(65 + random(26));
|
||||
}).join('');
|
||||
});
|
||||
|
||||
function addLinksInternal(string, obfuscate) {
|
||||
return string
|
||||
.replace(
|
||||
/\b((https?:\/\/|www\.).+?)([.,:;!?)\]]*?(\s|$))/gi,
|
||||
function(match, url, prefix, end) {
|
||||
prefix = prefix.toLowerCase() == 'www.' ? 'http://' : '';
|
||||
return formatString(
|
||||
'<a href="{prefix}{url}">{url}</a>{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 encodeEmailAddress(mail);
|
||||
} : '<a href="mailto:$1">$1</a>'
|
||||
);
|
||||
}
|
||||
|
||||
function decodeHTMLEntitiesInternal(string) {
|
||||
return string
|
||||
.replace(
|
||||
new RegExp('(' + values(htmlEntities).join('|') + ')', 'g'),
|
||||
function(match) {
|
||||
return keyOf(htmlEntities, match);
|
||||
}
|
||||
)
|
||||
.replace(
|
||||
/&#([0-9A-FX]+);/gi,
|
||||
function(match, code) {
|
||||
return 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 || [];
|
||||
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 <f> Takes a string and adds links for e-mail addresses and URLs
|
||||
(string[, isHTML]) -> <s> Formatted string
|
||||
string <s> String
|
||||
isHTML <b|false> If true, ignore matches in tags or enclosed by links
|
||||
> Ox.addLinks('foo bar <foo@bar.com>')
|
||||
'foo bar <<a href="mailto:foo@bar.com">foo@bar.com</a>>'
|
||||
> Ox.addLinks('www.foo.com/bar#baz, etc.')
|
||||
'<a href="http://www.foo.com/bar#baz">www.foo.com/bar#baz</a>, etc.'
|
||||
> Ox.addLinks('<a href="http://www.foo.com">www.foo.com</a>', true)
|
||||
'<a href="http://www.foo.com">www.foo.com</a>'
|
||||
@*/
|
||||
export function addLinks(string, isHTML) {
|
||||
var isLink = false;
|
||||
return isHTML
|
||||
? splitHTMLTags(string).map(function(string, i) {
|
||||
var isTag = i % 2;
|
||||
if (isTag) {
|
||||
if (/^<a/.test(string)) {
|
||||
isLink = true;
|
||||
} else if (/^<\/a/.test(string)) {
|
||||
isLink = false;
|
||||
}
|
||||
}
|
||||
return isTag || isLink ? string : addLinksInternal(string);
|
||||
}).join('')
|
||||
: normalizeHTML(addLinksInternal(string));
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.encodeEmailAddress <f> 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 map(part, function(char) {
|
||||
var code = char.charCodeAt(0);
|
||||
return char == ':' ? ':'
|
||||
: '&#'
|
||||
+ (Math.random() < 0.5 ? code : 'x' + code.toString(16))
|
||||
+ ';';
|
||||
});
|
||||
});
|
||||
return '<a href="' + parts[0] + '">' + parts[1] + '</a>';
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.encodeHTMLEntities <f> Encodes HTML entities
|
||||
(string[, encodeAll]) -> <s> String
|
||||
string <s> String
|
||||
encodeAll <b|false> 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 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'
|
||||
+ pad(code.toString(16).toUpperCase(), 'left', 4, '0')
|
||||
+ ';';
|
||||
}
|
||||
return char;
|
||||
});
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.decodeHTMLEntities <f> Decodes HTML entities
|
||||
(string[, decodeAll]) -> <s> String
|
||||
string <s> String
|
||||
decodeAll <b|false> 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('<b>β')
|
||||
'<b>β'
|
||||
> Ox.decodeHTMLEntities('<b>β', true)
|
||||
'<b>β</b>'
|
||||
> Ox.decodeHTMLEntities('<b>')
|
||||
'<b>'
|
||||
@*/
|
||||
export function decodeHTMLEntities(string, decodeAll) {
|
||||
return decodeAll
|
||||
? decodeHTMLEntities(normalizeHTML(string))
|
||||
: decodeHTMLEntitiesInternal(string);
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.highlight <f> Highlight matches in string
|
||||
(string, query, classname[, isHTML]) -> Output string
|
||||
string <s> Input string
|
||||
query <r|s> Case-insentitive query string, or regular expression
|
||||
classname <s> Class name for matches
|
||||
isHTML <b|false> If true, the input string is treated as HTML
|
||||
> Ox.highlight('<foo><bar>', 'foo', 'c')
|
||||
'<<span class="c">foo</span>><bar>'
|
||||
> Ox.highlight('&', '&', 'c')
|
||||
'<span class="c">&amp;</span>'
|
||||
> Ox.highlight('&', '&', 'c')
|
||||
'&'
|
||||
> Ox.highlight('<foo> <foo>', '<foo>', 'c', true)
|
||||
'<span class="c"><foo></span> <span class="c"><foo></span>'
|
||||
> Ox.highlight('<span class="name">name</span>', 'name', 'c', true)
|
||||
'<span class="name"><span class="c">name</span></span>'
|
||||
> Ox.highlight('amp & amp', 'amp', 'c', true)
|
||||
'<span class="c">amp</span> & <span class="c">amp</span>'
|
||||
> Ox.highlight('amp & amp', 'amp & amp', 'c', true)
|
||||
'<span class="c">amp & amp</span>'
|
||||
> Ox.highlight('<b><b></b>', '<b>', 'c', true)
|
||||
'<span class="c"><b><b></b></span>'
|
||||
> Ox.highlight('<b><b></b>', '<b>', 'c', true)
|
||||
'<b><b></b>'
|
||||
> Ox.highlight('foo<b>bar</b>baz', 'foobar', 'c', true)
|
||||
'<span class="c">foo<b>bar</b></span>baz'
|
||||
> Ox.highlight('foo<p>bar</p>baz', 'foobar', 'c', true)
|
||||
'foo<p>bar</p>baz'
|
||||
> Ox.highlight('foo <br/>bar baz', 'foo bar', 'c', true)
|
||||
'<span class="c">foo <br>bar</span> baz'
|
||||
@*/
|
||||
export function highlight(string, query, classname, isHTML) {
|
||||
if (!query) {
|
||||
return string;
|
||||
}
|
||||
var cursor = 0,
|
||||
entities = [],
|
||||
matches = [],
|
||||
offset = 0,
|
||||
re = isRegExp(query) ? query
|
||||
: new RegExp(escapeRegExp(query), 'gi'),
|
||||
span = ['<span class="' + classname + '">', '</span>'],
|
||||
tags = [];
|
||||
function insert(array) {
|
||||
// for each replacement
|
||||
array.forEach(function(v) {
|
||||
// replace the modified value with the original value
|
||||
string = 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 = 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 = decodeHTMLEntitiesInternal(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 = 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 = encodeHTMLEntities(
|
||||
string.replace(re, function(value) {
|
||||
matches.push(span.join(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 <f> Normalize HTML (using the DOM)
|
||||
> Ox.normalizeHTML('<b>foo')
|
||||
'<b>foo</b>'
|
||||
> Ox.normalizeHTML('<b>foo</b></b>')
|
||||
'<b>foo</b>'
|
||||
> Ox.normalizeHTML('<'&"> äbçdê')
|
||||
'<\'&"> äbçdê'
|
||||
@*/
|
||||
export function normalizeHTML(html) {
|
||||
if (!regexp.html.test(html)) {
|
||||
return html;
|
||||
}
|
||||
// Simple implementation for Node.js environments
|
||||
// In browser environments, this would use DOM manipulation
|
||||
if (typeof document !== 'undefined') {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.innerHTML;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.parseMarkdown <f> Parses (a tiny subset of) Markdown.
|
||||
Supports `*emphasis*`, `_emphasis_`, `**strong**`, `__strong__`,
|
||||
`` `code` ``, ``` ``code with backtick (`)`` ```,
|
||||
```` ```classname\ngithub-style\ncode blocks\n``` ````,
|
||||
`<mail@example.com>`, `<http://example.com>` and
|
||||
`[text](http://example.com "title")`.
|
||||
> Ox.parseMarkdown('*foo* **bar** `baz` ``back`tick``')
|
||||
'<em>foo</em> <strong>bar</strong> <code>baz</code> <code>back`tick</code>'
|
||||
> Ox.parseMarkdown('foo\n\nbar\n\nbaz')
|
||||
'foo<br><br>bar<br><br>baz'
|
||||
> Ox.parseMarkdown('```foo\n\nbar\n\nbaz\n```')
|
||||
'<pre><code class="foo">bar\n\nbaz\n</code></pre>'
|
||||
> Ox.parseMarkdown('<http://example.com>')
|
||||
'<a href="http://example.com">http://example.com</a>'
|
||||
> Ox.parseMarkdown('`<http://example.com>`')
|
||||
'<code><http://example.com></code>'
|
||||
> Ox.parseMarkdown('[example](http://example.com "example.com")')
|
||||
'<a href="http://example.com" title="example.com">example</a>'
|
||||
> Ox.parseMarkdown('[example](http://example.com?foo=bar&bar=baz)')
|
||||
'<a href="http://example.com?foo=bar&bar=baz">example</a>'
|
||||
> Ox(Ox.parseMarkdown('<mail@example.com>')).startsWith('<a href="')
|
||||
true
|
||||
> Ox(Ox.parseMarkdown('<mail@example.com>')).endsWith('</a>')
|
||||
true
|
||||
> Ox(Ox.parseMarkdown('<mail@example.com>')).count(':')
|
||||
1
|
||||
> Ox(Ox.parseMarkdown('<mail@example.com>')).decodeHTMLEntities()
|
||||
'<a href="mailto:mail@example.com">mail@example.com</a>'
|
||||
*/
|
||||
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(
|
||||
'<pre><code'
|
||||
+ (classname ? ' class="' + classname + '"' : '') + '>'
|
||||
+ code.trim().replace(/</g, '<') + '\n</code></pre>'
|
||||
);
|
||||
return salt.join(array.length - 1);
|
||||
}
|
||||
)
|
||||
.replace(
|
||||
/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
|
||||
function(match, prev, backticks, code, next) {
|
||||
array.push(
|
||||
prev + '<code>'
|
||||
+ code.trim().replace(/</g, '<') + '</code>'
|
||||
);
|
||||
return salt.join(array.length - 1);
|
||||
}
|
||||
)
|
||||
.replace(
|
||||
/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
|
||||
'<strong>$2</strong>'
|
||||
)
|
||||
.replace(
|
||||
/(\*|_)(?=\S)([^\r]*?\S)\1/g,
|
||||
'<em>$2</em>'
|
||||
)
|
||||
.replace(
|
||||
/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
|
||||
function(match, all, text, id, url, rest, quote, title) {
|
||||
return '<a href="' + encodeHTMLEntities(url) + '"' + (
|
||||
title ? ' title="' + encodeHTMLEntities(title) + '"' : ''
|
||||
) + '>' + text + '</a>';
|
||||
}
|
||||
)
|
||||
.replace(
|
||||
/<((https?|ftp|dict):[^'">\s]+)>/gi,
|
||||
'<a href=\"$1\">$1</a>'
|
||||
)
|
||||
.replace(
|
||||
/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
|
||||
function(match, mail) {
|
||||
return encodeEmailAddress(mail);
|
||||
}
|
||||
)
|
||||
.replace(/\n\n/g, '<br><br>')
|
||||
.replace(
|
||||
new RegExp(salt.join('(\\d+)'), 'g'),
|
||||
function(match, index) {
|
||||
return array[parseInt(index)];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.sanitizeHTML <f> Takes untrusted HTML and returns something trustworthy
|
||||
> Ox.sanitizeHTML('http://foo.com, ...')
|
||||
'<a href="http://foo.com">http://foo.com</a>, ...'
|
||||
> Ox.sanitizeHTML('http://foo.com/foo?bar&baz, ...')
|
||||
'<a href="http://foo.com/foo?bar&baz">http://foo.com/foo?bar&baz</a>, ...'
|
||||
> Ox.sanitizeHTML('(see: www.foo.com)')
|
||||
'(see: <a href="http://www.foo.com">www.foo.com</a>)'
|
||||
> Ox.sanitizeHTML('foo@bar.com')
|
||||
'<a href="mailto:foo@bar.com">foo@bar.com</a>'
|
||||
> Ox.sanitizeHTML('<a href="mailto:foo@bar.com">foo</a>')
|
||||
'<a href="mailto:foo@bar.com">foo</a>'
|
||||
> Ox.sanitizeHTML('<a href="http://foo.com">foo</a>')
|
||||
'<a href="http://foo.com">foo</a>'
|
||||
> Ox.sanitizeHTML('<a href="http://www.foo.com/">http://www.foo.com/</a>')
|
||||
'<a href="http://www.foo.com/">http://www.foo.com/</a>'
|
||||
> Ox.sanitizeHTML('<a href="http://foo.com" onclick="alert()">foo</a>')
|
||||
'<a href="http://foo.com">foo</a>'
|
||||
> Ox.sanitizeHTML('<a href="http://foo.com" target="_blank">foo</a>')
|
||||
'<a href="http://foo.com" target="_blank">foo</a>'
|
||||
> Ox.sanitizeHTML('<a href="javascript:alert()">foo</a>')
|
||||
'<a href="javascript:alert()">foo</a>'
|
||||
> Ox.sanitizeHTML('<a href="foo">foo</a>')
|
||||
'<a href="foo">foo</a>'
|
||||
> Ox.sanitizeHTML('<a href="/foo">foo</a>')
|
||||
'<a href="/foo">foo</a>'
|
||||
> Ox.sanitizeHTML('<a href="/">foo</a>')
|
||||
'<a href="/">foo</a>'
|
||||
> Ox.sanitizeHTML('[http://foo.com foo]')
|
||||
'<a href="http://foo.com">foo</a>'
|
||||
> Ox.sanitizeHTML('<div style="direction: rtl">foo</div>')
|
||||
'<div style="direction: rtl">foo</div>'
|
||||
> Ox.sanitizeHTML('<script>alert()</script>')
|
||||
'<script>alert()</script>'
|
||||
> Ox.sanitizeHTML('\'foo\' < \'bar\' && "foo" > "bar"')
|
||||
'\'foo\' < \'bar\' && "foo" > "bar"'
|
||||
> Ox.sanitizeHTML('<b>foo')
|
||||
'<b>foo</b>'
|
||||
> Ox.sanitizeHTML('<b>foo</b></b>')
|
||||
'<b>foo</b>'
|
||||
> Ox.sanitizeHTML('&&')
|
||||
'&&'
|
||||
> Ox.sanitizeHTML('<http://foo.com>')
|
||||
'<<a href="http://foo.com">http://foo.com</a>>'
|
||||
> Ox.sanitizeHTML('<foo value="http://foo.com"></foo>')
|
||||
'"<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 = clean(html); fixme: can this be a parameter?
|
||||
if (contains(validTags, '[]')) {
|
||||
html = html.replace(
|
||||
/\[((\/|https?:\/\/|mailto:).+?) (.+?)\]/gi,
|
||||
'<a href="$1">$3</a>'
|
||||
);
|
||||
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 = !isUndefined(tagMatch[1]);
|
||||
tag = tagMatch[2];
|
||||
attrString = tagMatch[3].trim();
|
||||
while (attrMatch = attrRegexp.exec(attrString)) {
|
||||
if (
|
||||
validAttributes[tag]
|
||||
&& contains(validAttributes[tag], attrMatch[1])
|
||||
) {
|
||||
attrs[attrMatch[1]] = attrMatch[2];
|
||||
}
|
||||
}
|
||||
if (!isClosing && !contains(selfClosingTags, tag)) {
|
||||
level++;
|
||||
}
|
||||
if (
|
||||
!contains(validTags, tag)
|
||||
|| (attrString.length && isEmpty(attrs))
|
||||
) {
|
||||
isValid = false;
|
||||
} else if (!isClosing && requiredAttributes[tag]) {
|
||||
requiredAttributes[tag].forEach(function(attr) {
|
||||
if (isUndefined(attrs[attr])) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isValid && !isEmpty(attrs)) {
|
||||
forEach(attrs, function(value, key) {
|
||||
if (
|
||||
!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 && !isEmpty(attrs)
|
||||
? ' ' + values(map(attrs, function(value, key) {
|
||||
return key + '="' + value + '"';
|
||||
})).join(' ')
|
||||
: '')
|
||||
+ '>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return encodeHTMLEntities(decodeHTMLEntitiesInternal(string));
|
||||
|
||||
}).join('');
|
||||
|
||||
//FIXME: dont add links to urls inside of escaped tags
|
||||
html = addLinks(html, true);
|
||||
html = html.replace(/\n\n/g, '<br/><br/>');
|
||||
// Close extra opening and remove extra closing tags.
|
||||
// Note: this converts ''' to "'" and '"' to '"'
|
||||
return normalizeHTML(html);
|
||||
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.stripTags <f> Strips HTML tags from a string
|
||||
> Ox.stripTags('f<span>o</span>o')
|
||||
'foo'
|
||||
@*/
|
||||
export function stripTags(string) {
|
||||
return string.replace(/<.*?>/g, '');
|
||||
}
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
import { pad } from './String.js';
|
||||
import { encodeUTF8 } from './Encoding.js';
|
||||
|
||||
/*@
|
||||
Ox.oshash <f> Calculates oshash for a given file or blob object. Async.
|
||||
@*/
|
||||
export function oshash(file, callback) {
|
||||
|
||||
// Needs to go via string to work for files > 2GB
|
||||
var hash = fromString(file.size.toString());
|
||||
|
||||
read(0);
|
||||
|
||||
function add(A, B) {
|
||||
var a, b, c, d;
|
||||
d = A[3] + B[3];
|
||||
c = A[2] + B[2] + (d >> 16);
|
||||
d &= 0xffff;
|
||||
b = A[1] + B[1] + (c >> 16);
|
||||
c &= 0xffff;
|
||||
a = A[0] + B[0] + (b >> 16);
|
||||
b &= 0xffff;
|
||||
// Cut off overflow
|
||||
a &= 0xffff;
|
||||
return [a, b, c, d];
|
||||
}
|
||||
|
||||
function fromData(s, offset) {
|
||||
offset = offset || 0;
|
||||
return [
|
||||
s.charCodeAt(offset + 6) + (s.charCodeAt(offset + 7) << 8),
|
||||
s.charCodeAt(offset + 4) + (s.charCodeAt(offset + 5) << 8),
|
||||
s.charCodeAt(offset + 2) + (s.charCodeAt(offset + 3) << 8),
|
||||
s.charCodeAt(offset + 0) + (s.charCodeAt(offset + 1) << 8)
|
||||
];
|
||||
}
|
||||
|
||||
function fromString(str) {
|
||||
var base = 10,
|
||||
blen = 1,
|
||||
i,
|
||||
num,
|
||||
pos,
|
||||
r = [0, 0, 0, 0];
|
||||
for (pos = 0; pos < str.length; pos++) {
|
||||
num = parseInt(str.charAt(pos), base);
|
||||
i = 0;
|
||||
do {
|
||||
while (i < blen) {
|
||||
num += r[3 - i] * base;
|
||||
r[3 - i++] = (num & 0xFFFF);
|
||||
num >>>= 16;
|
||||
}
|
||||
if (num) {
|
||||
blen++;
|
||||
}
|
||||
} while (num);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function hex(h) {
|
||||
return (
|
||||
pad(h[0].toString(16), 'left', 4, '0')
|
||||
+ pad(h[1].toString(16), 'left', 4, '0')
|
||||
+ pad(h[2].toString(16), 'left', 4, '0')
|
||||
+ pad(h[3].toString(16), 'left', 4, '0')
|
||||
).toLowerCase();
|
||||
}
|
||||
|
||||
function read(offset, last) {
|
||||
var blob,
|
||||
block = 65536,
|
||||
length = 8,
|
||||
reader = new FileReader();
|
||||
reader.onload = function(data) {
|
||||
var s = data.target.result,
|
||||
s_length = s.length - length,
|
||||
i;
|
||||
for (i = 0; i <= s_length; i += length) {
|
||||
hash = add(hash, fromData(s, i));
|
||||
}
|
||||
if (file.size < block || last) {
|
||||
callback(hex(hash));
|
||||
} else {
|
||||
read(file.size - block, true);
|
||||
}
|
||||
};
|
||||
if (file.mozSlice) {
|
||||
blob = file.mozSlice(offset, offset + block);
|
||||
} else if (file.webkitSlice) {
|
||||
blob = file.webkitSlice(offset, offset + block);
|
||||
} else {
|
||||
blob = file.slice(offset, offset + block);
|
||||
}
|
||||
reader.readAsBinaryString(blob);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.SHA1 <f> Calculates SHA1 hash of the given string
|
||||
@*/
|
||||
export function SHA1(msg) {
|
||||
|
||||
function rotate_left(n,s) {
|
||||
var t4 = ( n<<s ) | (n>>>(32-s));
|
||||
return t4;
|
||||
};
|
||||
|
||||
function cvt_hex(val) {
|
||||
var str="";
|
||||
var i;
|
||||
var v;
|
||||
for ( i=7; i>=0; i-- ) {
|
||||
v = (val>>>(i*4))&0x0f;
|
||||
str += v.toString(16);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
var blockstart;
|
||||
var i, j;
|
||||
var W = new Array(80);
|
||||
var H0 = 0x67452301;
|
||||
var H1 = 0xEFCDAB89;
|
||||
var H2 = 0x98BADCFE;
|
||||
var H3 = 0x10325476;
|
||||
var H4 = 0xC3D2E1F0;
|
||||
var A, B, C, D, E;
|
||||
var temp;
|
||||
|
||||
msg = encodeUTF8(msg);
|
||||
|
||||
var msg_len = msg.length;
|
||||
|
||||
var word_array = new Array();
|
||||
for ( i=0; i<msg_len-3; i+=4 ) {
|
||||
j = msg.charCodeAt(i)<<24 | msg.charCodeAt(i+1)<<16 |
|
||||
msg.charCodeAt(i+2)<<8 | msg.charCodeAt(i+3);
|
||||
word_array.push( j );
|
||||
}
|
||||
|
||||
switch( msg_len % 4 ) {
|
||||
case 0:
|
||||
i = 0x080000000;
|
||||
break;
|
||||
case 1:
|
||||
i = msg.charCodeAt(msg_len-1)<<24 | 0x0800000;
|
||||
break;
|
||||
case 2:
|
||||
i = msg.charCodeAt(msg_len-2)<<24 | msg.charCodeAt(msg_len-1)<<16 | 0x08000;
|
||||
break;
|
||||
case 3:
|
||||
i = msg.charCodeAt(msg_len-3)<<24 | msg.charCodeAt(msg_len-2)<<16 | msg.charCodeAt(msg_len-1)<<8 | 0x80;
|
||||
break;
|
||||
}
|
||||
|
||||
word_array.push( i );
|
||||
|
||||
while( (word_array.length % 16) != 14 ) {
|
||||
word_array.push( 0 );
|
||||
}
|
||||
|
||||
word_array.push( msg_len>>>29 );
|
||||
word_array.push( (msg_len<<3)&0x0ffffffff );
|
||||
|
||||
|
||||
for ( blockstart=0; blockstart<word_array.length; blockstart+=16 ) {
|
||||
|
||||
for ( i=0; i<16; i++ ) {
|
||||
W[i] = word_array[blockstart+i];
|
||||
}
|
||||
for ( i=16; i<=79; i++ ) {
|
||||
W[i] = rotate_left(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1);
|
||||
}
|
||||
|
||||
A = H0;
|
||||
B = H1;
|
||||
C = H2;
|
||||
D = H3;
|
||||
E = H4;
|
||||
|
||||
for ( i= 0; i<=19; i++ ) {
|
||||
temp = (rotate_left(A,5) + ((B&C) | (~B&D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
|
||||
E = D;
|
||||
D = C;
|
||||
C = rotate_left(B, 30);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for ( i=20; i<=39; i++ ) {
|
||||
temp = (rotate_left(A,5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
|
||||
E = D;
|
||||
D = C;
|
||||
C = rotate_left(B, 30);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for ( i=40; i<=59; i++ ) {
|
||||
temp = (rotate_left(A,5) + ((B&C) | (B&D) | (C&D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
|
||||
E = D;
|
||||
D = C;
|
||||
C = rotate_left(B, 30);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for ( i=60; i<=79; i++ ) {
|
||||
temp = (rotate_left(A,5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
|
||||
E = D;
|
||||
D = C;
|
||||
C = rotate_left(B, 30);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
H0 = (H0 + A) & 0x0ffffffff;
|
||||
H1 = (H1 + B) & 0x0ffffffff;
|
||||
H2 = (H2 + C) & 0x0ffffffff;
|
||||
H3 = (H3 + D) & 0x0ffffffff;
|
||||
H4 = (H4 + E) & 0x0ffffffff;
|
||||
|
||||
}
|
||||
var temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
|
||||
return temp.toLowerCase();
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
// JavaScript module - complex documentation parser and test runner
|
||||
// This will be migrated in a future iteration due to its complexity
|
||||
// For now, we provide basic stubs
|
||||
|
||||
export function doc() {
|
||||
throw new Error('JavaScript.doc not yet migrated to ES modules');
|
||||
}
|
||||
|
||||
export function test() {
|
||||
throw new Error('JavaScript.test not yet migrated to ES modules');
|
||||
}
|
||||
|
||||
export function tokenize() {
|
||||
throw new Error('JavaScript.tokenize not yet migrated to ES modules');
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
/*@
|
||||
Ox.escapeRegExp <f> Escapes a string for use in a regular expression
|
||||
(str) -> <r> Escaped string
|
||||
str <s> String
|
||||
> Ox.escapeRegExp('foo.com/bar?baz')
|
||||
'foo\\.com\\/bar\\?baz'
|
||||
> new RegExp(Ox.escapeRegExp('/\\^$*+?.-|(){}[]')).test('/\\^$*+?.-|(){}[]')
|
||||
true
|
||||
@*/
|
||||
// see https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions
|
||||
export function escapeRegExp(string) {
|
||||
return (string + '').replace(/([\/\\^$*+?.\-|(){}[\]])/g, '\\$1');
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
import { isArray, isNumber, isString, isUndefined } from './Type.js';
|
||||
import { map, slice } from './Collection.js';
|
||||
import { map } from './Collection.js';
|
||||
|
||||
/**
|
||||
* Returns a string with the first letter capitalized
|
||||
|
|
@ -242,22 +242,6 @@ export function wordwrap(string, length, newline, balanced) {
|
|||
return lines.join(newline);
|
||||
}
|
||||
|
||||
/*@
|
||||
Ox.char <f> Alias for String.fromCharCode
|
||||
@*/
|
||||
export const char = String.fromCharCode;
|
||||
|
||||
/*@
|
||||
Ox.splice <f> `[].splice` for strings, returns a new string
|
||||
> Ox.splice('12xxxxx89', 2, 5, 3, 4, 5, 6, 7)
|
||||
'123456789'
|
||||
@*/
|
||||
export function splice(string, index, remove) {
|
||||
const array = string.split('');
|
||||
Array.prototype.splice.apply(array, slice(arguments, 1));
|
||||
return array.join('');
|
||||
}
|
||||
|
||||
// Export all functions
|
||||
export default {
|
||||
capitalize,
|
||||
|
|
@ -282,7 +266,5 @@ export default {
|
|||
truncate,
|
||||
uppercase,
|
||||
words,
|
||||
wordwrap,
|
||||
char,
|
||||
splice
|
||||
wordwrap
|
||||
};
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// Video module - video manipulation utilities
|
||||
// This will be migrated in a future iteration
|
||||
// For now, we provide basic stubs
|
||||
|
||||
export function parsePath() {
|
||||
throw new Error('Video.parsePath not yet migrated to ES modules');
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/**
|
||||
* Stub implementations for modules not yet converted
|
||||
* These will be replaced with full implementations
|
||||
*/
|
||||
|
||||
// Format utilities stub
|
||||
export const FormatUtils = {
|
||||
formatNumber: (n) => n.toString(),
|
||||
formatDuration: (ms) => `${ms}ms`,
|
||||
formatBytes: (b) => `${b}B`
|
||||
};
|
||||
|
||||
// Color utilities stub
|
||||
export const ColorUtils = {
|
||||
rgb: (r, g, b) => `rgb(${r}, ${g}, ${b})`,
|
||||
hex: (color) => color
|
||||
};
|
||||
|
||||
// Encoding utilities stub
|
||||
export const EncodingUtils = {
|
||||
encodeBase64: (str) => btoa(str),
|
||||
decodeBase64: (str) => atob(str)
|
||||
};
|
||||
|
||||
// RegExp utilities stub
|
||||
export const RegExpUtils = {
|
||||
escape: (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
};
|
||||
|
||||
// HTML utilities stub
|
||||
export const HTMLUtils = {
|
||||
encode: (str) => str.replace(/[<>&"']/g, (c) => `&#${c.charCodeAt(0)};`),
|
||||
decode: (str) => str
|
||||
};
|
||||
|
||||
// Async utilities stub
|
||||
export const AsyncUtils = {
|
||||
sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
|
||||
series: async (tasks) => {
|
||||
const results = [];
|
||||
for (const task of tasks) {
|
||||
results.push(await task());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
};
|
||||
|
||||
// Geo utilities stub (enhance existing)
|
||||
export const GeoUtils = {
|
||||
getDistance: (a, b) => Math.sqrt(Math.pow(b.lat - a.lat, 2) + Math.pow(b.lng - a.lng, 2))
|
||||
};
|
||||
|
||||
// JavaScript utilities stub
|
||||
export const JavaScriptUtils = {
|
||||
minify: (code) => code,
|
||||
tokenize: (code) => []
|
||||
};
|
||||
|
|
@ -16,22 +16,18 @@ import * as CollectionUtils from './core/Collection.js';
|
|||
import * as MathUtils from './core/Math.js';
|
||||
import * as ObjectUtils from './core/Object.js';
|
||||
import * as DateUtils from './core/Date.js';
|
||||
import * as DOMUtils from './core/DOM.js';
|
||||
import * as RequestUtils from './core/Request.js';
|
||||
import * as LocaleUtils from './core/Locale.js';
|
||||
import * as Constants from './core/Constants.js';
|
||||
|
||||
// Import newly converted modules
|
||||
import * as FormatUtils from './core/Format.js';
|
||||
import * as ColorUtils from './core/Color.js';
|
||||
import * as EncodingUtils from './core/Encoding.js';
|
||||
import * as RegExpUtils from './core/RegExp.js';
|
||||
import * as HTMLUtils from './core/HTML.js';
|
||||
import * as DOMUtils from './core/DOM.js';
|
||||
import * as RequestUtils from './core/Request.js';
|
||||
import * as AsyncUtils from './core/Async.js';
|
||||
import * as HashUtils from './core/Hash.js';
|
||||
import * as GeoUtils from './core/Geo.js';
|
||||
import * as JavaScriptUtils from './core/JavaScript.js';
|
||||
import * as VideoUtils from './core/Video.js';
|
||||
import * as LocaleUtils from './core/Locale.js';
|
||||
import * as Constants from './core/Constants.js';
|
||||
|
||||
// Create the main Ox object
|
||||
const Ox = function(value) {
|
||||
|
|
@ -54,13 +50,11 @@ Object.assign(Ox,
|
|||
EncodingUtils,
|
||||
RegExpUtils,
|
||||
HTMLUtils,
|
||||
HashUtils,
|
||||
DOMUtils,
|
||||
RequestUtils,
|
||||
AsyncUtils,
|
||||
GeoUtils,
|
||||
JavaScriptUtils,
|
||||
VideoUtils,
|
||||
LocaleUtils,
|
||||
Constants
|
||||
);
|
||||
|
|
@ -91,13 +85,11 @@ export {
|
|||
EncodingUtils,
|
||||
RegExpUtils,
|
||||
HTMLUtils,
|
||||
HashUtils,
|
||||
DOMUtils,
|
||||
RequestUtils,
|
||||
AsyncUtils,
|
||||
GeoUtils,
|
||||
JavaScriptUtils,
|
||||
VideoUtils,
|
||||
LocaleUtils,
|
||||
Constants
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
/**
|
||||
* Test setup for running extracted OxJS inline tests
|
||||
*/
|
||||
|
||||
// Load OxJS ES modules
|
||||
import Ox from '../src/ox/index.js';
|
||||
|
||||
// Helper function to evaluate test statements in context
|
||||
global.evaluateInContext = async function(statement) {
|
||||
try {
|
||||
// This will need to be enhanced to handle async tests
|
||||
// For now, we'll use eval which isn't ideal but matches the original test system
|
||||
return eval(statement);
|
||||
} catch (error) {
|
||||
console.error('Error evaluating:', statement, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Make Ox available globally for tests
|
||||
global.Ox = Ox;
|
||||
console.log('Test environment setup complete');
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import { resolve } from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/ox/index.js'),
|
||||
name: 'Ox',
|
||||
formats: ['es', 'umd'],
|
||||
fileName: (format) => {
|
||||
if (format === 'es') return 'ox.esm.js';
|
||||
if (format === 'umd') return 'ox.umd.js';
|
||||
return `ox.${format}.js`;
|
||||
}
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
globals: {
|
||||
// Any external dependencies would go here
|
||||
},
|
||||
// Keep all exports at top level
|
||||
preserveModules: false,
|
||||
// Ensure compatibility with older environments
|
||||
generatedCode: {
|
||||
constBindings: false
|
||||
}
|
||||
}
|
||||
},
|
||||
sourcemap: true,
|
||||
minify: false, // We'll minify separately for min/Ox.js
|
||||
outDir: 'dist',
|
||||
emptyOutDir: false
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -5,7 +5,7 @@ export default defineConfig({
|
|||
test: {
|
||||
globals: true,
|
||||
environment: 'node', // Use node for now to avoid jsdom issues
|
||||
setupFiles: './test/test-setup.js'
|
||||
setupFiles: './test/setup.js'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue