jquery.terminal
Version:
jQuery Terminal Emulator is a plugin for creating command line interpreters in your applications.
1,267 lines (1,217 loc) • 583 kB
JavaScript
/**@license
* __ _____ ________ __
* / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /
* __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ /
* / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__
* \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
* \/ /____/ version 2.45.2
*
* This file is part of jQuery Terminal. https://terminal.jcubic.pl
*
* Copyright (c) 2010-2025 Jakub T. Jankiewicz <https://jcubic.pl/me>
* Released under the MIT license
*
* Contains:
*
* Storage plugin Distributed under the MIT License
* modified to work from Data URIs that block storage and cookies in Chrome
* Copyright (c) 2010 Dave Schindler
*
* jQuery Timers licenced with the WTFPL
* <http://jquery.offput.ca/timers/>
*
* Cross-Browser Split 1.1.1
* Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
* Available under the MIT License
*
* jQuery Caret
* Copyright (c) 2009, Gideon Sireling
* 3 clause BSD License
*
* sprintf.js
* Copyright (c) 2007-2013 Alexandru Marasteanu <hello at alexei dot ro>
* licensed under 3 clause BSD license
*
* debounce function from Lodash
* Copyright JS Foundation and other contributors <https://js.foundation/>
* The MIT License
*
* emoji regex v9.0.0 by Mathias Bynens
* MIT license
*
* broken image by Sophia Bai from the Noun Project (CC-BY)
*
* Date: Mon, 21 Jul 2025 22:45:43 +0000
*/
/* global define, Map, BigInt */
/* eslint-disable */
/* istanbul ignore next */
(function(ctx) {
var sprintf = function() {
if (!sprintf.cache.hasOwnProperty(arguments[0])) {
sprintf.cache[arguments[0]] = sprintf.parse(arguments[0]);
}
return sprintf.format.call(null, sprintf.cache[arguments[0]], arguments);
};
sprintf.format = function(parse_tree, argv) {
var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
for (i = 0; i < tree_length; i++) {
node_type = get_type(parse_tree[i]);
if (node_type === 'string') {
output.push(parse_tree[i]);
}
else if (node_type === 'array') {
match = parse_tree[i]; // convenience purposes only
if (match[2]) { // keyword argument
arg = argv[cursor];
for (k = 0; k < match[2].length; k++) {
if (!arg.hasOwnProperty(match[2][k])) {
throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
}
arg = arg[match[2][k]];
}
}
else if (match[1]) { // positional argument (explicit)
arg = argv[match[1]];
}
else { // positional argument (implicit)
arg = argv[cursor++];
}
if (/[^s]/.test(match[8]) && (get_type(arg) !== 'number')) {
throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
}
switch (match[8]) {
case 'b': arg = arg.toString(2); break;
case 'c': arg = String.fromCharCode(arg); break;
case 'd': arg = parseInt(arg, 10); break;
case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
case 'o': arg = arg.toString(8); break;
case 's': arg = ((arg = String(arg)) && match[7] ? arg.slice(0, match[7]) : arg); break;
case 'u': arg = arg >>> 0; break;
case 'x': arg = arg.toString(16); break;
case 'X': arg = arg.toString(16).toUpperCase(); break;
}
arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? ' +' + arg : arg);
pad_character = match[4] ? match[4] === '0' ? '0' : match[4].charAt(1) : ' ';
pad_length = match[6] - String(arg).length;
pad = match[6] ? str_repeat(pad_character, pad_length) : '';
output.push(match[5] ? arg + pad : pad + arg);
}
}
return output.join('');
};
sprintf.cache = {};
sprintf.parse = function(fmt) {
var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
while (_fmt) {
if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
parse_tree.push(match[0]);
}
else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
parse_tree.push('%');
}
else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
if (match[2]) {
arg_names |= 1;
var field_list = [], replacement_field = match[2], field_match = [];
if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
while ((replacement_field = replacement_field.slice(field_match[0].length)) !== '') {
if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else {
throw('[sprintf] huh?');
}
}
}
else {
throw('[sprintf] huh?');
}
match[2] = field_list;
}
else {
arg_names |= 2;
}
if (arg_names === 3) {
throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
}
parse_tree.push(match);
}
else {
throw('[sprintf] huh?');
}
_fmt = _fmt.slice(match[0].length);
}
return parse_tree;
};
var vsprintf = function(fmt, argv, _argv) {
_argv = argv.slice(0);
_argv.splice(0, 0, fmt);
return sprintf.apply(null, _argv);
};
/**
* helpers
*/
function get_type(variable) {
return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
}
function str_repeat(input, multiplier) {
for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
return output.join('');
}
/**
* export to either browser or node.js
*/
ctx.sprintf = sprintf;
ctx.vsprintf = vsprintf;
})(typeof global !== "undefined" ? global : self || window);
// -----------------------------------------------------------------------
/* eslint-enable */
// UMD taken from https://github.com/umdjs/umd
(function(factory, undefined) {
var root;
if (typeof window !== 'undefined') {
root = window;
} else if (typeof self !== 'undefined') {
root = self;
} else if (typeof global !== 'undefined') {
root = global;
} else {
throw new Error('Unknow context');
}
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
// istanbul ignore next
define(['jquery', 'wcwidth'], function(jquery, wcwidth) {
factory(jquery, wcwidth, root);
return jquery;
});
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = function(root, jQuery, wcwidth) {
if (jQuery === undefined) {
// require('jQuery') returns a factory that requires window to
// build a jQuery instance, we normalize how we use modules
// that require this pattern but the window provided is a noop
// if it's defined (how jquery works)
if (typeof window !== 'undefined') {
jQuery = require('jquery');
} else {
jQuery = require('jquery')(root);
}
}
if (wcwidth === undefined) {
wcwidth = require('wcwidth');
}
factory(jQuery, wcwidth, root);
return jQuery;
};
} else {
// Browser
// istanbul ignore next
if (!root.jQuery) {
root.$ = root.cash;
}
factory(root.jQuery || root.cash, root.wcwidth, root);
}
})(function($, wcwidth, root, undefined) {
'use strict';
// -----------------------------------------------------------------------
// :: debug functions
// -----------------------------------------------------------------------
/* eslint-disable */
/* istanbul ignore next */
function debug(str) {
if (false) {
console.log(str);
//$.terminal.active().echo(str);
}
}
/* eslint-enable */
// -----------------------------------------------------------------------
// :: Replacemenet for jQuery 2 deferred objects
// -----------------------------------------------------------------------
function DelayQueue() {
var callbacks = $.Callbacks();
var resolved = false;
var self = this;
this.resolve = function() {
callbacks.fire();
self.resolved = resolved = true;
};
this.add = function(fn) {
if (resolved) {
fn();
} else {
callbacks.add(fn);
}
};
}
// -----------------------------------------------------------------------
// :: EventEmitter class, created by ChatGPT (with small refactoring)
// -----------------------------------------------------------------------
function EventEmitter() {
this._events = {};
}
// -----------------------------------------------------------------------
EventEmitter.prototype.on = function(event, listener) {
if (!this._events[event]) {
this._events[event] = [];
}
this._events[event].push(listener);
};
// -----------------------------------------------------------------------
EventEmitter.prototype.emit = function(event) {
if (this._events[event]) {
var args = Array.prototype.slice.call(arguments, 1);
this._events[event].forEach(function(listener) {
listener.apply(null, args);
});
}
};
// -----------------------------------------------------------------------
EventEmitter.prototype.off = function(event, listener) {
if (!this._events[event]) {
return;
}
if (!listener) {
delete this._events[event];
} else {
this._events[event] = this._events[event].filter(function(l) {
return l !== listener;
});
}
};
// -----------------------------------------------------------------------
EventEmitter.prototype.once = function(event, listener) {
var self = this;
function wrapper() {
self.off(event, wrapper);
listener.apply(null, arguments);
}
this.on(event, wrapper);
};
// -----------------------------------------------------------------------
EventEmitter.prototype.wait_for = function(event) {
var deferred = new $.Deferred();
this.once(event, function() {
deferred.resolve();
});
return deferred.promise();
};
// -----------------------------------------------------------------------
// :: map object to object
// -----------------------------------------------------------------------
$.omap = function(o, fn) {
var result = {};
$.each(o, function(k, v) {
result[k] = fn.call(o, k, v);
});
return result;
};
$.fn.text_length = function() {
return this.map(function() {
return $(this).text().length;
}).get().reduce(function(a, b) {
return a + b;
}, 0);
};
// -----------------------------------------------------------------------
// :: Deep clone of objects and arrays
// -----------------------------------------------------------------------
var Clone = {
clone_object: function(object) {
var tmp = {};
if (typeof object === 'object') {
if (Array.isArray(object)) {
return this.clone_array(object);
} else if (object === null) {
return object;
} else {
for (var key in object) {
if (!object.hasOwnProperty(key)) {
continue;
}
if (Array.isArray(object[key])) {
tmp[key] = this.clone_array(object[key]);
} else if (typeof object[key] === 'object') {
tmp[key] = this.clone_object(object[key]);
} else {
tmp[key] = object[key];
}
}
}
}
return tmp;
},
clone_array: function(array) {
if (!is_function(Array.prototype.map)) {
throw new Error("Your browser don't support ES5 array map " +
'use es5-shim');
}
return array.slice(0).map(function(item) {
if (typeof item === 'object') {
return this.clone_object(item);
} else {
return item;
}
}.bind(this));
}
};
var clone = function(object) {
return Clone.clone_object(object);
};
// -----------------------------------------------------------------------
// IE11 polyfill
// -----------------------------------------------------------------------
/* eslint-disable */
if ('Map' in root && !('clear' in Map.prototype)) {
Map.prototype.clear = function() {
this.forEach(function(value, key, map) {
map.delete(key);
});
};
}
// -----------------------------------------------------------------------
// :: Storage plugin
// -----------------------------------------------------------------------
var localStorage;
/* istanbul ignore next */
(function() {
var hasLS = function() {
try {
var testKey = 'test', storage = window.localStorage;
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
};
var hasCookies = function() {
try {
document.cookie.split(';');
return true;
} catch (e) {
return false;
}
};
// Private data
var isLS = hasLS();
// Private functions
function wls(n, v) {
var c;
if (typeof n === 'string' && typeof v === 'string') {
localStorage[n] = v;
return true;
} else if (typeof n === 'object' && typeof v === 'undefined') {
for (c in n) {
if (n.hasOwnProperty(c)) {
localStorage[c] = n[c];
}
}
return true;
}
return false;
}
function wc(n, v) {
var dt, e, c;
dt = new Date();
dt.setTime(dt.getTime() + 31536000000);
e = '; expires=' + dt.toGMTString();
if (typeof n === 'string' && typeof v === 'string') {
document.cookie = n + '=' + v + e + '; path=/';
return true;
} else if (typeof n === 'object' && typeof v === 'undefined') {
for (c in n) {
if (n.hasOwnProperty(c)) {
document.cookie = c + '=' + n[c] + e + '; path=/';
}
}
return true;
}
return false;
}
function rls(n) {
return localStorage[n];
}
function rc(n) {
var nn, ca, i, c;
nn = n + '=';
ca = document.cookie.split(';');
for (i = 0; i < ca.length; i++) {
c = ca[i];
while (c.charAt(0) === ' ') {
c = c.slice(1, c.length);
}
if (c.indexOf(nn) === 0) {
return c.slice(nn.length, c.length);
}
}
return null;
}
function dls(n) {
return delete localStorage[n];
}
function dc(n) {
return wc(n, '', -1);
}
/**
* Public API
* $.Storage.set("name", "value")
* $.Storage.set({"name1":"value1", "name2":"value2", etc})
* $.Storage.get("name")
* $.Storage.remove("name")
*/
if (!hasCookies() && !isLS) {
localStorage = {};
$.extend({
Storage: {
set: wls,
get: rls,
remove: dls
}
});
} else {
if (isLS) {
localStorage = window.localStorage;
}
$.extend({
Storage: {
set: isLS ? wls : wc,
get: isLS ? rls : rc,
remove: isLS ? dls : dc
}
});
}
})();
// -----------------------------------------------------------------------
// :: Debounce from Lodash
// -----------------------------------------------------------------------
/* istanbul ignore next */
var debounce = (function() {
var FUNC_ERROR_TEXT = 'Expected a function';
function isObject(value) {
var type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
function now() {
return Date.now();
}
return function debounce(func, wait, options) {
var nativeMax = Math.max,
nativeMin = Math.min;
var lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime,
lastInvokeTime = 0,
leading = false,
maxing = false,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = wait || 0;
if (isObject(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? nativeMax(options.maxWait || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function invokeFunc(time) {
var args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
// Start the timer for the trailing edge.
timerId = setTimeout(timerExpired, wait);
// Invoke the leading edge.
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
timeWaiting = wait - timeSinceLastCall;
return maxing
? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
function shouldInvoke(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
}
function timerExpired() {
var time = now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// Restart the timer.
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timerId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
function flush() {
return timerId === undefined ? result : trailingEdge(now());
}
function debounced() {
var time = now(),
isInvoking = shouldInvoke(time);
lastArgs = arguments;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// Handle invocations in a tight loop.
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
};
})();
// -----------------------------------------------------------------------
// :: jQuery Timers
// -----------------------------------------------------------------------
var jQuery = $;
/* istanbul ignore next */
(function($) {
jQuery.fn.extend({
everyTime: function(interval, label, fn, times, belay) {
return this.each(function() {
jQuery.timer.add(this, interval, label, fn, times, belay);
});
},
oneTime: function(interval, label, fn) {
return this.each(function() {
jQuery.timer.add(this, interval, label, fn, 1);
});
},
stopTime: function(label, fn) {
return this.each(function() {
jQuery.timer.remove(this, label, fn);
});
}
});
jQuery.extend({
timer: {
guid: 1,
global: {},
regex: /^([0-9]+)\s*(.*s)?$/,
powers: {
// Yeah this is major overkill...
'ms': 1,
'cs': 10,
'ds': 100,
's': 1000,
'das': 10000,
'hs': 100000,
'ks': 1000000
},
timeParse: function(value) {
if (value === undefined || value === null) {
return null;
}
var result = this.regex.exec(value.toString().trim());
if (result[2]) {
var num = parseInt(result[1], 10);
var mult = this.powers[result[2]] || 1;
return num * mult;
} else {
return value;
}
},
add: function(element, interval, label, fn, times, belay) {
var counter = 0;
if (typeof label === 'function') {
if (!times) {
times = fn;
}
fn = label;
label = interval;
}
interval = jQuery.timer.timeParse(interval);
if (typeof interval !== 'number' ||
isNaN(interval) ||
interval <= 0) {
return;
}
if (times && times.constructor !== Number) {
belay = !!times;
times = 0;
}
times = times || 0;
belay = belay || false;
if (!element.$timers) {
element.$timers = {};
}
if (!element.$timers[label]) {
element.$timers[label] = {};
}
fn.$timerID = fn.$timerID || this.guid++;
var handler = function() {
if (belay && handler.inProgress) {
return;
}
handler.inProgress = true;
if ((++counter > times && times !== 0) ||
fn.call(element, counter) === false) {
jQuery.timer.remove(element, label, fn);
}
handler.inProgress = false;
};
handler.$timerID = fn.$timerID;
if (!element.$timers[label][fn.$timerID]) {
element.$timers[label][fn.$timerID] = setInterval(handler, interval);
}
if (!this.global[label]) {
this.global[label] = [];
}
this.global[label].push(element);
},
remove: function(element, label, fn) {
var timers = element.$timers, ret;
if (timers) {
if (!label) {
for (var lab in timers) {
if (timers.hasOwnProperty(lab)) {
this.remove(element, lab, fn);
}
}
} else if (timers[label]) {
if (fn) {
if (fn.$timerID) {
clearInterval(timers[label][fn.$timerID]);
delete timers[label][fn.$timerID];
}
} else {
for (var _fn in timers[label]) {
if (timers[label].hasOwnProperty(_fn)) {
clearInterval(timers[label][_fn]);
delete timers[label][_fn];
}
}
}
for (ret in timers[label]) {
if (timers[label].hasOwnProperty(ret)) {
break;
}
}
if (!ret) {
ret = null;
delete timers[label];
}
}
for (ret in timers) {
if (timers.hasOwnProperty(ret)) {
break;
}
}
if (!ret) {
element.$timers = null;
}
}
}
}
});
if (/(msie) ([\w.]+)/.exec(navigator.userAgent.toLowerCase())) {
$(window).one('unload', function() {
var global = jQuery.timer.global;
for (var label in global) {
if (global.hasOwnProperty(label)) {
var els = global[label], i = els.length;
while (--i) {
jQuery.timer.remove(els[i], label);
}
}
}
});
}
})(jQuery);
// -----------------------------------------------------------------------
// :: CROSS BROWSER SPLIT
// -----------------------------------------------------------------------
/* istanbul ignore next */
(function(undef) {
// prevent double include
if (!String.prototype.split.toString().match(/\[native/)) {
return;
}
var nativeSplit = String.prototype.split,
compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group
self;
self = function(str, separator, limit) {
// If `separator` is not a regex, use `nativeSplit`
if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
return nativeSplit.call(str, separator, limit);
}
var output = [],
flags = (separator.ignoreCase ? "i" : "") +
(separator.multiline ? "m" : "") +
(separator.extended ? "x" : "") + // Proposed for ES6
(separator.sticky ? "y" : ""), // Firefox 3+
lastLastIndex = 0,
// Make `global` and avoid `lastIndex` issues by working with a copy
separator2, match, lastIndex, lastLength;
separator = new RegExp(separator.source, flags + "g");
str += ""; // Type-convert
if (!compliantExecNpcg) {
// Doesn't need flags gy, but they don't hurt
separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
}
/* Values for `limit`, per the spec:
* If undefined: 4294967295 // Math.pow(2, 32) - 1
* If 0, Infinity, or NaN: 0
* If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
* If negative number: 4294967296 - Math.floor(Math.abs(limit))
* If other: Type-convert, then use the above rules
*/
// ? Math.pow(2, 32) - 1 : ToUint32(limit)
limit = limit === undef ? -1 >>> 0 : limit >>> 0;
while (match = separator.exec(str)) {
// `separator.lastIndex` is not reliable cross-browser
lastIndex = match.index + match[0].length;
if (lastIndex > lastLastIndex) {
output.push(str.slice(lastLastIndex, match.index));
// Fix browsers whose `exec` methods don't consistently return `undefined` for
// nonparticipating capturing groups
if (!compliantExecNpcg && match.length > 1) {
match[0].replace(separator2, function() {
for (var i = 1; i < arguments.length - 2; i++) {
if (arguments[i] === undef) {
match[i] = undef;
}
}
});
}
if (match.length > 1 && match.index < str.length) {
Array.prototype.push.apply(output, match.slice(1));
}
lastLength = match[0].length;
lastLastIndex = lastIndex;
if (output.length >= limit) {
break;
}
}
if (separator.lastIndex === match.index) {
separator.lastIndex++; // Avoid an infinite loop
}
}
if (lastLastIndex === str.length) {
if (lastLength || !separator.test("")) {
output.push("");
}
} else {
output.push(str.slice(lastLastIndex));
}
return output.length > limit ? output.slice(0, limit) : output;
};
// For convenience
String.prototype.split = function(separator, limit) {
return self(this, separator, limit);
};
return self;
})();
// -----------------------------------------------------------------------
// :: jQuery Caret
// -----------------------------------------------------------------------
/* istanbul ignore next */
$.fn.caret = function(pos) {
var target = this[0];
var isContentEditable = target.contentEditable === 'true';
//get
if (arguments.length === 0) {
//HTML5
if (window.getSelection) {
//contenteditable
if (isContentEditable) {
if (!this.is(':focus')) {
target.focus();
}
var range1 = window.getSelection().getRangeAt(0),
range2 = range1.cloneRange();
range2.selectNodeContents(target);
range2.setEnd(range1.endContainer, range1.endOffset);
return range2.toString().length;
}
//textarea
return target.selectionStart;
}
//IE<9
if (document.selection) {
target.focus();
//contenteditable
if (isContentEditable) {
var range1 = document.selection.createRange(),
range2 = document.body.createTextRange();
range2.moveToElementText(target);
range2.setEndPoint('EndToEnd', range1);
return range2.text.length;
}
//textarea
var pos = 0,
range = target.createTextRange(),
range2 = document.selection.createRange().duplicate(),
bookmark = range2.getBookmark();
range.moveToBookmark(bookmark);
while (range.moveStart('character', -1) !== 0) pos++;
return pos;
}
//not supported
return 0;
}
//set
if (pos === -1)
pos = this[isContentEditable? 'text' : 'val']().length;
//HTML5
if (window.getSelection) {
//contenteditable
if (isContentEditable) {
if (!this.is(':focus')) {
target.focus();
}
var selection = window.getSelection();
selection.collapse(selection.focusNode, pos);
}
//textarea
else
target.setSelectionRange(pos, pos);
}
//IE<9
else if (document.body.createTextRange) {
var range = document.body.createTextRange();
range.moveToElementText(target);
range.moveStart('character', pos);
range.collapse(true);
range.select();
}
if (!isContentEditable && !this.is(':focus')) {
target.focus();
}
return pos;
};
/* eslint-enable */
// -----------------------------------------------------------------------
// :: callback based event handler plugin generator
// -----------------------------------------------------------------------
function make_callback_plugin(options) {
var factory_settings = $.extend({
init: $.noop,
destroy: $.noop,
name: 'event'
}, options);
return function(callback, options) {
var trigger = arguments.length === 0;
var unbind = arguments[0] === "unbind";
if (!trigger && !unbind && !is_function(callback)) {
throw new Error('Invalid argument, it need to a function or string ' +
'"unbind" or no arguments.');
}
if (unbind) {
callback = is_function(arguments[1]) ? arguments[1] : null;
}
var data_name = 'callbacks_' + factory_settings.name;
return this.each(function() {
var $this = $(this);
var callbacks;
function handler(arg) {
callbacks.fireWith($this, [arg]);
}
if (trigger || unbind) {
callbacks = $this.data(data_name);
if (trigger) {
callbacks && callbacks.fire();
} else {
if (callback && callbacks) {
callbacks.remove(callback);
if (!callbacks.has()) {
callbacks = null;
}
} else {
callbacks = null;
}
if (!callbacks) {
$this.removeData(data_name);
factory_settings.destroy.call(this, handler, options);
}
}
} else if ($this.data(data_name)) {
$(this).data(data_name).add(callback);
} else {
callbacks = $.Callbacks();
callbacks.add(callback);
$this.data(data_name, callbacks);
factory_settings.init.call(this, handler, options);
}
});
};
}
// -----------------------------------------------------------------------
// :: Cross-browser resize element plugin using sentinel iframe or
// :: resizeObserver
// -----------------------------------------------------------------------
$.fn.resizer = make_callback_plugin({
name: 'resize',
init: function(handler, options) {
var settings = $.extend({
prefix: ''
}, options);
var $this = $(this);
var resizer;
var first = true;
if ($this.is('body')) {
$(window).on('resize.resizer', handler);
} else if (window.ResizeObserver) {
resizer = new ResizeObserver(function() {
if (!first) {
setTimeout(handler, 0);
}
first = false;
});
resizer.observe(this);
$this.data('observer', resizer);
} else {
var iframe = $('<iframe/>').addClass(settings.prefix + 'resizer')
.appendTo(this)[0];
$(iframe.contentWindow).on('resize', handler);
}
},
destroy: function() {
var $this = $(this);
if (window.ResizeObserver) {
var observer = $this.data('observer');
if (observer) {
observer.unobserve(this);
$this.removeData('observer');
}
} else {
var iframe = $this.find('> iframe[class$="resizer"]');
if (iframe.length) {
// just in case of memory leaks in IE
$(iframe[0].contentWindow).off('resize').remove();
iframe.remove();
} else if ($this.is('body')) {
$(window).off('resize.resizer');
}
}
}
});
// -----------------------------------------------------------------------
// :: Mobile friendly scroll that work without scrollbar (for less)
// -----------------------------------------------------------------------
$.fn.touch_scroll = make_callback_plugin({
name: 'touch',
init: function(handler) {
var origin;
var previous;
$(this).on('touchstart.scroll', function(e) {
e = e.originalEvent;
if (e.target.tagName.toLowerCase() !== 'a' && e.touches.length === 1) {
previous = origin = e.touches[0];
}
}).on('touchmove.scroll', function(e) {
e = e.originalEvent;
if (origin && e.touches.length === 1) {
var current = e.touches[0];
var ret = handler({
origin: origin,
previous: previous,
current: current
});
if (ret === false) {
e.preventDefault();
}
previous = current;
}
}).on('touchend.scroll', function() {
if (origin || previous) {
origin = previous = null;
}
});
},
destroy: function() {
$(this).off('touchstart.scroll touchmove.scroll touchend.scroll');
}
});
// -----------------------------------------------------------------------
// :: This plugin is used to pause terminal when images and iframes
// :: are loading
// -----------------------------------------------------------------------
$.fn.on_load = function(options) {
var settings = $.extend({
error: $.noop,
load: $.noop,
done: $.noop
}, options);
var defers = [];
this.find('img,iframe').each(function() {
var self = $(this);
var defer = new $.Deferred();
self.on('load', defer.resolve)
.on('error', function() {
settings.error(self);
defer.reject();
});
defers.push(defer);
});
settings.load(!!defers.length);
if (defers.length) {
$.when.apply($, defers).then(function() {
settings.done(true);
});
} else {
settings.done(false);
}
return this;
};
// -----------------------------------------------------------------------
function jquery_resolve(value) {
var defer = jQuery.Deferred();
defer.resolve(value);
return defer.promise();
}
// -----------------------------------------------------------------------
function always(value, callback) {
if (is_function(value.finally)) {
return value.finally(callback);
}
if (is_function(value.always)) {
return value.always(callback);
}
return value;
}
// -----------------------------------------------------------------------
function unpromise(value, callback, error) {
if (value !== undefined) {
if (is_promise(value)) {
if (is_function(value.done)) {
value = value.done(callback);
} else if (is_function(value.then)) {
value = value.then(callback);
}
if (is_function(value.catch) && is_function(error)) {
value.catch(error);
}
return value;
} else if (value instanceof Array) {
var promises = value.filter(function(value) {
return value && (is_function(value.done) || is_function(value.then));
});
if (promises.length) {
var result = $.when.apply($, value).then(function() {
return callback([].slice.call(arguments));
});
if (is_function(result.catch)) {
result = result.catch(error);
}
return result;
}
}
// TODO: investigate why it break when called
// when value is undefined
// when moving this line outside if
// it breaks all completion unit tests
try {
return callback(value);
} catch (e) {
if (is_function(error)) {
error(e);
} else {
throw e;
}
}
}
}
// -----------------------------------------------------------------------
// :: helper to allow to execute unpromise with undefined
// :: this is workaround on the problem above
// -----------------------------------------------------------------------
function defined(value) {
return value === undefined ? true : value;
}
// -----------------------------------------------------------------------
// :: based on https://github.com/zeusdeux/isInViewport
// :: work only vertically and on dom elements
// -----------------------------------------------------------------------
$.fn.is_fully_in_viewport = (function() {
function is_visible(node, container) {
var box = node.getBoundingClientRect();
var viewport = container[0].getBoundingClientRect();
var top = box.top - viewport.top;
var bottom = box.bottom - viewport.top;
var height = container.height();
return bottom > 0 && top <= height;
}
if (root.IntersectionObserver) {
return function(container) {
var node = this[0];
var defer = jQuery.Deferred();
var item_observer = new root.IntersectionObserver(function(entries) {
defer.resolve(entries[0].isIntersecting && entries[0].ratio === 1);
item_observer.unobserve(node);
}, {
root: container[0]
});
item_observer.observe(node);
return defer.promise();
};
} else {
return function(container) {
return jquery_resolve(is_visible(this[0], container));
};
}
})();
// -------------------------------------------------------------------------
/* eslint-disable */
var entity_re = /(&(?:[a-z\d]+|#\d+|#x[a-f\d]+);)/i;
var space_re = /\s/;
// regex that match single character at begining and folowing combine character
// https://en.wikipedia.org/wiki/Combining_character
var combine_chr_re = /(.(?:[\u0300-\u036F]|[\u1AB0-\u1abE]|[\u1DC0-\u1DF9]|[\u1DFB-\u1DFF]|[\u20D0-\u20F0]|[\uFE20-\uFE2F])+)/;
// source: https://mathiasbynens.be/notes/javascript-unicode
var astral_symbols_re = /([\uD800-\uDBFF][\uDC00-\uDFFF])/;
// source: https://github.com/mathiasbynens/emoji-regex
var emoji_re = /(\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC68(?:\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\u