/**@license * __ _____ ________ __ * / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / / * __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ / * / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__ * \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/ * \/ /____/ version 1.11.0 * * This file is part of jQuery Terminal. http://terminal.jcubic.pl * * Copyright (c) 2010-2017 Jakub Jankiewicz * 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 * * * Cross-Browser Split 1.1.1 * Copyright 2007-2012 Steven Levithan * Available under the MIT License * * jQuery Caret * Copyright (c) 2009, Gideon Sireling * 3 clause BSD License * * sprintf.js * Copyright (c) 2007-2013 Alexandru Marasteanu * licensed under 3 clause BSD license * * Date: Sun, 17 Dec 2017 14:45:52 +0000 */ /* TODO: * * Debug interpreters names in LocalStorage * onPositionChange event add to terminal ??? * different command line history for each login users (add login if present to * localStorage key) * * TEST: login + promises/exec * json-rpc/object + promises * * NOTE: json-rpc don't need promises and delegate resume/pause because only * exec can call it and exec call interpreter that work with resume/pause */ /* global location, jQuery, setTimeout, window, global, localStorage, sprintf, setImmediate, IntersectionObserver, MutationObserver, ResizeObserver, wcwidth, module, require, define */ /* eslint-disable */ (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.substring(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.substring(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.substring(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 : window); /* eslint-enable */ // UMD taken from https://github.com/umdjs/umd (function(factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); } else if (typeof module === 'object' && module.exports) { // Node/CommonJS module.exports = function(root, jQuery) { 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); } } factory(jQuery); return jQuery; }; } else { // Browser globals factory(jQuery); } })(function($, undefined) { 'use strict'; // ----------------------------------------------------------------------- // :: Replacemenet for jQuery 2 deferred objects // ----------------------------------------------------------------------- function DelayQueue() { var callbacks = $.Callbacks(); var resolved = false; this.resolve = function() { callbacks.fire(); resolved = true; }; this.add = function(fn) { if (resolved) { fn(); } else { callbacks.add(fn); } }; } // ----------------------------------------------------------------------- // :: 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 ($.isArray(object)) { return this.clone_array(object); } else if (object === null) { return object; } else { for (var key in object) { if ($.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 (!$.isFunction(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); }; /* eslint-disable */ // ----------------------------------------------------------------------- // :: Storage plugin // ----------------------------------------------------------------------- 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.substring(1, c.length); } if (c.indexOf(nn) === 0) { return c.substring(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") */ var localStorage; 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 } }); } // ----------------------------------------------------------------------- // :: jQuery Timers // ----------------------------------------------------------------------- var jQuery = $; 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(jQuery.trim(value.toString())); 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 (jQuery.isFunction(label)) { 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] = window.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) { window.clearInterval(timers[label][fn.$timerID]); delete timers[label][fn.$timerID]; } } else { for (var _fn in timers[label]) { if (timers[label].hasOwnProperty(_fn)) { window.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); } } } }); } // ----------------------------------------------------------------------- // :: CROSS BROWSER SPLIT // ----------------------------------------------------------------------- (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 // ----------------------------------------------------------------------- $.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) { 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) { target.focus(); window.getSelection().collapse(target.firstChild, 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 */ var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || function(fn) { return setTimeout(fn, 20); }; // ----------------------------------------------------------------------- // :: Cross-browser resize element plugin // :: Taken from ResizeSensor.js file from marcj/css-element-queries (MIT license) // :: not all jQuerifided // ----------------------------------------------------------------------- $.fn.resizer = function(callback) { var unbind = arguments[0] === "unbind"; if (!unbind && !$.isFunction(callback)) { throw new Error( 'Invalid argument, it need to a function or string "unbind".' ); } if (unbind) { callback = $.isFunction(arguments[1]) ? arguments[1] : null; } return this.each(function() { var $this = $(this); var callbacks; if (unbind) { callbacks = $this.data('callbacks'); if (callback && callbacks) { callbacks.remove(callback); if (!callbacks.has()) { callbacks = null; } } else { callbacks = null; } if (!callbacks) { $this.removeData('callbacks'); if (window.ResizeObserver) { var observer = $this.data('observer'); if (observer) { observer.unobserve(this); $this.removeData('observer'); } } else { $this.find('.resizer').remove(); } } } else if ($this.data('callbacks')) { $(this).data('callbacks').add(callback); } else { callbacks = $.Callbacks(); callbacks.add(callback); $this.data('callbacks', callbacks); var resizer; if (window.ResizeObserver) { resizer = new ResizeObserver(function() { var callbacks = $this.data('callbacks'); callbacks.fire(); }); resizer.observe(this); $this.data('observer', resizer); return; } var self = this; resizer = $('
').addClass('resizer').appendTo(this)[0]; var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; ' + 'overflow: hidden; z-index: -1; visibility: hidden;'; var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;'; resizer.style.cssText = style; resizer.innerHTML = '
' + '
' + "
" + '
' + '
' + "
"; var expand = resizer.childNodes[0]; var expandChild = expand.childNodes[0]; var shrink = resizer.childNodes[1]; var dirty, rafId, newWidth, newHeight; var lastWidth = self.offsetWidth; var lastHeight = self.offsetHeight; var reset = function() { expandChild.style.width = '100000px'; expandChild.style.height = '100000px'; expand.scrollLeft = 100000; expand.scrollTop = 100000; shrink.scrollLeft = 100000; shrink.scrollTop = 100000; }; reset(); var onResized = function() { rafId = 0; if (!dirty) { return; } lastWidth = newWidth; lastHeight = newHeight; callbacks.fire(); }; var onScroll = function() { newWidth = self.offsetWidth; newHeight = self.offsetHeight; dirty = newWidth !== lastWidth || newHeight !== lastHeight; if (dirty && !rafId) { rafId = requestAnimationFrame(onResized); } reset(); }; $(expand).on("scroll", onScroll); $(shrink).on("scroll", onScroll); } }); }; // ----------------------------------------------------------------------- // :: hide elements from screen readers // ----------------------------------------------------------------------- function a11y_hide(element) { element.attr({ role: 'presentation', 'aria-hidden': 'true' }); } // --------------------------------------------------------------------- // :: alert only first exception of type // --------------------------------------------------------------------- var excepctions = []; function alert_exception(label, e) { var message = (label ? label + ': ' : '') + exception_message(e); if (excepctions.indexOf(message) === -1) { excepctions.push(message); alert(message + (e.stack ? '\n' + e.stack : '')); } } // --------------------------------------------------------------------- // :; detect if mouse event happen on scrollbar // --------------------------------------------------------------------- function scrollbar_event(e, node) { var left = node.offset().left; return node.outerWidth() <= e.clientX - left; } // --------------------------------------------------------------------- // :: Return exception message as string // --------------------------------------------------------------------- function exception_message(e) { if (typeof e === 'string') { return e; } else if (typeof e.fileName === 'string') { return e.fileName + ': ' + e.message; } else { return e.message; } } // ----------------------------------------------------------------------- // :: CYCLE DATA STRUCTURE // ----------------------------------------------------------------------- function Cycle() { var data = [].slice.call(arguments); var pos = 0; $.extend(this, { get: function() { return data; }, index: function() { return pos; }, rotate: function(skip) { if (!skip) { var defined = data.filter(function(item) { return typeof item !== 'undefined'; }); if (!defined.length) { return; } } if (data.length === 1) { return data[0]; } else { if (pos === data.length - 1) { pos = 0; } else { ++pos; } if (typeof data[pos] !== 'undefined') { return data[pos]; } else { return this.rotate(true); } } }, length: function() { return data.length; }, remove: function(index) { delete data[index]; }, set: function(item) { for (var i = data.length; i--;) { if (data[i] === item) { pos = i; return; } } this.append(item); pos = data.length - 1; }, front: function() { if (data.length) { var index = pos; var restart = false; while (!data[index]) { index++; if (index > data.length) { if (restart) { break; } index = 0; restart = true; } } return data[index]; } }, map: function(fn) { return data.filter(Boolean).map(fn); }, forEach: function(fn) { data.filter(Boolean).forEach(fn); }, append: function(item) { data.push(item); } }); } // ----------------------------------------------------------------------- // :: STACK DATA STRUCTURE // ----------------------------------------------------------------------- function Stack(init) { var data = init instanceof Array ? init : init ? [init] : []; $.extend(this, { data: function() { return data; }, map: function(fn) { return $.map(data, fn); }, size: function() { return data.length; }, pop: function() { if (data.length === 0) { return null; } else { var value = data[data.length - 1]; data = data.slice(0, data.length - 1); return value; } }, push: function(value) { data = data.concat([value]); return value; }, top: function() { return data.length > 0 ? data[data.length - 1] : null; }, clone: function() { return new Stack(data.slice(0)); } }); } // ------------------------------------------------------------------------- // :: HISTORY CLASS // ------------------------------------------------------------------------- function History(name, size, memory) { var enabled = true; var storage_key = ''; if (typeof name === 'string' && name !== '') { storage_key = name + '_'; } storage_key += 'commands'; var data; if (memory) { data = []; } else { data = $.Storage.get(storage_key); data = data ? JSON.parse(data) : []; } var pos = data.length - 1; $.extend(this, { append: function(item) { if (enabled) { if (data[data.length - 1] !== item) { data.push(item); if (size && data.length > size) { data = data.slice(-size); } pos = data.length - 1; if (!memory) { $.Storage.set(storage_key, JSON.stringify(data)); } } } }, set: function(new_data) { if (new_data instanceof Array) { data = new_data; if (!memory) { $.Storage.set(storage_key, JSON.stringify(data)); } } }, data: function() { return data; }, reset: function() { pos = data.length - 1; }, last: function() { return data[data.length - 1]; }, end: function() { return pos === data.length - 1; }, position: function() { return pos; }, current: function() { return data[pos]; }, next: function() { var old = pos; if (pos < data.length - 1) { ++pos; } if (old !== pos) { return data[pos]; } }, previous: function() { var old = pos; if (pos > 0) { --pos; } if (old !== pos) { return data[pos]; } }, clear: function() { data = []; this.purge(); }, enabled: function() { return enabled; }, enable: function() { enabled = true; }, purge: function() { if (!memory) { $.Storage.remove(storage_key); } }, disable: function() { enabled = false; } }); } // ------------------------------------------------------------------------- // :: COMMAND LINE PLUGIN // ------------------------------------------------------------------------- var cmd_index = 0; $.fn.cmd = function(options) { var self = this; var maybe_data = self.data('cmd'); if (maybe_data) { return maybe_data; } var id = cmd_index++; self.addClass('cmd'); self.append(''); self.append('
' + '' + ' ' + '' + '
'); // a11y: don't read command it's in textarea that's in focus a11y_hide(self.find('.cursor-line')); // on mobile the only way to hide textarea on desktop it's needed because // textarea show up after focus //self.append(''); var clip = $('