2015-12-17 03:34:20 +03:00
|
|
|
/**********************************************************************
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
**********************************************************************/
|
2016-08-21 02:19:24 +03:00
|
|
|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
|
|
|
(function(require){ var module={} // make module AMD/node compatible...
|
2016-08-20 22:49:36 +03:00
|
|
|
/*********************************************************************/
|
2015-12-17 03:34:20 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
|
|
2015-12-31 10:37:21 +03:00
|
|
|
String.prototype.capitalize = function(){
|
2017-01-08 03:32:49 +03:00
|
|
|
return this == '' ?
|
|
|
|
|
this
|
|
|
|
|
: this[0].toUpperCase() + this.slice(1) }
|
2015-12-31 10:37:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// XXX not sure if this has to be a utility or a method...
|
|
|
|
|
Object.get = function(obj, name, dfl){
|
|
|
|
|
var val = obj[name]
|
|
|
|
|
if(val === undefined && dfl != null){
|
|
|
|
|
return dfl
|
|
|
|
|
}
|
|
|
|
|
return val
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 04:15:42 +03:00
|
|
|
Object.defineProperty(Object.prototype, 'run', {
|
|
|
|
|
enumerable: false,
|
|
|
|
|
value: function(func){
|
|
|
|
|
var res = func ? func.call(this) : undefined
|
|
|
|
|
return res === undefined ? this : res
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2015-12-31 10:37:21 +03:00
|
|
|
|
2018-11-14 15:18:06 +03:00
|
|
|
// Array.prototype.flat polyfill...
|
|
|
|
|
//
|
|
|
|
|
// NOTE: .flat(..) is not yet supported in IE/Edge...
|
|
|
|
|
Array.prototype.flat
|
|
|
|
|
|| (Array.prototype.flat = function(depth){
|
|
|
|
|
depth = typeof(depth) == typeof(123) ? depth : 1
|
|
|
|
|
return this.reduce(function(res, e){
|
|
|
|
|
return res.concat(e instanceof Array && depth > 0 ?
|
|
|
|
|
e.flat(depth-1)
|
|
|
|
|
: [e]) }, []) })
|
|
|
|
|
|
|
|
|
|
|
2018-08-21 14:31:19 +03:00
|
|
|
// Extended map...
|
|
|
|
|
//
|
|
|
|
|
// .emap(func)
|
|
|
|
|
// -> array
|
|
|
|
|
//
|
|
|
|
|
// func has the same input signature used in .map(..) but returns an array
|
|
|
|
|
// that will be merged into the resulting array. This frees us from the
|
|
|
|
|
// 1:1 nature of .map(..) and adds the ability to return 0 or more items
|
|
|
|
|
// on each iteration.
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// Example:
|
|
|
|
|
// // double each item...
|
|
|
|
|
// ;[1, 2, 3]
|
|
|
|
|
// .emap(function(e){ return [e, e] })
|
|
|
|
|
// // -> [1, 1, 2, 2, 3, 3]
|
|
|
|
|
//
|
|
|
|
|
// // filter-like behaviour...
|
|
|
|
|
// ;[1, 2, 3]
|
|
|
|
|
// .emap(function(e){ retunr e%2 == 0 ? [] : e })
|
|
|
|
|
// // -> [2]
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// NOTE: if func returns a non-Array it will be placed in the resulting
|
|
|
|
|
// array as-is...
|
|
|
|
|
// NOTE: to return an explicit array, wrap it in an array, e.g:
|
|
|
|
|
// ;[1, 2, 3]
|
|
|
|
|
// .emap(function(e){ return [[e]] })
|
|
|
|
|
// // -> [[1], [2], [3]]
|
2018-11-14 15:18:06 +03:00
|
|
|
Array.prototype.emap = function(func){ return this.map(func).flat() }
|
2018-08-21 14:31:19 +03:00
|
|
|
|
|
|
|
|
|
2015-12-31 10:37:21 +03:00
|
|
|
// Compact a sparse array...
|
|
|
|
|
//
|
|
|
|
|
// NOTE: this will not compact in-place.
|
|
|
|
|
Array.prototype.compact = function(){
|
2018-04-03 00:07:38 +03:00
|
|
|
return this.filter(function(){ return true }) }
|
2018-03-23 00:47:39 +03:00
|
|
|
|
|
|
|
|
|
2018-11-14 15:18:06 +03:00
|
|
|
// like .length but for sparse arrays will return the element count...
|
|
|
|
|
// XXX make this a prop...
|
|
|
|
|
/*
|
|
|
|
|
Array.prototype.len = function(){
|
|
|
|
|
//return this.compact().length
|
|
|
|
|
return Object.keys(this).length
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(Array.prototype, 'len', {
|
|
|
|
|
get : function () {
|
|
|
|
|
return Object.keys(this).length
|
|
|
|
|
},
|
|
|
|
|
set : function(val){},
|
|
|
|
|
|
|
|
|
|
// NOTE: this is hear to enable running this module multiple times
|
|
|
|
|
// without any side-effects...
|
|
|
|
|
configurable: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-03-23 00:47:39 +03:00
|
|
|
// Convert an array to object...
|
|
|
|
|
//
|
|
|
|
|
// Format:
|
|
|
|
|
// {
|
|
|
|
|
// <item>: <index>,
|
|
|
|
|
// ...
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// NOTE: items should be strings, other types will get converted to
|
|
|
|
|
// strings and thus may mess things up.
|
|
|
|
|
// NOTE: this will forget repeating items...
|
|
|
|
|
// NOTE: normalize will slow things down...
|
|
|
|
|
Array.prototype.toKeys = function(normalize){
|
2018-03-23 02:22:42 +03:00
|
|
|
return normalize ?
|
|
|
|
|
this.reduce(function(r, e, i){
|
|
|
|
|
r[normalize(e)] = i
|
|
|
|
|
return r
|
|
|
|
|
}, {})
|
|
|
|
|
: this.reduce(function(r, e, i){
|
|
|
|
|
r[e] = i
|
|
|
|
|
return r
|
2018-04-03 00:07:38 +03:00
|
|
|
}, {}) }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Convert an array to a map...
|
|
|
|
|
//
|
|
|
|
|
// This is similar to Array.prototype.toKeys(..) but does not restrict
|
|
|
|
|
// value type to string.
|
|
|
|
|
//
|
|
|
|
|
// Format:
|
|
|
|
|
// Map([
|
|
|
|
|
// [<item>, <index>],
|
|
|
|
|
// ...
|
|
|
|
|
// ])
|
|
|
|
|
//
|
|
|
|
|
// NOTE: this will forget repeating items...
|
|
|
|
|
// NOTE: normalize will slow things down...
|
|
|
|
|
Array.prototype.toMap = function(normalize){
|
|
|
|
|
return normalize ?
|
2018-08-08 03:55:38 +03:00
|
|
|
this
|
|
|
|
|
.reduce(function(m, e, i){
|
|
|
|
|
m.set(normalize(e), i)
|
|
|
|
|
return m
|
|
|
|
|
}, new Map())
|
|
|
|
|
: this
|
|
|
|
|
.reduce(function(m, e, i){
|
|
|
|
|
m.set(e, i)
|
|
|
|
|
return m
|
|
|
|
|
}, new Map()) }
|
2015-12-31 10:37:21 +03:00
|
|
|
|
|
|
|
|
|
2018-03-23 00:47:39 +03:00
|
|
|
// Return an array with duplicate elements removed...
|
2015-12-31 10:37:21 +03:00
|
|
|
//
|
2017-06-06 21:36:17 +03:00
|
|
|
// NOTE: we are not using an Object as an index here as an Array can
|
|
|
|
|
// contain any type of item while Object keys can only be strings...
|
2018-03-23 02:22:42 +03:00
|
|
|
// NOTE: for an array containing only strings use a much faster .uniqueStrings(..)
|
2018-04-03 00:07:38 +03:00
|
|
|
// NOTE: this may not work on IE...
|
2016-04-14 16:37:58 +03:00
|
|
|
Array.prototype.unique = function(normalize){
|
2018-08-08 03:55:38 +03:00
|
|
|
return normalize ?
|
|
|
|
|
[...new Map(this.map(function(e){ return [normalize(e), e] })).values()]
|
|
|
|
|
: [...(new Set(this))] }
|
2018-03-23 00:47:39 +03:00
|
|
|
|
|
|
|
|
|
2015-12-31 10:37:21 +03:00
|
|
|
// Compare two arrays...
|
|
|
|
|
//
|
|
|
|
|
Array.prototype.cmp = function(other){
|
|
|
|
|
if(this === other){
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if(this.length != other.length){
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for(var i=0; i<this.length; i++){
|
|
|
|
|
if(this[i] != other[i]){
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compare two Arrays as sets...
|
|
|
|
|
//
|
|
|
|
|
// This will ignore order
|
2018-04-03 00:07:38 +03:00
|
|
|
//
|
|
|
|
|
// XXX should we use Set(..) here???
|
2015-12-31 10:37:21 +03:00
|
|
|
Array.prototype.setCmp = function(other){
|
|
|
|
|
return this === other
|
2018-03-23 02:22:42 +03:00
|
|
|
|| this
|
|
|
|
|
.unique()
|
|
|
|
|
.sort()
|
|
|
|
|
.cmp(other
|
|
|
|
|
.unique()
|
|
|
|
|
.sort()) }
|
2015-12-31 10:37:21 +03:00
|
|
|
|
|
|
|
|
|
2017-02-01 19:25:58 +03:00
|
|
|
Array.prototype.sortAs = function(other){
|
|
|
|
|
return this.sort(function(a, b){
|
|
|
|
|
var i = other.indexOf(a)
|
|
|
|
|
var j = other.indexOf(b)
|
|
|
|
|
return i < 0 && j < 0 ? 0
|
|
|
|
|
: i < 0 ? 1
|
|
|
|
|
: j < 0 ? -1
|
|
|
|
|
: i - j
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-11-29 04:59:50 +03:00
|
|
|
// Set set operation shorthands...
|
|
|
|
|
Set.prototype.unite = function(other){
|
|
|
|
|
return new Set([...this, ...other]) }
|
|
|
|
|
Set.prototype.intersect = function(other){
|
|
|
|
|
var test = other.has ? 'has' : 'includes'
|
|
|
|
|
return new Set([...this]
|
|
|
|
|
.filter(function(e){ return other[test](e) })) }
|
|
|
|
|
Set.prototype.subtract = function(other){
|
|
|
|
|
var test = other.has ? 'has' : 'includes'
|
|
|
|
|
return new Set([...this]
|
|
|
|
|
.filter(function(e){ return !other[test](e) })) }
|
|
|
|
|
|
|
|
|
|
|
2016-04-20 16:17:12 +03:00
|
|
|
module.chainCmp = function(cmp_chain){
|
|
|
|
|
return function(a, b, get, data){
|
|
|
|
|
var res
|
|
|
|
|
for(var i=0; i < cmp_chain.length; i++){
|
|
|
|
|
res = cmp_chain[i](a, b, get, data)
|
|
|
|
|
if(res != 0){
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-31 10:37:21 +03:00
|
|
|
|
2016-05-18 20:23:31 +03:00
|
|
|
// Get all the accessible keys...
|
|
|
|
|
//
|
|
|
|
|
// This is different to Object.keys(..) in that this will return keys
|
|
|
|
|
// from all the prototypes while .keys(..) will only return the keys
|
|
|
|
|
// defined in the last layer.
|
|
|
|
|
Object.deepKeys = function(obj){
|
|
|
|
|
var res = []
|
|
|
|
|
while(obj != null){
|
|
|
|
|
res = res.concat(Object.keys(obj))
|
|
|
|
|
obj = obj.__proto__
|
|
|
|
|
}
|
|
|
|
|
return res.unique()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make a full key set copy of an object...
|
|
|
|
|
//
|
|
|
|
|
// NOTE: this will not deep-copy the values...
|
|
|
|
|
Object.flatCopy = function(obj){
|
|
|
|
|
var res = {}
|
|
|
|
|
Object.deepKeys(obj).forEach(function(key){
|
|
|
|
|
res[key] = obj[key]
|
|
|
|
|
})
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-12-17 09:14:59 +03:00
|
|
|
// Quote a string and convert to RegExp to match self literally.
|
|
|
|
|
var quoteRegExp =
|
2016-05-28 02:47:52 +03:00
|
|
|
RegExp.quoteRegExp =
|
2015-12-17 09:14:59 +03:00
|
|
|
module.quoteRegExp =
|
|
|
|
|
function(str){
|
|
|
|
|
return str.replace(/([\.\\\/\(\)\[\]\$\*\+\-\{\}\@\^\&\?\<\>])/g, '\\$1')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// XXX do we need to quote anything else???
|
|
|
|
|
var path2url =
|
|
|
|
|
module.path2url =
|
|
|
|
|
function(path){
|
|
|
|
|
// test if we have a schema, and if yes return as-is...
|
2017-06-16 19:41:27 +03:00
|
|
|
if(/^(data|http|https|file|[\w-]*):[\\\/]{2}/.test(path)){
|
2015-12-17 09:14:59 +03:00
|
|
|
return path
|
|
|
|
|
}
|
|
|
|
|
// skip encoding windows drives...
|
2017-06-17 06:33:47 +03:00
|
|
|
path = path
|
2015-12-17 09:14:59 +03:00
|
|
|
.split(/[\\\/]/g)
|
2017-06-17 06:33:47 +03:00
|
|
|
drive = path[0].endsWith(':') ?
|
|
|
|
|
path.shift() + '/'
|
|
|
|
|
: ''
|
|
|
|
|
return drive + (path
|
2015-12-17 09:14:59 +03:00
|
|
|
// XXX these are too aggressive...
|
|
|
|
|
//.map(encodeURI)
|
|
|
|
|
//.map(encodeURIComponent)
|
|
|
|
|
.join('/')
|
|
|
|
|
// NOTE: keep '%' the first...
|
2017-11-17 02:45:44 +03:00
|
|
|
.replace(/%/g, '%25')
|
2015-12-17 09:14:59 +03:00
|
|
|
.replace(/#/g, '%23')
|
|
|
|
|
.replace(/&/g, '%26'))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-12-17 03:34:20 +03:00
|
|
|
// NOTE: we are not using node's path module as we need this to work in
|
|
|
|
|
// all contexts, not only node... (???)
|
|
|
|
|
var normalizePath =
|
|
|
|
|
module.normalizePath =
|
|
|
|
|
function(path){
|
|
|
|
|
return typeof(path) == typeof('str') ? path
|
|
|
|
|
// normalize the slashes...
|
2016-05-29 23:49:01 +03:00
|
|
|
.replace(/\\/g, '/')
|
2015-12-17 03:34:20 +03:00
|
|
|
// remove duplicate '/'
|
|
|
|
|
.replace(/(\/)\1+/g, '/')
|
|
|
|
|
// remove trailing '/'
|
|
|
|
|
.replace(/\/+$/, '')
|
|
|
|
|
// take care of .
|
|
|
|
|
.replace(/\/\.\//g, '/')
|
|
|
|
|
.replace(/\/\.$/, '')
|
|
|
|
|
// take care of ..
|
|
|
|
|
.replace(/\/[^\/]+\/\.\.\//g, '/')
|
|
|
|
|
.replace(/\/[^\/]+\/\.\.$/, '')
|
|
|
|
|
: path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-14 16:01:17 +03:00
|
|
|
/*********************************************************************/
|
|
|
|
|
|
2018-03-30 14:24:05 +03:00
|
|
|
var selectElemText =
|
|
|
|
|
module.selectElemText =
|
|
|
|
|
function(elem){
|
2016-04-14 16:01:17 +03:00
|
|
|
var range = document.createRange()
|
|
|
|
|
range.selectNodeContents(elem)
|
|
|
|
|
var sel = window.getSelection()
|
|
|
|
|
sel.removeAllRanges()
|
|
|
|
|
sel.addRange(range)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-03-30 14:24:05 +03:00
|
|
|
// XXX make this global...
|
|
|
|
|
var getCaretOffset =
|
|
|
|
|
module.getCaretOffset =
|
|
|
|
|
function(elem){
|
2018-03-31 15:24:42 +03:00
|
|
|
var s = window.getSelection()
|
|
|
|
|
if(s.rangeCount == 0){
|
2018-03-30 14:24:05 +03:00
|
|
|
return -1
|
|
|
|
|
}
|
2018-03-31 15:24:42 +03:00
|
|
|
var r = s.getRangeAt(0)
|
|
|
|
|
var pre = r.cloneRange()
|
|
|
|
|
pre.selectNodeContents(elem)
|
|
|
|
|
pre.setEnd(r.endContainer, r.endOffset)
|
|
|
|
|
return pre.toString().length || 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var selectionCollapsed =
|
|
|
|
|
module.selectionCollapsed =
|
|
|
|
|
function(elem){
|
|
|
|
|
var s = window.getSelection()
|
|
|
|
|
if(s.rangeCount == 0){
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return s.getRangeAt(0).cloneRange().collapsed
|
2018-03-30 14:24:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-04-14 16:01:17 +03:00
|
|
|
|
2016-05-28 02:47:52 +03:00
|
|
|
/*********************************************************************/
|
|
|
|
|
// NOTE: repatching a date should not lead to any side effects as this
|
|
|
|
|
// does not add any state...
|
|
|
|
|
var patchDate =
|
|
|
|
|
module.patchDate = function(date){
|
|
|
|
|
date = date || Date
|
|
|
|
|
|
|
|
|
|
date.prototype.toShortDate = function(){
|
|
|
|
|
var y = this.getFullYear()
|
|
|
|
|
var M = this.getMonth()+1
|
|
|
|
|
M = M < 10 ? '0'+M : M
|
|
|
|
|
var D = this.getDate()
|
|
|
|
|
D = D < 10 ? '0'+D : D
|
|
|
|
|
var H = this.getHours()
|
|
|
|
|
H = H < 10 ? '0'+H : H
|
|
|
|
|
var m = this.getMinutes()
|
|
|
|
|
m = m < 10 ? '0'+m : m
|
|
|
|
|
var s = this.getSeconds()
|
|
|
|
|
s = s < 10 ? '0'+s : s
|
|
|
|
|
|
|
|
|
|
return ''+y+'-'+M+'-'+D+' '+H+':'+m+':'+s
|
|
|
|
|
}
|
|
|
|
|
date.prototype.getTimeStamp = function(no_seconds){
|
|
|
|
|
var y = this.getFullYear()
|
|
|
|
|
var M = this.getMonth()+1
|
|
|
|
|
M = M < 10 ? '0'+M : M
|
|
|
|
|
var D = this.getDate()
|
|
|
|
|
D = D < 10 ? '0'+D : D
|
|
|
|
|
var H = this.getHours()
|
|
|
|
|
H = H < 10 ? '0'+H : H
|
|
|
|
|
var m = this.getMinutes()
|
|
|
|
|
m = m < 10 ? '0'+m : m
|
|
|
|
|
var s = this.getSeconds()
|
|
|
|
|
s = s < 10 ? '0'+s : s
|
|
|
|
|
|
|
|
|
|
return ''+y+M+D+H+m+s
|
|
|
|
|
}
|
|
|
|
|
date.prototype.setTimeStamp = function(ts){
|
|
|
|
|
ts = ts.replace(/[^0-9]*/g, '')
|
|
|
|
|
this.setFullYear(ts.slice(0, 4))
|
|
|
|
|
this.setMonth(ts.slice(4, 6)*1-1)
|
|
|
|
|
this.setDate(ts.slice(6, 8))
|
|
|
|
|
this.setHours(ts.slice(8, 10))
|
|
|
|
|
this.setMinutes(ts.slice(10, 12))
|
|
|
|
|
this.setSeconds(ts.slice(12, 14))
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
date.timeStamp = function(){
|
|
|
|
|
return (new this()).getTimeStamp()
|
|
|
|
|
}
|
|
|
|
|
date.fromTimeStamp = function(ts){
|
|
|
|
|
return (new this()).setTimeStamp(ts)
|
|
|
|
|
}
|
|
|
|
|
// convert string time period to milliseconds...
|
|
|
|
|
date.str2ms = function(str, dfl){
|
|
|
|
|
dfl = dfl || 'ms'
|
|
|
|
|
|
|
|
|
|
if(typeof(str) == typeof(123)){
|
|
|
|
|
var val = str
|
|
|
|
|
str = dfl
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
var val = parseFloat(str)
|
|
|
|
|
str = str.trim()
|
|
|
|
|
|
|
|
|
|
// check if a unit is given...
|
|
|
|
|
str = str == val ? dfl : str
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var c = /(m(illi)?(-)?s(ec(ond(s)?)?)?)$/i.test(str) ? 1
|
|
|
|
|
: /s(ec(ond(s)?)?)?$/i.test(str) ? 1000
|
|
|
|
|
: /m(in(ute(s)?)?)?$/i.test(str) ? 1000*60
|
|
|
|
|
: /h(our(s)?)?$/i.test(str) ? 1000*60*60
|
|
|
|
|
: /d(ay(s)?)?$/i.test(str) ? 1000*60*60*24
|
|
|
|
|
: null
|
|
|
|
|
|
|
|
|
|
return c ? val * c : NaN
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return date
|
|
|
|
|
}
|
|
|
|
|
// patch the root date...
|
|
|
|
|
patchDate()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-12-19 09:34:22 +03:00
|
|
|
/*********************************************************************/
|
|
|
|
|
|
2015-12-23 07:09:43 +03:00
|
|
|
// XXX experiment
|
2015-12-31 10:37:21 +03:00
|
|
|
if(typeof(jQuery) != typeof(undefined)){
|
|
|
|
|
jQuery.fn._drag = function(){
|
|
|
|
|
var dragging = false
|
|
|
|
|
var s,
|
|
|
|
|
px, py
|
|
|
|
|
|
|
|
|
|
var elem = $(this)
|
|
|
|
|
.on('mousedown touchstart', function(evt){
|
|
|
|
|
dragging = true
|
|
|
|
|
px = evt.clientX
|
|
|
|
|
px = evt.clientY
|
|
|
|
|
|
|
|
|
|
s = elem.rscale()
|
|
|
|
|
})
|
|
|
|
|
.on('mousemove touchmove', function(evt){
|
|
|
|
|
if(!dragging){
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var x = evt.clientX
|
|
|
|
|
var dx = px - x
|
|
|
|
|
px = x
|
|
|
|
|
|
|
|
|
|
var y = evt.clientY
|
|
|
|
|
var dy = py - y
|
|
|
|
|
py = y
|
|
|
|
|
|
|
|
|
|
elem
|
|
|
|
|
.velocity('stop')
|
|
|
|
|
.velocity({
|
|
|
|
|
translateX: '-=' + (dx / s),
|
|
|
|
|
translateY: '-=' + (dy / s),
|
|
|
|
|
}, 0)
|
|
|
|
|
})
|
|
|
|
|
.on('mouseup touchend', function(evt){
|
|
|
|
|
dragging = false
|
|
|
|
|
elem.velocity('stop')
|
|
|
|
|
})
|
|
|
|
|
}
|
2016-04-14 16:01:17 +03:00
|
|
|
|
2016-04-17 00:58:21 +03:00
|
|
|
|
2016-04-14 16:01:17 +03:00
|
|
|
jQuery.fn.selectText = function(){
|
|
|
|
|
var range = document.createRange()
|
|
|
|
|
|
|
|
|
|
this.each(function(){
|
|
|
|
|
range.selectNodeContents(this)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var sel = window.getSelection()
|
|
|
|
|
sel.removeAllRanges()
|
|
|
|
|
sel.addRange(range)
|
|
|
|
|
|
|
|
|
|
return this
|
|
|
|
|
}
|
2018-03-30 14:24:05 +03:00
|
|
|
jQuery.fn.caretOffset = function(){ return getCaretOffset(this) }
|
2018-03-31 15:24:42 +03:00
|
|
|
jQuery.fn.selectionCollapsed = function(){ return selectionCollapsed(this) }
|
2016-04-14 16:01:17 +03:00
|
|
|
|
|
|
|
|
|
2016-04-17 00:58:21 +03:00
|
|
|
var keyboard = require('lib/keyboard')
|
|
|
|
|
|
|
|
|
|
// Make element editable...
|
|
|
|
|
//
|
|
|
|
|
// Options format:
|
|
|
|
|
// {
|
2017-01-20 23:03:09 +03:00
|
|
|
// // activate (focus) element...
|
|
|
|
|
// //
|
|
|
|
|
// // NOTE: this will also select the element text...
|
2016-06-07 04:28:56 +03:00
|
|
|
// activate: false,
|
|
|
|
|
//
|
2018-03-31 15:24:42 +03:00
|
|
|
// // set multi-line edit mode...
|
2016-04-17 00:58:21 +03:00
|
|
|
// multiline: false,
|
|
|
|
|
//
|
2018-03-31 15:24:42 +03:00
|
|
|
// // if true in multi-line mode, accept filed on Enter, while
|
|
|
|
|
// // ctrl-Enter / meta-Enter insert a new line; otherwise
|
|
|
|
|
// // ctrl-Enter / meta-Enter will accept the edit.
|
|
|
|
|
// accept_on_enter: true,
|
|
|
|
|
//
|
2016-06-07 06:03:16 +03:00
|
|
|
// // clear element value on edit...
|
|
|
|
|
// clear_on_edit: false,
|
|
|
|
|
//
|
2017-02-02 07:26:39 +03:00
|
|
|
// // reset value on commit/abort...
|
|
|
|
|
// // XXX revise default...
|
|
|
|
|
// reset_on_commit: true,
|
2016-04-17 00:58:21 +03:00
|
|
|
// reset_on_abort: true,
|
2016-06-07 04:03:56 +03:00
|
|
|
//
|
2017-02-02 07:26:39 +03:00
|
|
|
// // blur element on commit/abort...
|
2016-06-07 04:03:56 +03:00
|
|
|
// blur_on_commit: false,
|
2017-02-02 07:26:39 +03:00
|
|
|
// blur_on_abort: false,
|
2016-06-07 04:03:56 +03:00
|
|
|
//
|
2017-01-18 20:48:30 +03:00
|
|
|
// // restore focus before disabling the editor...
|
|
|
|
|
// keep_focus_on_parent: true,
|
|
|
|
|
//
|
2017-02-02 07:26:39 +03:00
|
|
|
// // clear selection on commit/abort...
|
2016-06-07 04:03:56 +03:00
|
|
|
// clear_selection_on_commit: true,
|
2017-02-02 07:26:39 +03:00
|
|
|
// clear_selection_on_abort: true,
|
2016-06-07 04:03:56 +03:00
|
|
|
//
|
2017-01-24 06:16:20 +03:00
|
|
|
// // If false unhandled key events will not be propagated to
|
|
|
|
|
// // parents...
|
2017-01-24 06:14:38 +03:00
|
|
|
// propagate_unhandled_keys: true,
|
|
|
|
|
//
|
2017-01-24 06:16:20 +03:00
|
|
|
// // If false the element editable state will not be reset to
|
|
|
|
|
// // the original when edit is done...
|
2017-01-24 06:14:38 +03:00
|
|
|
// reset_on_done: true,
|
|
|
|
|
//
|
2016-06-07 04:28:56 +03:00
|
|
|
// // Keys that will abort the edit...
|
2016-04-17 00:58:21 +03:00
|
|
|
// abort_keys: [
|
|
|
|
|
// 'Esc',
|
|
|
|
|
// ],
|
|
|
|
|
// }
|
|
|
|
|
//
|
2016-05-08 18:51:26 +03:00
|
|
|
// This listens to these events triggerable by user:
|
2017-01-21 05:21:41 +03:00
|
|
|
// 'edit-commit' - will commit changes, this is passed the
|
|
|
|
|
// new text just edited.
|
|
|
|
|
// 'edit-abort' - will reset field, this is passed the
|
|
|
|
|
// original text before the edit.
|
2016-05-08 18:51:26 +03:00
|
|
|
//
|
2017-01-21 23:39:41 +03:00
|
|
|
// These events get passed the relevant text, but the element is
|
|
|
|
|
// likely to be already reset to a different state, to get the
|
|
|
|
|
// element before any state change is started use one of the
|
|
|
|
|
// following variants:
|
|
|
|
|
// 'edit-committing' - triggered within 'edit-commit' but before
|
|
|
|
|
// anything is changed, gets passed the final
|
|
|
|
|
// text (same as 'edit-commit')
|
|
|
|
|
// 'edit-aborting' - triggered within 'edit-abort' but before
|
|
|
|
|
// anything is changed, gets passed the
|
|
|
|
|
// original text value (same as 'edit-abort')
|
|
|
|
|
//
|
2017-09-24 02:17:44 +03:00
|
|
|
// This will try and preserve element content DOM when resetting.
|
|
|
|
|
//
|
2017-01-18 20:48:30 +03:00
|
|
|
//
|
|
|
|
|
// NOTE: removing tabindex will reset focus, so this will attempt to
|
|
|
|
|
// focus the first [tabindex] element up the tree...
|
|
|
|
|
//
|
2017-01-21 05:21:41 +03:00
|
|
|
// XXX add option to select the element on start or just focus it...
|
2017-01-21 05:27:10 +03:00
|
|
|
// .activate: 'select' | true | false
|
2016-04-17 00:58:21 +03:00
|
|
|
// XXX should we just use form elements???
|
|
|
|
|
// ...it's a trade-off, here we add editing functionality and fight
|
|
|
|
|
// a bit the original function, in an input we'll need to fight part
|
|
|
|
|
// of the editing functionality and add our own navigation...
|
|
|
|
|
// XXX move this to a more generic spot...
|
|
|
|
|
jQuery.fn.makeEditable = function(options){
|
2017-01-18 20:48:30 +03:00
|
|
|
var that = this
|
|
|
|
|
|
2016-06-07 04:03:56 +03:00
|
|
|
if(options == false){
|
|
|
|
|
this
|
2017-01-21 04:36:21 +03:00
|
|
|
.removeAttr('contenteditable')
|
|
|
|
|
.removeAttr('tabindex')
|
2017-01-18 20:48:30 +03:00
|
|
|
.removeClass('editable-field')
|
|
|
|
|
|
|
|
|
|
var events = this.data('editable-field-events')
|
|
|
|
|
for(var e in events){
|
|
|
|
|
this.off(e, events[e])
|
|
|
|
|
}
|
|
|
|
|
this.removeData('editable-field-events')
|
2016-06-07 04:03:56 +03:00
|
|
|
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-31 15:24:42 +03:00
|
|
|
options = Object.assign({
|
|
|
|
|
// defaults...
|
|
|
|
|
activate: false,
|
|
|
|
|
multiline: false,
|
|
|
|
|
accept_on_enter: true,
|
|
|
|
|
clear_on_edit: false,
|
|
|
|
|
reset_on_commit: true,
|
|
|
|
|
reset_on_abort: true,
|
|
|
|
|
blur_on_commit: false,
|
|
|
|
|
blur_on_abort: false,
|
|
|
|
|
keep_focus_on_parent: true,
|
|
|
|
|
clear_selection_on_commit: true,
|
|
|
|
|
clear_selection_on_abort: true,
|
|
|
|
|
propagate_unhandled_keys: true,
|
|
|
|
|
reset_on_done: true,
|
|
|
|
|
abort_keys: ['Esc'],
|
|
|
|
|
}, options || {})
|
|
|
|
|
|
|
|
|
|
var original_text = this[0].innerText
|
2017-09-24 02:17:44 +03:00
|
|
|
var original_dom = document.createDocumentFragment()
|
|
|
|
|
this[0].childNodes
|
|
|
|
|
.forEach(function(node){
|
|
|
|
|
original_dom.appendChild(node.cloneNode(true)) })
|
|
|
|
|
var resetOriginal = function(){
|
|
|
|
|
//that.text(original_text)
|
|
|
|
|
that[0].innerHTML = ''
|
|
|
|
|
that[0].appendChild(original_dom.cloneNode(true))
|
|
|
|
|
}
|
2016-04-17 00:58:21 +03:00
|
|
|
|
2016-06-07 04:28:56 +03:00
|
|
|
this.prop('contenteditable', true)
|
|
|
|
|
|
2016-06-07 06:03:16 +03:00
|
|
|
options.activate
|
|
|
|
|
&& options.clear_on_edit
|
2017-02-01 22:50:01 +03:00
|
|
|
// XXX this for some reason breaks on click...
|
2016-06-07 06:03:16 +03:00
|
|
|
&& this.text('')
|
|
|
|
|
|
2016-06-07 04:28:56 +03:00
|
|
|
// NOTE: this will also focus the element...
|
2017-01-20 23:03:09 +03:00
|
|
|
options.activate
|
|
|
|
|
&& this.selectText()
|
2016-04-17 00:58:21 +03:00
|
|
|
|
2016-06-07 04:03:56 +03:00
|
|
|
// do not setup handlers more than once...
|
|
|
|
|
if(!this.hasClass('editable-field')){
|
2017-01-18 20:48:30 +03:00
|
|
|
var events = {}
|
2016-06-07 04:03:56 +03:00
|
|
|
this
|
|
|
|
|
// make the element focusable and selectable...
|
|
|
|
|
.attr('tabindex', '0')
|
|
|
|
|
.addClass('editable-field')
|
2018-03-29 14:48:30 +03:00
|
|
|
.keydown(events.keydown = function(in_evt){
|
2018-03-26 18:05:53 +03:00
|
|
|
var evt = window.event || in_evt
|
2016-06-07 04:03:56 +03:00
|
|
|
if(!that.prop('contenteditable')){
|
|
|
|
|
return
|
|
|
|
|
}
|
2018-03-30 12:58:23 +03:00
|
|
|
|
2018-03-19 01:42:36 +03:00
|
|
|
evt.stopPropagation()
|
2016-06-07 04:03:56 +03:00
|
|
|
|
2018-03-31 15:24:42 +03:00
|
|
|
var c = getCaretOffset(this)
|
|
|
|
|
var collapsed = selectionCollapsed(this)
|
2018-03-19 01:42:36 +03:00
|
|
|
var n = keyboard.code2key(evt.keyCode)
|
2016-06-07 04:03:56 +03:00
|
|
|
|
|
|
|
|
// abort...
|
2018-03-31 15:24:42 +03:00
|
|
|
if((options.abort_keys || []).indexOf(n) >= 0){
|
2017-09-24 02:17:44 +03:00
|
|
|
that.trigger('edit-abort', original_text)
|
2016-06-07 04:03:56 +03:00
|
|
|
|
|
|
|
|
// done -- single line...
|
|
|
|
|
} else if(n == 'Enter'
|
|
|
|
|
&& !options.multiline){
|
2018-03-19 01:42:36 +03:00
|
|
|
evt.preventDefault()
|
2018-03-31 15:24:42 +03:00
|
|
|
that.trigger('edit-commit', that[0].innerText)
|
2016-06-07 04:03:56 +03:00
|
|
|
|
2017-01-21 23:39:41 +03:00
|
|
|
// done -- multi-line...
|
2018-03-31 15:24:42 +03:00
|
|
|
} else if(options.multiline
|
|
|
|
|
&& n == 'Enter'
|
|
|
|
|
&& (options.accept_on_enter ?
|
|
|
|
|
!(evt.ctrlKey || evt.shiftKey || evt.metaKey)
|
|
|
|
|
: (evt.ctrlKey || evt.shiftKey || evt.metaKey)) ){
|
2018-03-19 01:42:36 +03:00
|
|
|
evt.preventDefault()
|
2018-03-31 15:24:42 +03:00
|
|
|
that.trigger('edit-commit', that[0].innerText)
|
2017-01-18 20:48:30 +03:00
|
|
|
|
2018-03-30 12:58:23 +03:00
|
|
|
// multi-line keep keys...
|
2018-03-31 15:24:42 +03:00
|
|
|
} else if(options.multiline
|
|
|
|
|
&& options.accept_on_enter ?
|
|
|
|
|
(n == 'Enter'
|
|
|
|
|
&& (evt.ctrlKey || evt.shiftKey || evt.metaKey))
|
|
|
|
|
: n == 'Enter'){
|
2018-03-30 12:58:23 +03:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
// multi-line arrow keys -- keep key iff not at first/last position...
|
2018-03-31 15:24:42 +03:00
|
|
|
} else if(options.multiline
|
|
|
|
|
&& n == 'Up'
|
|
|
|
|
&& (c > 0 || !collapsed)){
|
2018-03-30 12:58:23 +03:00
|
|
|
return
|
2018-03-31 15:24:42 +03:00
|
|
|
} else if(options.multiline
|
|
|
|
|
&& n == 'Down'
|
|
|
|
|
&& (c < $(this).text().length || !collapsed)){
|
2018-03-30 12:58:23 +03:00
|
|
|
return
|
2018-03-31 15:24:42 +03:00
|
|
|
} else if(n == 'Up' || n == 'Down'){
|
|
|
|
|
evt.preventDefault()
|
|
|
|
|
that.trigger('edit-commit', that[0].innerText)
|
2018-03-30 12:58:23 +03:00
|
|
|
|
2017-01-18 20:48:30 +03:00
|
|
|
// continue handling...
|
2018-03-31 15:24:42 +03:00
|
|
|
} else if(options.propagate_unhandled_keys){
|
2018-03-26 19:54:02 +03:00
|
|
|
// NOTE: jQuery can't reuse browser events, this
|
|
|
|
|
// we need to pass a jq event/proxy here...
|
2018-03-29 14:48:30 +03:00
|
|
|
$(this).parent().trigger(in_evt || evt)
|
2016-06-07 04:03:56 +03:00
|
|
|
}
|
|
|
|
|
})
|
2017-01-18 20:48:30 +03:00
|
|
|
.blur(events.blur = function(){
|
2016-06-07 04:03:56 +03:00
|
|
|
window.getSelection().removeAllRanges()
|
|
|
|
|
})
|
2017-01-18 20:48:30 +03:00
|
|
|
.on('focus click', events['focus click'] = function(evt){
|
2016-06-07 06:03:16 +03:00
|
|
|
evt.stopPropagation()
|
|
|
|
|
options.clear_on_edit
|
|
|
|
|
&& $(this)
|
|
|
|
|
.text('')
|
|
|
|
|
.selectText()
|
|
|
|
|
})
|
2016-06-07 04:03:56 +03:00
|
|
|
// user triggerable events...
|
2017-01-21 23:39:41 +03:00
|
|
|
.on('edit-abort', events['edit-abort'] = function(evt, text){
|
|
|
|
|
that.trigger('edit-aborting', text)
|
|
|
|
|
|
2018-03-31 15:24:42 +03:00
|
|
|
options.clear_selection_on_abort
|
2016-06-07 04:03:56 +03:00
|
|
|
&& window.getSelection().removeAllRanges()
|
|
|
|
|
|
2017-01-18 20:48:30 +03:00
|
|
|
// reset original value...
|
2018-03-31 15:24:42 +03:00
|
|
|
options.reset_on_abort
|
2017-09-24 02:17:44 +03:00
|
|
|
&& resetOriginal()
|
2018-03-31 15:24:42 +03:00
|
|
|
options.blur_on_abort
|
2017-01-18 20:48:30 +03:00
|
|
|
&& this.blur()
|
|
|
|
|
|
|
|
|
|
// restore focus on parent...
|
2018-03-31 15:24:42 +03:00
|
|
|
options.keep_focus_on_parent
|
2017-01-18 20:48:30 +03:00
|
|
|
&& that.parents('[tabindex]').first().focus()
|
|
|
|
|
|
2018-03-31 15:24:42 +03:00
|
|
|
options.reset_on_done
|
2017-01-24 06:14:38 +03:00
|
|
|
&& that.makeEditable(false)
|
2016-06-07 04:03:56 +03:00
|
|
|
})
|
2017-01-21 23:39:41 +03:00
|
|
|
.on('edit-commit', events['edit-commit'] = function(evt, text){
|
|
|
|
|
that.trigger('edit-committing', text)
|
|
|
|
|
|
2018-03-31 15:24:42 +03:00
|
|
|
options.clear_selection_on_commit
|
2016-06-07 04:03:56 +03:00
|
|
|
&& window.getSelection().removeAllRanges()
|
|
|
|
|
|
2017-01-18 20:48:30 +03:00
|
|
|
// reset original value...
|
2018-03-31 15:24:42 +03:00
|
|
|
options.reset_on_commit
|
2017-09-24 02:17:44 +03:00
|
|
|
&& resetOriginal()
|
2018-03-31 15:24:42 +03:00
|
|
|
options.blur_on_commit
|
2017-01-18 20:48:30 +03:00
|
|
|
&& this.blur()
|
|
|
|
|
|
|
|
|
|
// restore focus on parent...
|
2018-03-31 15:24:42 +03:00
|
|
|
options.keep_focus_on_parent
|
2017-01-18 20:48:30 +03:00
|
|
|
&& that.parents('[tabindex]').first().focus()
|
|
|
|
|
|
2018-03-31 15:24:42 +03:00
|
|
|
options.reset_on_done
|
2017-01-24 06:14:38 +03:00
|
|
|
&& that.makeEditable(false)
|
2016-06-07 04:03:56 +03:00
|
|
|
})
|
2017-01-18 20:48:30 +03:00
|
|
|
|
|
|
|
|
this.data('editable-field-events', events)
|
2016-06-07 04:03:56 +03:00
|
|
|
}
|
2016-04-17 00:58:21 +03:00
|
|
|
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-12-23 07:09:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-04-14 16:01:17 +03:00
|
|
|
|
2015-12-17 03:34:20 +03:00
|
|
|
/**********************************************************************
|
2016-08-20 22:49:36 +03:00
|
|
|
* vim:set ts=4 sw=4 : */ return module })
|