<commit_before>import {merge} from 'app/utils'
import rpc from 'app/rpc'

var screens = {}
var widgets = {}
var already_loaded = {}

export function join_path(url){
  let full_url=`/static/${url}`
  if (localStorage.servername)
    full_url=`${localStorage.servername}${full_url}`
  return full_url
}

export function load_js(url, options){
  if (already_loaded[url]){
    return Promise.resolve()
  }
  let promise = new Promise(function(accept, reject){
    let full_url=join_path(url)

    options = $.extend( options || {}, {
      dataType: "script",
      cache: true,
      url: full_url
    });

    return jQuery.ajax( options ).done( accept ).fail( reject )
  })
  promise = promise.then( () => {
    already_loaded[url]=true
    console.log("Loaded JS %o", url)
  })
  return promise
}

export function load_css(url, options={}){
  if (already_loaded[url]){
    return Promise.resolve()
  }
  let full_url=join_path(url)

  options = merge(options, { type: "text/css", rel: "stylesheet", href: full_url })
  $('<link>')
    .appendTo("head")
    .attr(options)
  console.log("Loaded CSS %o", url)
  already_loaded[url]=true
}

export function load_html(url, options={}){
  let p = new Promise((accept, reject) => {
    url = join_path(url)
    $.get(url, function(html){
      if (options.base_url)
        html = html.replace(/(src=")([^/])/g, `$1${join_path(options.base_url)}/$2`)
      accept(html)
    }).error( (e) => {
      reject(e)
    })
  })
  return p
}

export function load(url, options={}){
  if (url.endsWith(".css"))
    return load_css(url, options)
  if (url.endsWith(".js"))
    return load_js(url, options)
  if (url.endsWith(".html"))
    return load_html(url, options)
  throw ("Dont know how to load based on URL extension: "+url)
}

let waiting_for_screen={}

function when_screen_added(id){
  return new Promise(function(accept, reject){
    waiting_for_screen[id] = (waiting_for_screen[id] || []).concat({accept, reject})
    setTimeout(30000, () => { // Will always fire, but maybe empty
      for (let {reject} of (waiting_for_screen[id] || []))
        reject(id)
    })
  })
}

export function add_screen(id, fn){
  screens[id]=fn
  for (let {accept} of (waiting_for_screen[id] || [])){
    accept(fn)
  }
  delete waiting_for_screen[id]
}

export function do_screen(id, el, data, context){
  if (id in screens){
    let cleanf=screens[id](el, data, context)
    return Promise.resolve(cleanf)
  }
  return when_screen_added(id).then( (screen) => {
    return screen(el,data)
  })
}

// Allow to be added asynchronously
let waiting_for_widgets={}

export function add_widget(id, fn){
  widgets[id]=fn
  if (id in waiting_for_widgets)
    for (let {accept, el, data, context} of waiting_for_widgets[id])
      accept(fn(el, data, context))
  delete waiting_for_widgets[id]
}

export function do_widget(id, el, data, context){
  if (!(id in widgets)){
    let p = new Promise(function(accept, reject){
      waiting_for_widgets[id]=(waiting_for_widgets[id] || []).concat({accept, reject, el, data, context})
      setTimeout(function(){
        console.error("timeout waiting for widget %o", id)
        if (id in waiting_for_widgets)
          reject("Timeout")
      }, 1000)
    })
    return p
  }
  return Promise.resolve(widgets[id](el, data, context))
}

class PluginCaller{
  constructor(pluginid, options = {}){
    this.pluginid = pluginid
    this.uuid = undefined
    this.options = { restart: true }
  }
  start(method, params){
    return rpc.call("plugin.start", [this.pluginid]).then( (uuid_) => {
      this.uuid = uuid_
      return this
    })
  }
  call(method, params){
    return rpc.call(`${this.uuid}.${method}`, params).catch( (e) => {
      if (e=='unknown_method')
        return this.maybe_reconnect(e).then( () => this.call(method, params) )
      throw(e)
    })
  }
  maybe_reconnect(e){
    if (!this.options.restart)
      throw(e)
    return rpc.call("plugin.is_running", [this.uuid]).then( (is_running) => {
      if (is_running)
        throw(e)
      else{
        return this.start()
      }
    })
  }
  stop(){
    return rpc.call("plugin.stop", [this.uuid])
  }
}

export function start(pluginid, options={}){
  const pc = new PluginCaller(pluginid, options)
  return pc.start()
}

export function start_call_stop(pluginid, method, args){
  return start(pluginid).then( (pl) => (
    pl
      .call(method, args)
      .then( (res) => {pl.stop(); return res; })
      .catch( (e) => {pl.stop(); throw e; })
      .catch( (e) => { if (e!="cant_stop") throw e; })
  ))
}

export default {load, add_screen, do_screen, add_widget, do_widget, join_path, start, start_call_stop}
<commit_msg>Improved parsing of HTML for relative links<commit_after>import {merge} from 'app/utils'
import rpc from 'app/rpc'

var screens = {}
var widgets = {}
var already_loaded = {}

export function join_path(url){
  let full_url=`/static/${url}`
  if (localStorage.servername)
    full_url=`${localStorage.servername}${full_url}`
  return full_url
}

export function load_js(url, options){
  if (already_loaded[url]){
    return Promise.resolve()
  }
  let promise = new Promise(function(accept, reject){
    let full_url=join_path(url)

    options = $.extend( options || {}, {
      dataType: "script",
      cache: true,
      url: full_url
    });

    return jQuery.ajax( options ).done( accept ).fail( reject )
  })
  promise = promise.then( () => {
    already_loaded[url]=true
    console.log("Loaded JS %o", url)
  })
  return promise
}

export function load_css(url, options={}){
  if (already_loaded[url]){
    return Promise.resolve()
  }
  let full_url=join_path(url)

  options = merge(options, { type: "text/css", rel: "stylesheet", href: full_url })
  $('<link>')
    .appendTo("head")
    .attr(options)
  console.log("Loaded CSS %o", url)
  already_loaded[url]=true
}

export function load_html(url, options={}){
  let p = new Promise((accept, reject) => {
    url = join_path(url)
    $.get(url, function(html){
      if (options.base_url){
        const base_static = join_path(options.base_url)
        html = html.replace(/(src="|href=")([^/])/g, `$1/$2`) // maybe relative, convert to absolute
        html = html.replace(/(src="|href=")/g, `$1${base_static}/`) // Add the prefixes
        // and undo duplicate paths, because user already had them
        html = html.replace(RegExp(`/static/${options.base_url}//static/${options.base_url}/`,'g'), `/static/${options.base_url}/`)
      }
      accept(html)
    }).error( (e) => {
      reject(e)
    })
  })
  return p
}

export function load(url, options={}){
  if (url.endsWith(".css"))
    return load_css(url, options)
  if (url.endsWith(".js"))
    return load_js(url, options)
  if (url.endsWith(".html"))
    return load_html(url, options)
  throw ("Dont know how to load based on URL extension: "+url)
}

let waiting_for_screen={}

function when_screen_added(id){
  return new Promise(function(accept, reject){
    waiting_for_screen[id] = (waiting_for_screen[id] || []).concat({accept, reject})
    setTimeout(30000, () => { // Will always fire, but maybe empty
      for (let {reject} of (waiting_for_screen[id] || []))
        reject(id)
    })
  })
}

export function add_screen(id, fn){
  screens[id]=fn
  for (let {accept} of (waiting_for_screen[id] || [])){
    accept(fn)
  }
  delete waiting_for_screen[id]
}

export function do_screen(id, el, data, context){
  if (id in screens){
    let cleanf=screens[id](el, data, context)
    return Promise.resolve(cleanf)
  }
  return when_screen_added(id).then( (screen) => {
    return screen(el,data)
  })
}

// Allow to be added asynchronously
let waiting_for_widgets={}

export function add_widget(id, fn){
  widgets[id]=fn
  if (id in waiting_for_widgets)
    for (let {accept, el, data, context} of waiting_for_widgets[id])
      accept(fn(el, data, context))
  delete waiting_for_widgets[id]
}

export function do_widget(id, el, data, context){
  if (!(id in widgets)){
    let p = new Promise(function(accept, reject){
      waiting_for_widgets[id]=(waiting_for_widgets[id] || []).concat({accept, reject, el, data, context})
      setTimeout(function(){
        console.error("timeout waiting for widget %o", id)
        if (id in waiting_for_widgets)
          reject("Timeout")
      }, 1000)
    })
    return p
  }
  return Promise.resolve(widgets[id](el, data, context))
}

class PluginCaller{
  constructor(pluginid, options = {}){
    this.pluginid = pluginid
    this.uuid = undefined
    this.options = { restart: true }
  }
  start(method, params){
    return rpc.call("plugin.start", [this.pluginid]).then( (uuid_) => {
      this.uuid = uuid_
      return this
    })
  }
  call(method, params){
    return rpc.call(`${this.uuid}.${method}`, params).catch( (e) => {
      if (e=='unknown_method')
        return this.maybe_reconnect(e).then( () => this.call(method, params) )
      throw(e)
    })
  }
  maybe_reconnect(e){
    if (!this.options.restart)
      throw(e)
    return rpc.call("plugin.is_running", [this.uuid]).then( (is_running) => {
      if (is_running)
        throw(e)
      else{
        return this.start()
      }
    })
  }
  stop(){
    return rpc.call("plugin.stop", [this.uuid])
  }
}

export function start(pluginid, options={}){
  const pc = new PluginCaller(pluginid, options)
  return pc.start()
}

export function start_call_stop(pluginid, method, args){
  return start(pluginid).then( (pl) => (
    pl
      .call(method, args)
      .then( (res) => {pl.stop(); return res; })
      .catch( (e) => {pl.stop(); throw e; })
      .catch( (e) => { if (e!="cant_stop") throw e; })
  ))
}

export default {load, add_screen, do_screen, add_widget, do_widget, join_path, start, start_call_stop}
<|endoftext|>


<commit_before>var clipboard = {};

clipboard.copy = (function() {
  var _intercept = false;
  var _data; // Map from data type (e.g. "text/html") to value.

  document.addEventListener("copy", function(e){
    if (_intercept) {
      _intercept = false;
      for (var key in _data) {
        e.clipboardData.setData(key, _data[key]);
      }
      e.preventDefault();
    }
  });

  return function(data) {
    return new Promise(function(resolve, reject) {
      _intercept = true; // Race condition?
      _data = (typeof data === "string" ? {"text/plain": data} : data);
      try {
        if (document.execCommand("copy")) {
          // document.execCommand is synchronous: http://www.w3.org/TR/2015/WD-clipboard-apis-20150421/#integration-with-rich-text-editing-apis
          // So we can call resolve() back here.
          resolve();
        }
        else {
          _intercept = false;
          reject(new Error("Unable to copy. Perhaps it's not available in your browser?"));
        }
      }
      catch (e) {
        _intercept = false;
        reject(e);
      }
    });
  };
}());

clipboard.paste = (function() {
  var _intercept = false;
  var _resolve;
  var _dataType;

  document.addEventListener("paste", function(e) {
    if (_intercept) {
      _intercept = false;
      e.preventDefault();
      _resolve(e.clipboardData.getData(_dataType));
    }
  });

  return function(dataType) {
    return new Promise(function(resolve, reject) {
      _intercept = true; // Race condition?
      _resolve = resolve;
      _dataType = dataType || "text/plain";
      try {
        if (!document.execCommand("paste")) {
          _intercept = false;
          reject(new Error("Unable to paste. Perhaps it's not available in your browser?"));
        }
      } catch (e) {
        _intercept = false;
        reject(new Error(e));
      }
    });
  };
}());

// Handle IE behaviour.
if (typeof ClipboardEvent === "undefined" &&
    typeof window.clipboardData !== "undefined" &&
    typeof window.clipboardData.setData !== "undefined") {

  /*! promise-polyfill 2.0.1 */
  !function(a){function b(a,b){return function(){a.apply(b,arguments)}}function c(a){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof a)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],i(a,b(e,this),b(f,this))}function d(a){var b=this;return null===this._state?void this._deferreds.push(a):void j(function(){var c=b._state?a.onFulfilled:a.onRejected;if(null===c)return void(b._state?a.resolve:a.reject)(b._value);var d;try{d=c(b._value)}catch(e){return void a.reject(e)}a.resolve(d)})}function e(a){try{if(a===this)throw new TypeError("A promise cannot be resolved with itself.");if(a&&("object"==typeof a||"function"==typeof a)){var c=a.then;if("function"==typeof c)return void i(b(c,a),b(e,this),b(f,this))}this._state=!0,this._value=a,g.call(this)}catch(d){f.call(this,d)}}function f(a){this._state=!1,this._value=a,g.call(this)}function g(){for(var a=0,b=this._deferreds.length;b>a;a++)d.call(this,this._deferreds[a]);this._deferreds=null}function h(a,b,c,d){this.onFulfilled="function"==typeof a?a:null,this.onRejected="function"==typeof b?b:null,this.resolve=c,this.reject=d}function i(a,b,c){var d=!1;try{a(function(a){d||(d=!0,b(a))},function(a){d||(d=!0,c(a))})}catch(e){if(d)return;d=!0,c(e)}}var j=c.immediateFn||"function"==typeof setImmediate&&setImmediate||function(a){setTimeout(a,1)},k=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)};c.prototype["catch"]=function(a){return this.then(null,a)},c.prototype.then=function(a,b){var e=this;return new c(function(c,f){d.call(e,new h(a,b,c,f))})},c.all=function(){var a=Array.prototype.slice.call(1===arguments.length&&k(arguments[0])?arguments[0]:arguments);return new c(function(b,c){function d(f,g){try{if(g&&("object"==typeof g||"function"==typeof g)){var h=g.then;if("function"==typeof h)return void h.call(g,function(a){d(f,a)},c)}a[f]=g,0===--e&&b(a)}catch(i){c(i)}}if(0===a.length)return b([]);for(var e=a.length,f=0;f<a.length;f++)d(f,a[f])})},c.resolve=function(a){return a&&"object"==typeof a&&a.constructor===c?a:new c(function(b){b(a)})},c.reject=function(a){return new c(function(b,c){c(a)})},c.race=function(a){return new c(function(b,c){for(var d=0,e=a.length;e>d;d++)a[d].then(b,c)})},"undefined"!=typeof module&&module.exports?module.exports=c:a.Promise||(a.Promise=c)}(this);

  clipboard.copy = function(data) {
    return new Promise(function(resolve, reject) {
      // IE supports string and URL types: https://msdn.microsoft.com/en-us/library/ms536744(v=vs.85).aspx
      // We only support the string type for now.
      if (typeof data !== "string" && !("text/plain" in data)) {
        throw new Error("You must provide a text/plain type.")
      }

      var strData = (typeof data === "string" ? data : data["text/plain"]);
      var copySucceeded = window.clipboardData.setData("Text", strData);
      copySucceeded ? resolve() : reject(new Error("Copying was rejected."));
    });
  }

  clipboard.paste = function(data) {
    return new Promise(function(resolve, reject) {
      var strData = window.clipboardData.getData("Text");
      if (strData) {
        resolve(strData);
      } else {
        // The user rejected the paste request.
        reject(new Error("Pasting was rejected."));
      }
    });
  }
}
<commit_msg>Added module support<commit_after>//  Import support http://stackoverflow.com/questions/13673346/supporting-both-commonjs-and-amd
(function(name, definition) {
    if (typeof module != 'undefined') module.exports = definition();
    else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
    else this[name] = definition();
}('mod', function() {

  var clipboard = {};

  clipboard.copy = (function() {
    var _intercept = false;
    var _data; // Map from data type (e.g. "text/html") to value.

    document.addEventListener("copy", function(e){
      if (_intercept) {
        _intercept = false;
        for (var key in _data) {
          e.clipboardData.setData(key, _data[key]);
        }
        e.preventDefault();
      }
    });

    return function(data) {
      return new Promise(function(resolve, reject) {
        _intercept = true; // Race condition?
        _data = (typeof data === "string" ? {"text/plain": data} : data);
        try {
          if (document.execCommand("copy")) {
            // document.execCommand is synchronous: http://www.w3.org/TR/2015/WD-clipboard-apis-20150421/#integration-with-rich-text-editing-apis
            // So we can call resolve() back here.
            resolve();
          }
          else {
            _intercept = false;
            reject(new Error("Unable to copy. Perhaps it's not available in your browser?"));
          }
        }
        catch (e) {
          _intercept = false;
          reject(e);
        }
      });
    };
  }());

  clipboard.paste = (function() {
    var _intercept = false;
    var _resolve;
    var _dataType;

    document.addEventListener("paste", function(e) {
      if (_intercept) {
        _intercept = false;
        e.preventDefault();
        _resolve(e.clipboardData.getData(_dataType));
      }
    });

    return function(dataType) {
      return new Promise(function(resolve, reject) {
        _intercept = true; // Race condition?
        _resolve = resolve;
        _dataType = dataType || "text/plain";
        try {
          if (!document.execCommand("paste")) {
            _intercept = false;
            reject(new Error("Unable to paste. Perhaps it's not available in your browser?"));
          }
        } catch (e) {
          _intercept = false;
          reject(new Error(e));
        }
      });
    };
  }());

  // Handle IE behaviour.
  if (typeof ClipboardEvent === "undefined" &&
      typeof window.clipboardData !== "undefined" &&
      typeof window.clipboardData.setData !== "undefined") {

    /*! promise-polyfill 2.0.1 */
    !function(a){function b(a,b){return function(){a.apply(b,arguments)}}function c(a){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof a)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],i(a,b(e,this),b(f,this))}function d(a){var b=this;return null===this._state?void this._deferreds.push(a):void j(function(){var c=b._state?a.onFulfilled:a.onRejected;if(null===c)return void(b._state?a.resolve:a.reject)(b._value);var d;try{d=c(b._value)}catch(e){return void a.reject(e)}a.resolve(d)})}function e(a){try{if(a===this)throw new TypeError("A promise cannot be resolved with itself.");if(a&&("object"==typeof a||"function"==typeof a)){var c=a.then;if("function"==typeof c)return void i(b(c,a),b(e,this),b(f,this))}this._state=!0,this._value=a,g.call(this)}catch(d){f.call(this,d)}}function f(a){this._state=!1,this._value=a,g.call(this)}function g(){for(var a=0,b=this._deferreds.length;b>a;a++)d.call(this,this._deferreds[a]);this._deferreds=null}function h(a,b,c,d){this.onFulfilled="function"==typeof a?a:null,this.onRejected="function"==typeof b?b:null,this.resolve=c,this.reject=d}function i(a,b,c){var d=!1;try{a(function(a){d||(d=!0,b(a))},function(a){d||(d=!0,c(a))})}catch(e){if(d)return;d=!0,c(e)}}var j=c.immediateFn||"function"==typeof setImmediate&&setImmediate||function(a){setTimeout(a,1)},k=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)};c.prototype["catch"]=function(a){return this.then(null,a)},c.prototype.then=function(a,b){var e=this;return new c(function(c,f){d.call(e,new h(a,b,c,f))})},c.all=function(){var a=Array.prototype.slice.call(1===arguments.length&&k(arguments[0])?arguments[0]:arguments);return new c(function(b,c){function d(f,g){try{if(g&&("object"==typeof g||"function"==typeof g)){var h=g.then;if("function"==typeof h)return void h.call(g,function(a){d(f,a)},c)}a[f]=g,0===--e&&b(a)}catch(i){c(i)}}if(0===a.length)return b([]);for(var e=a.length,f=0;f<a.length;f++)d(f,a[f])})},c.resolve=function(a){return a&&"object"==typeof a&&a.constructor===c?a:new c(function(b){b(a)})},c.reject=function(a){return new c(function(b,c){c(a)})},c.race=function(a){return new c(function(b,c){for(var d=0,e=a.length;e>d;d++)a[d].then(b,c)})},"undefined"!=typeof module&&module.exports?module.exports=c:a.Promise||(a.Promise=c)}(this);

    clipboard.copy = function(data) {
      return new Promise(function(resolve, reject) {
        // IE supports string and URL types: https://msdn.microsoft.com/en-us/library/ms536744(v=vs.85).aspx
        // We only support the string type for now.
        if (typeof data !== "string" && !("text/plain" in data)) {
          throw new Error("You must provide a text/plain type.")
        }

        var strData = (typeof data === "string" ? data : data["text/plain"]);
        var copySucceeded = window.clipboardData.setData("Text", strData);
        copySucceeded ? resolve() : reject(new Error("Copying was rejected."));
      });
    };

    clipboard.paste = function(data) {
      return new Promise(function(resolve, reject) {
        var strData = window.clipboardData.getData("Text");
        if (strData) {
          resolve(strData);
        } else {
          // The user rejected the paste request.
          reject(new Error("Pasting was rejected."));
        }
      });
    };
  }

  return clipboard;
}));<|endoftext|>


<commit_before>'use strict';

import eventsMixin from '../events'
import mixin       from '../mixin'
import Model       from '../models/base'
import utils       from '../utils'

class DragonBaseCollection {

  constructor(entries = [], options = {}) {
    this.uid = utils.uniqueId(this)
    this.mixin(eventsMixin)

    this.disposed = false
    this.Model    = Model
    this.models   = []
    this.url      = options.url || this.url || ''

    /*
    TODO: this check is flawed
    */
    if(entries.length > 0 && !(entries[Symbol.iterator])) {
      throw new Error('Collection entries must be an iterable')
    }

    if(!this.Model || !(this.Model instanceof Function)) {
      throw new Error('Collection requires a valid Model Class')
    }

    if(typeof this.url != 'string') {
      throw new Error('Collection requires a valid url')
    }

    this.ensureEntries(entries)
  }

  add(entries) {
    this.ensureEntries(entries)
  }

  clear() {
    this.models = []
  }

  /*
  TODO: Clean this up, make checks stronger
  Currently, a mixed array of objects and models could be passed; it's an odd scenario that would cause that to hapen thoug.
  Tecnnically a model instance of a different model could be passed; need to check and prevent that.
  Should also consider concatting arrays as pushing arrays of 1000 or more can be very time consuming/lots of looping.
  Overall this function sucks but helps move the project forward atm.
  */

  ensureEntries(entries) {

    /*
    TODO: figure out how to clean this up
    */
    if(!entries || !entries.length || entries.length == 0) return

    // we will suppport all kind of iterable  here !!
    // It is simpler to manage things by making a single item an array
    if(!(entries[Symbol.iterator])) {
      entries = [entries]
    }

    for(let entry of entries) {
      if(entry instanceof this.Model) {
        this.models.push(entry)
      }

      else {
        this.models.push(new this.Model(entry))
      }

    }

    this.emit('change')

  }

  /*
  TODO: not really sure what to call this function
  */
  getData() {
    var data = []

    this.models.forEach( (model) => {
      data.push(model.attr)
    })

    return data
  }

  toJSON() {
    return this.models.map( (model) => model.toJSON())
  }

  [Symbol.iterator](){
    var collectionsModels = this.models,
        index = 0

    return {
      next: function next () {
        if (index + 1 > collectionsModels.length) {
          return { done: true };
        }
        return { value: collectionsModels[index++], done: false };
      }
    }
  }

  dispose() {

    if(!this.disposed) {

      utils.dispose(this)

    }

  }

}

Object.assign(DragonBaseCollection.prototype, {mixin})

module.exports = DragonBaseCollection
<commit_msg>Don't fire stores on collections<commit_after>'use strict';

import eventsMixin from '../events'
import mixin       from '../mixin'
import Model       from '../models/base'
import utils       from '../utils'

class DragonBaseCollection {

  constructor(entries = [], options = {}) {
    this.uid = utils.uniqueId(this)
    this.mixin(eventsMixin)

    this.disposed = false
    this.Model    = Model
    this.models   = []
    this.url      = options.url || this.url || ''

    /*
    TODO: this check is flawed
    */
    if(entries.length > 0 && !(entries[Symbol.iterator])) {
      throw new Error('Collection entries must be an iterable')
    }

    if(!this.Model || !(this.Model instanceof Function)) {
      throw new Error('Collection requires a valid Model Class')
    }

    if(typeof this.url != 'string') {
      throw new Error('Collection requires a valid url')
    }

    this.ensureEntries(entries)
  }

  add(entries) {
    this.ensureEntries(entries)
  }

  clear() {
    this.models = []
  }

  /*
  TODO: Clean this up, make checks stronger
  Currently, a mixed array of objects and models could be passed; it's an odd scenario that would cause that to hapen thoug.
  Tecnnically a model instance of a different model could be passed; need to check and prevent that.
  Should also consider concatting arrays as pushing arrays of 1000 or more can be very time consuming/lots of looping.
  Overall this function sucks but helps move the project forward atm.
  */

  ensureEntries(entries) {

    /*
    TODO: figure out how to clean this up
    */
    if(!entries || !entries.length || entries.length == 0) return

    // we will suppport all kind of iterable  here !!
    // It is simpler to manage things by making a single item an array
    if(!(entries[Symbol.iterator])) {
      entries = [entries]
    }

    for(let entry of entries) {
      if(entry instanceof this.Model) {
        this.models.push(entry)
      }

      else {
        this.models.push(new this.Model(entry, {store: null}))
      }

    }

    this.emit('change')

  }

  /*
  TODO: not really sure what to call this function
  */
  getData() {
    var data = []

    this.models.forEach( (model) => {
      data.push(model.attr)
    })

    return data
  }

  toJSON() {
    return this.models.map( (model) => model.toJSON())
  }

  [Symbol.iterator](){
    var collectionsModels = this.models,
        index = 0

    return {
      next: function next () {
        if (index + 1 > collectionsModels.length) {
          return { done: true };
        }
        return { value: collectionsModels[index++], done: false };
      }
    }
  }

  dispose() {

    if(!this.disposed) {

      utils.dispose(this)

    }

  }

}

Object.assign(DragonBaseCollection.prototype, {mixin})

module.exports = DragonBaseCollection
<|endoftext|>


<commit_before>'use strict';

module.exports = function (grunt) {
  grunt.initConfig({
    // Metadata
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
    '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
    '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
    '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
    ' Licensed <%= props.license %> */\n',
    // Task configuration
    gruntfile: {
      src: 'gruntfile.js'
    },
    // shared config between update tasks
    update: {
      destination: 'update/',
      version: ''
    },
    curl: {
      // Bootstrap files
      'update/bootstrap/js/bootstrap.min.js': 'http://netdna.bootstrapcdn.com/bootstrap/latest/js/bootstrap.min.js',
      'update/bootstrap/fonts/glyphicons-halflings-regular.eot': 'http://netdna.bootstrapcdn.com/bootstrap/latest/fonts/glyphicons-halflings-regular.eot',
      'update/bootstrap/fonts/glyphicons-halflings-regular.woff': 'http://netdna.bootstrapcdn.com/bootstrap/latest/fonts/glyphicons-halflings-regular.woff',
      'update/bootstrap/fonts/glyphicons-halflings-regular.ttf': 'http://netdna.bootstrapcdn.com/bootstrap/latest/fonts/glyphicons-halflings-regular.ttf',
      'update/bootstrap/fonts/glyphicons-halflings-regular.svg': 'http://netdna.bootstrapcdn.com/bootstrap/latest/fonts/glyphicons-halflings-regular.svg'
    },
    http: {
      bootswatch: {
        options: {
          url: 'http://api.bootswatch.com/3/',
          callback: function(error, response, data) {
            data = JSON.parse(data);
            if(error)
              grunt.log.error('Something went wrong. Can\'t reach Bootswatch API');
            else {
              var latestVersion = data.version;
              grunt.config.set('update.version', latestVersion);
              grunt.config.set('update.themes', data.themes);
              grunt.log.writeln('Current version: ',grunt.config.get('pkg.bootswatch.version'));
              if(grunt.config.get('pkg.bootswatch.version') < latestVersion) {
                grunt.log.writeln('New version is available. Updating to: ' + latestVersion);
                // fetch themes
                data.themes.forEach(function(theme) {
                  // create dynamic http tasks and run them
                  grunt.config.set('http.fetch' + theme.name + '.options.url', theme.cssMin);
                  grunt.config.set('http.fetch' + theme.name + '.dest', 'update/'+ theme.name.toLowerCase() + '/bootstrap.min.css');
                  grunt.task.run('http:fetch' + theme.name);
                });
                grunt.task.run('fetchBootstrapFiles');
                grunt.task.run('release');
                grunt.task.run('updatePackageJson');
              }
              else {
                grunt.task.run('checkThemes');
                grunt.task.run('releaseNewThemes');
                grunt.task.run('updatePackageJson');
              }
            }
          }
        }
      }
    },
    clean: {
      dist: {
        src: ['dist']
      }
    },
    shell: {
      setUser: {
        options: {
        },
        command: 'git config --global user.name "Travis Build" && git config --global user.email "$GH_TOKEN"'
      },
      cloneProject: {
        options: {
        },
        command: 'git clone https://$GH_TOKEN@' + grunt.file.readJSON('package.json').repository.url.split('://')[1] + ' dist'
      },
      createBranch: {
        options: {
          execOptions: {
            cwd: 'dist'
          }
        },
        command: function (theme) {
          return 'git checkout --orphan ' + theme + ' && rm -rf * .gitignore .travis.yml';
        }
      },
      switchBranch: {
        options: {
          failOnError: true,
          callback: function(err, stdout, stderr, cb) {
            if(err) {
              grunt.task.run('shell:createBranch:' + this.args[0]);
            }
            cb();
          },
          execOptions: {
            cwd: 'dist'
          }
        },
        command: function (theme) {
          return 'git checkout ' + theme;
        }
      },
      copyUpdate: {
        options: {
        },
        command:  function (theme) {
          return 'mkdir dist/css && ' +
                 'cp -f update/' + theme + '/bootstrap.min.css dist/css/bootstrap.min.css && ' +
                 'cp -Rf update/bootstrap/* dist';
        }
      },
      commitChanges: {
        options: {
          failOnError: false,
          execOptions: {
            cwd: 'dist'
          }
        },
        command: 'git add . --all && ' +
                 'git commit -m "Auto update [ci skip]"'
      },
      tagVersion: {
        options: {
          failOnError: false,
          execOptions: {
            cwd: 'dist'
          }
        },
        command: function(theme, version) {
          return 'git tag ' + version + '-' + theme + ' -m " ' + version + ' update" -f && ' +
                 'git push origin ' + theme;
        }
      },
      pushChanges: {
        options: {
          execOptions: {
            cwd: 'dist'
          }
        },
        command: function(theme) {
          return 'git push origin ' + theme + ' --tags';
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-curl');
  grunt.loadNpmTasks('grunt-http');
  grunt.loadNpmTasks('grunt-shell');
  grunt.loadNpmTasks('grunt-contrib-clean');

  grunt.registerTask('updatePackageJson', 'Updates version of package.json file ', function() {
    var content = grunt.file.readJSON('package.json');
    content.bootswatch.version = grunt.config.get('update.version');
    grunt.file.write('package.json', JSON.stringify(content, undefined, 2));
  });

  grunt.registerTask('createBowerJson', 'Creates a bower.json for new themes ', function() {
    // read bower template
    var content = grunt.file.readJSON('bower.json');
    content.version = grunt.config.get('update.version');
    grunt.file.write('dist/bower.json', JSON.stringify(content, undefined, 2));
  });

  grunt.registerTask('updateBowerJson', 'Updates version of bower.json for themes ', function(theme) {
    if(grunt.file.exists('dist/bower.json')) {
      var content = grunt.file.readJSON('dist/bower.json');
      content.version = grunt.config.get('update.version') + '-' + theme;
      grunt.file.write('dist/bower.json', JSON.stringify(content, undefined, 2));
    }
    else // file not found create one
      grunt.task.run('createBowerJson');
  });

  grunt.registerTask('fetchBootstrapFiles', [
    'curl'
  ]);

  grunt.registerTask('release', '', function() {
    grunt.task.run('shell:setUser');
    grunt.task.run('clean:dist');
    grunt.task.run('shell:cloneProject');

    grunt.config.get('update.themes').forEach(function(theme) {
      grunt.task.run('shell:switchBranch:'+theme.name.toLowerCase());
      grunt.task.run('shell:copyUpdate:'+theme.name.toLowerCase());
      grunt.task.run('updateBowerJson:'+theme.name.toLowerCase());
      grunt.task.run('shell:commitChanges');
      grunt.task.run('shell:tagVersion:' + theme.name.toLowerCase() + ':' + grunt.config.get('update.version'));
      grunt.task.run('shell:pushChanges:' + theme.name.toLowerCase());
    });
  });

  grunt.registerTask('releaseNewThemes', function() {
    grunt.log.writeln;('themes to release',grunt.config.get('update.newThemes').length);
    if(grunt.config.get('update.newThemes').length > 0) {
      grunt.config.set('update.themes', grunt.config.get('update.newThemes'));
      grunt.task.run('fetchBootstrapFiles');
      grunt.task.run('release');
    }
    else
      grunt.log.write('All themes up to date. Version: ' + grunt.config.get('pkg.bootswatch.version'));
  })

  grunt.registerTask('checkThemes', '', function() {

    grunt.config.set('update.newThemes', []);
    grunt.config.get('update.themes').forEach(function(theme) {
      var url = grunt.config.get('pkg.repository.rawUrl') + '/' + theme.name.toLowerCase() + '/bower.json';

      grunt.config.set('http.check' + theme.name + '.options.url', url);
      grunt.config.set('http.check' + theme.name + '.options.ignoreErrors', true);
      grunt.config.set('http.check' + theme.name + '.options.callback', function(error, response, data) {
        var release = false;
        if(response.statusCode == '404') {
          release = true;
          grunt.log.writeln('New theme found: ' + theme.name);
        }
        else if(data.version < grunt.config.get('update.version')) {
          release = true;
          grunt.log.writeln('Theme out of date: ' + theme.name);
        }

        if(release) {
          // new theme release
          grunt.config.set('http.fetch' + theme.name + '.options.url', theme.cssMin);
          grunt.config.set('http.fetch' + theme.name + '.dest', 'update/' + theme.name.toLowerCase() + '/bootstrap.min.css');
          grunt.task.run('http:fetch' + theme.name);
          var newThemes = grunt.config.get('update.newThemes');
          newThemes.push(theme);
          grunt.config.set('update.newThemes', newThemes);
        }
      });
      grunt.task.run('http:check' + theme.name);
    });
  });

  grunt.registerTask('checkUpdate', [
    'http:bootswatch'
  ]);

  // Default task
  grunt.registerTask('default', [
    'checkUpdate'
  ]);
};
<commit_msg>ci skip removed not necessary anymore<commit_after>'use strict';

module.exports = function (grunt) {
  grunt.initConfig({
    // Metadata
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
    '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
    '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
    '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
    ' Licensed <%= props.license %> */\n',
    // Task configuration
    gruntfile: {
      src: 'gruntfile.js'
    },
    // shared config between update tasks
    update: {
      destination: 'update/',
      version: ''
    },
    curl: {
      // Bootstrap files
      'update/bootstrap/js/bootstrap.min.js': 'http://netdna.bootstrapcdn.com/bootstrap/latest/js/bootstrap.min.js',
      'update/bootstrap/fonts/glyphicons-halflings-regular.eot': 'http://netdna.bootstrapcdn.com/bootstrap/latest/fonts/glyphicons-halflings-regular.eot',
      'update/bootstrap/fonts/glyphicons-halflings-regular.woff': 'http://netdna.bootstrapcdn.com/bootstrap/latest/fonts/glyphicons-halflings-regular.woff',
      'update/bootstrap/fonts/glyphicons-halflings-regular.ttf': 'http://netdna.bootstrapcdn.com/bootstrap/latest/fonts/glyphicons-halflings-regular.ttf',
      'update/bootstrap/fonts/glyphicons-halflings-regular.svg': 'http://netdna.bootstrapcdn.com/bootstrap/latest/fonts/glyphicons-halflings-regular.svg'
    },
    http: {
      bootswatch: {
        options: {
          url: 'http://api.bootswatch.com/3/',
          callback: function(error, response, data) {
            data = JSON.parse(data);
            if(error)
              grunt.log.error('Something went wrong. Can\'t reach Bootswatch API');
            else {
              var latestVersion = data.version;
              grunt.config.set('update.version', latestVersion);
              grunt.config.set('update.themes', data.themes);
              grunt.log.writeln('Current version: ',grunt.config.get('pkg.bootswatch.version'));
              if(grunt.config.get('pkg.bootswatch.version') < latestVersion) {
                grunt.log.writeln('New version is available. Updating to: ' + latestVersion);
                // fetch themes
                data.themes.forEach(function(theme) {
                  // create dynamic http tasks and run them
                  grunt.config.set('http.fetch' + theme.name + '.options.url', theme.cssMin);
                  grunt.config.set('http.fetch' + theme.name + '.dest', 'update/'+ theme.name.toLowerCase() + '/bootstrap.min.css');
                  grunt.task.run('http:fetch' + theme.name);
                });
                grunt.task.run('fetchBootstrapFiles');
                grunt.task.run('release');
                grunt.task.run('updatePackageJson');
              }
              else {
                grunt.task.run('checkThemes');
                grunt.task.run('releaseNewThemes');
                grunt.task.run('updatePackageJson');
              }
            }
          }
        }
      }
    },
    clean: {
      dist: {
        src: ['dist']
      }
    },
    shell: {
      setUser: {
        options: {
        },
        command: 'git config --global user.name "Travis Build" && git config --global user.email "$GH_TOKEN"'
      },
      cloneProject: {
        options: {
        },
        command: 'git clone https://$GH_TOKEN@' + grunt.file.readJSON('package.json').repository.url.split('://')[1] + ' dist'
      },
      createBranch: {
        options: {
          execOptions: {
            cwd: 'dist'
          }
        },
        command: function (theme) {
          return 'git checkout --orphan ' + theme + ' && rm -rf * .gitignore .travis.yml';
        }
      },
      switchBranch: {
        options: {
          failOnError: true,
          callback: function(err, stdout, stderr, cb) {
            if(err) {
              grunt.task.run('shell:createBranch:' + this.args[0]);
            }
            cb();
          },
          execOptions: {
            cwd: 'dist'
          }
        },
        command: function (theme) {
          return 'git checkout ' + theme;
        }
      },
      copyUpdate: {
        options: {
        },
        command:  function (theme) {
          return 'mkdir dist/css && ' +
                 'cp -f update/' + theme + '/bootstrap.min.css dist/css/bootstrap.min.css && ' +
                 'cp -Rf update/bootstrap/* dist';
        }
      },
      commitChanges: {
        options: {
          failOnError: false,
          execOptions: {
            cwd: 'dist'
          }
        },
        command: 'git add . --all && ' +
                 'git commit -m "Auto update"'
      },
      tagVersion: {
        options: {
          failOnError: false,
          execOptions: {
            cwd: 'dist'
          }
        },
        command: function(theme, version) {
          return 'git tag ' + version + '-' + theme + ' -m " ' + version + ' update" -f && ' +
                 'git push origin ' + theme;
        }
      },
      pushChanges: {
        options: {
          execOptions: {
            cwd: 'dist'
          }
        },
        command: function(theme) {
          return 'git push origin ' + theme + ' --tags';
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-curl');
  grunt.loadNpmTasks('grunt-http');
  grunt.loadNpmTasks('grunt-shell');
  grunt.loadNpmTasks('grunt-contrib-clean');

  grunt.registerTask('updatePackageJson', 'Updates version of package.json file ', function() {
    var content = grunt.file.readJSON('package.json');
    content.bootswatch.version = grunt.config.get('update.version');
    grunt.file.write('package.json', JSON.stringify(content, undefined, 2));
  });

  grunt.registerTask('createBowerJson', 'Creates a bower.json for new themes ', function() {
    // read bower template
    var content = grunt.file.readJSON('bower.json');
    content.version = grunt.config.get('update.version');
    grunt.file.write('dist/bower.json', JSON.stringify(content, undefined, 2));
  });

  grunt.registerTask('updateBowerJson', 'Updates version of bower.json for themes ', function(theme) {
    if(grunt.file.exists('dist/bower.json')) {
      var content = grunt.file.readJSON('dist/bower.json');
      content.version = grunt.config.get('update.version') + '-' + theme;
      grunt.file.write('dist/bower.json', JSON.stringify(content, undefined, 2));
    }
    else // file not found create one
      grunt.task.run('createBowerJson');
  });

  grunt.registerTask('fetchBootstrapFiles', [
    'curl'
  ]);

  grunt.registerTask('release', '', function() {
    grunt.task.run('shell:setUser');
    grunt.task.run('clean:dist');
    grunt.task.run('shell:cloneProject');

    grunt.config.get('update.themes').forEach(function(theme) {
      grunt.task.run('shell:switchBranch:'+theme.name.toLowerCase());
      grunt.task.run('shell:copyUpdate:'+theme.name.toLowerCase());
      grunt.task.run('updateBowerJson:'+theme.name.toLowerCase());
      grunt.task.run('shell:commitChanges');
      grunt.task.run('shell:tagVersion:' + theme.name.toLowerCase() + ':' + grunt.config.get('update.version'));
      grunt.task.run('shell:pushChanges:' + theme.name.toLowerCase());
    });
  });

  grunt.registerTask('releaseNewThemes', function() {
    grunt.log.writeln;('themes to release',grunt.config.get('update.newThemes').length);
    if(grunt.config.get('update.newThemes').length > 0) {
      grunt.config.set('update.themes', grunt.config.get('update.newThemes'));
      grunt.task.run('fetchBootstrapFiles');
      grunt.task.run('release');
    }
    else
      grunt.log.write('All themes up to date. Version: ' + grunt.config.get('pkg.bootswatch.version'));
  })

  grunt.registerTask('checkThemes', '', function() {

    grunt.config.set('update.newThemes', []);
    grunt.config.get('update.themes').forEach(function(theme) {
      var url = grunt.config.get('pkg.repository.rawUrl') + '/' + theme.name.toLowerCase() + '/bower.json';

      grunt.config.set('http.check' + theme.name + '.options.url', url);
      grunt.config.set('http.check' + theme.name + '.options.ignoreErrors', true);
      grunt.config.set('http.check' + theme.name + '.options.callback', function(error, response, data) {
        var release = false;
        if(response.statusCode == '404') {
          release = true;
          grunt.log.writeln('New theme found: ' + theme.name);
        }
        else if(data.version < grunt.config.get('update.version')) {
          release = true;
          grunt.log.writeln('Theme out of date: ' + theme.name);
        }

        if(release) {
          // new theme release
          grunt.config.set('http.fetch' + theme.name + '.options.url', theme.cssMin);
          grunt.config.set('http.fetch' + theme.name + '.dest', 'update/' + theme.name.toLowerCase() + '/bootstrap.min.css');
          grunt.task.run('http:fetch' + theme.name);
          var newThemes = grunt.config.get('update.newThemes');
          newThemes.push(theme);
          grunt.config.set('update.newThemes', newThemes);
        }
      });
      grunt.task.run('http:check' + theme.name);
    });
  });

  grunt.registerTask('checkUpdate', [
    'http:bootswatch'
  ]);

  // Default task
  grunt.registerTask('default', [
    'checkUpdate'
  ]);
};
<|endoftext|>


<commit_before>module.exports = function (grunt) {
  'use strict';

  // Force use of Unix newlines
  grunt.util.linefeed = '\n';

  RegExp.quote = function (string) {
    return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
  };

  var fs = require('fs');
  var path = require('path');
  var npmShrinkwrap = require('npm-shrinkwrap');
  var generateGlyphiconsData = require('./grunt/bs-glyphicons-data-generator.js');
  var BsLessdocParser = require('./grunt/bs-lessdoc-parser.js');
  var getLessVarsData = function () {
    var filePath = path.join(__dirname, 'less/variables.less');
    var fileContent = fs.readFileSync(filePath, { encoding: 'utf8' });
    var parser = new BsLessdocParser(fileContent);
    return { sections: parser.parseFile() };
  };
  var generateRawFiles = require('./grunt/bs-raw-files-generator.js');
  var generateCommonJSModule = require('./grunt/bs-commonjs-generator.js');
  var configBridge = grunt.file.readJSON('./grunt/configBridge.json', { encoding: 'utf8' });

  Object.keys(configBridge.paths).forEach(function (key) {
    configBridge.paths[key].forEach(function (val, i, arr) {
      arr[i] = path.join('./docs/assets', val);
    });
  });

  // Project configuration.
  grunt.initConfig({

    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    // jqueryVersionCheck: configBridge.config.jqueryVersionCheck.join('\n'),

    // Task configuration.
    clean: {
      dist: 'dist',
      docs: 'docs/dist'
    },

    prefix: {
      options: {
        //均有默认配置
        keyClass: []
        ,prefix: 'sui-'
      },
      sui: {
        expand: true,
        cwd: 'less/',
        src: ['./*.less'],
        dest: 'less/',
        ext: '.less'
      }
    },

    jshint: {
      options: {
        jshintrc: 'js/.jshintrc'
      },
      grunt: {
        options: {
          jshintrc: 'grunt/.jshintrc'
        },
        src: ['Gruntfile.js', 'grunt/*.js']
      },
      core: {
        src: 'js/*.js'
      },
      test: {
        options: {
          jshintrc: 'js/tests/unit/.jshintrc'
        },
        src: 'js/tests/unit/*.js'
      },
      assets: {
        src: ['docs/assets/js/src/*.js', 'docs/assets/js/*.js', '!docs/assets/js/*.min.js']
      }
    },

    jscs: {
      options: {
        config: 'js/.jscsrc'
      },
      grunt: {
        src: '<%= jshint.grunt.src %>'
      },
      core: {
        src: '<%= jshint.core.src %>'
      },
      test: {
        src: '<%= jshint.test.src %>'
      },
      assets: {
        options: {
          requireCamelCaseOrUpperCaseIdentifiers: null
        },
        src: '<%= jshint.assets.src %>'
      }
    },

    concat: {
      options: {
      },
      sui: {
        src: [
          'js/transition.js',
          'js/alert.js',
          'js/button.js',
          'js/carousel.js',
          'js/collapse.js',
          'js/dropdown.js',
          'js/modal.js',
          'js/tooltip.js',
          'js/popover.js',
          'js/scrollspy.js',
          'js/tab.js',
          'js/affix.js'
        ],
        dest: 'dist/js/<%= pkg.name %>.js'
      }
    },

    uglify: {
      options: {
        preserveComments: 'some'
      },
      core: {
        src: '<%= concat.sui.dest %>',
        dest: 'dist/js/<%= pkg.name %>.min.js'
      },
      customize: {
        src: configBridge.paths.customizerJs,
        dest: 'docs/assets/js/customize.min.js'
      },
      docsJs: {
        src: configBridge.paths.docsJs,
        dest: 'docs/assets/js/docs.min.js'
      }
    },

    qunit: {
      options: {
        inject: 'js/tests/unit/phantom.js'
      },
      files: 'js/tests/index.html'
    },

    less: {
      compileCore: {
        options: {
          strictMath: true,
          sourceMap: true,
          outputSourceFiles: true,
          sourceMapURL: '<%= pkg.name %>.css.map',
          sourceMapFilename: 'dist/css/<%= pkg.name %>.css.map'
        },
        src: 'less/sui.less',
        dest: 'dist/css/<%= pkg.name %>.css'
      },
      compileTheme: {
        options: {
          strictMath: true,
          sourceMap: true,
          outputSourceFiles: true,
          sourceMapURL: '<%= pkg.name %>-theme.css.map',
          sourceMapFilename: 'dist/css/<%= pkg.name %>-theme.css.map'
        },
        src: 'less/theme.less',
        dest: 'dist/css/<%= pkg.name %>-theme.css'
      }
    },

    autoprefixer: {
      options: {
        browsers: configBridge.config.autoprefixerBrowsers
      },
      core: {
        options: {
          map: true
        },
        src: 'dist/css/<%= pkg.name %>.css'
      },
      theme: {
        options: {
          map: true
        },
        src: 'dist/css/<%= pkg.name %>-theme.css'
      },
      docs: {
        src: 'docs/assets/css/src/docs.css'
      },
      examples: {
        expand: true,
        cwd: 'docs/examples/',
        src: ['**/*.css'],
        dest: 'docs/examples/'
      }
    },

    csslint: {
      options: {
        csslintrc: 'less/.csslintrc'
      },
      dist: [
        'dist/css/sui.css',
        'dist/css/sui-theme.css'
      ],
      examples: [
        'docs/examples/**/*.css'
      ],
      docs: {
        options: {
          ids: false,
          'overqualified-elements': false
        },
        src: 'docs/assets/css/src/docs.css'
      }
    },

    cssmin: {
      options: {
        compatibility: 'ie8',
        keepSpecialComments: '*',
        advanced: false
      },
      minifyCore: {
        src: 'dist/css/<%= pkg.name %>.css',
        dest: 'dist/css/<%= pkg.name %>.min.css'
      },
      minifyTheme: {
        src: 'dist/css/<%= pkg.name %>-theme.css',
        dest: 'dist/css/<%= pkg.name %>-theme.min.css'
      },
      docs: {
        src: [
          'docs/assets/css/src/docs.css',
          'docs/assets/css/src/pygments-manni.css'
        ],
        dest: 'docs/assets/css/docs.min.css'
      }
    },

    csscomb: {
      options: {
        config: 'less/.csscomb.json'
      },
      dist: {
        expand: true,
        cwd: 'dist/css/',
        src: ['*.css', '!*.min.css'],
        dest: 'dist/css/'
      },
      examples: {
        expand: true,
        cwd: 'docs/examples/',
        src: '**/*.css',
        dest: 'docs/examples/'
      },
      docs: {
        src: 'docs/assets/css/src/docs.css',
        dest: 'docs/assets/css/src/docs.css'
      }
    },

    copy: {
      fonts: {
        src: 'fonts/*',
        dest: 'dist/'
      },
      docs: {
        src: 'dist/*/*',
        dest: 'docs/'
      }
    },

    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
        }
      }
    },

    jekyll: {
      options: {
        config: '_config.yml'
      },
      docs: {},
      github: {
        options: {
          raw: 'github: true'
        }
      }
    },

    jade: {
      options: {
        pretty: true,
        data: getLessVarsData
      },
      customizerVars: {
        src: 'docs/_jade/customizer-variables.jade',
        dest: 'docs/_includes/customizer-variables.html'
      },
      customizerNav: {
        src: 'docs/_jade/customizer-nav.jade',
        dest: 'docs/_includes/nav/customize.html'
      }
    },

    validation: {
      options: {
        charset: 'utf-8',
        doctype: 'HTML5',
        failHard: true,
        reset: true,
        relaxerror: [
          'Element img is missing required attribute src.',
          'Attribute autocomplete not allowed on element input at this point.',
          'Attribute autocomplete not allowed on element button at this point.',
          'Bad value separator for attribute role on element li.'
        ]
      },
      files: {
        src: '_gh_pages/**/*.html'
      }
    },

    watch: {
      src: {
        files: '<%= jshint.core.src %>',
        tasks: ['jshint:src', 'qunit', 'concat']
      },
      test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'qunit']
      },
      less: {
        files: 'less/**/*.less',
        tasks: 'less'
      }
    },

    sed: {
      versionNumber: {
        pattern: (function () {
          var old = grunt.option('oldver');
          return old ? RegExp.quote(old) : old;
        })(),
        replacement: grunt.option('newver'),
        recursive: true
      }
    },

    'saucelabs-qunit': {
      all: {
        options: {
          build: process.env.TRAVIS_JOB_ID,
          throttled: 10,
          maxRetries: 3,
          maxPollRetries: 4,
          urls: ['http://127.0.0.1:3000/js/tests/index.html'],
          browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
        }
      }
    },

    exec: {
      npmUpdate: {
        command: 'npm update'
      }
    },

    compress: {
      main: {
        options: {
          archive: 'sui-<%= pkg.version %>-dist.zip',
          mode: 'zip',
          level: 9,
          pretty: true
        },
        files: [
          {
            expand: true,
            cwd: 'dist/',
            src: ['**'],
            dest: 'sui-<%= pkg.version %>-dist'
          }
        ]
      }
    }

  });

  // These plugins provide necessary tasks.
  require('load-grunt-tasks')(grunt, { scope: 'devDependencies' });
  require('time-grunt')(grunt);

  // Docs HTML validation task
  grunt.registerTask('validate-html', ['jekyll:docs', 'validation']);

  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };

  // Test task.
  var testSubtasks = [];
  // Skip core tests if running a different subset of the test suite
  if (runSubset('core') &&
      // Skip core tests if this is a Savage build
      process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'csslint:dist', 'test-js', 'docs']);
  }
  // Skip HTML validation if running a different subset of the test suite
  if (runSubset('validate-html') &&
      // Skip HTML5 validator on Travis when [skip validator] is in the commit message
      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
    testSubtasks.push('validate-html');
  }
  // Only run Sauce Labs tests if there's a Sauce access key
  if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
      // Skip Sauce if running a different subset of the test suite
      runSubset('sauce-js-unit') &&
      // Skip Sauce on Travis when [skip sauce] is in the commit message
      isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
    testSubtasks.push('connect');
    testSubtasks.push('saucelabs-qunit');
  }
  grunt.registerTask('test', testSubtasks);
  grunt.registerTask('test-js', ['jshint:core', 'jshint:test', 'jshint:grunt', 'jscs:core', 'jscs:test', 'jscs:grunt', 'qunit']);

  // JS distribution task.
  grunt.registerTask('dist-js', ['concat', 'uglify:core', 'commonjs']);

  // CSS distribution task.
  grunt.registerTask('less-compile', ['less:compileCore', 'less:compileTheme']);
  grunt.registerTask('dist-css', ['less-compile', 'autoprefixer:core', 'autoprefixer:theme', 'csscomb:dist', 'cssmin:minifyCore', 'cssmin:minifyTheme']);

  // Full distribution task.
  grunt.registerTask('dist', ['clean:dist', 'dist-css', 'copy:fonts', 'dist-js']);

  // Default task.
  grunt.registerTask('default', ['clean:dist', 'copy:fonts', 'test']);

  // Version numbering task.
  // grunt change-version-number --oldver=A.B.C --newver=X.Y.Z
  // This can be overzealous, so its changes should always be manually reviewed!
  grunt.registerTask('change-version-number', 'sed');

  grunt.registerTask('build-glyphicons-data', function () { generateGlyphiconsData.call(this, grunt); });

  // task for building customizer
  grunt.registerTask('build-customizer', ['build-customizer-html', 'build-raw-files']);
  grunt.registerTask('build-customizer-html', 'jade');
  grunt.registerTask('build-raw-files', 'Add scripts/less files to customizer.', function () {
    generateRawFiles(grunt, '');
  });

  grunt.registerTask('commonjs', 'Generate CommonJS entrypoint module in dist dir.', function () {
    var srcFiles = grunt.config.get('concat.sui.src');
    var destFilepath = 'dist/js/npm.js';
    generateCommonJSModule(grunt, srcFiles, destFilepath);
  });

  // Docs task.
  grunt.registerTask('docs-css', ['autoprefixer:docs', 'autoprefixer:examples', 'csscomb:docs', 'csscomb:examples', 'cssmin:docs']);
  grunt.registerTask('lint-docs-css', ['csslint:docs', 'csslint:examples']);
  grunt.registerTask('docs-js', ['uglify:docsJs', 'uglify:customize']);
  grunt.registerTask('lint-docs-js', ['jshint:assets', 'jscs:assets']);
  grunt.registerTask('docs', ['docs-css', 'lint-docs-css', 'docs-js', 'lint-docs-js', 'clean:docs', 'copy:docs', 'build-glyphicons-data', 'build-customizer']);

  grunt.registerTask('prep-release', ['jekyll:github', 'compress']);

  grunt.registerTask('addprefix', ['prefix:sui']);

  // Task for updating the cached npm packages used by the Travis build (which are controlled by test-infra/npm-shrinkwrap.json).
  // This task should be run and the updated file should be committed whenever Bootstrap's dependencies change.
  grunt.registerTask('update-shrinkwrap', ['exec:npmUpdate', '_update-shrinkwrap']);
  grunt.registerTask('_update-shrinkwrap', function () {
    var done = this.async();
    npmShrinkwrap({ dev: true, dirname: __dirname }, function (err) {
      if (err) {
        grunt.fail.warn(err);
      }
      var dest = 'test-infra/npm-shrinkwrap.json';
      fs.renameSync('npm-shrinkwrap.json', dest);
      grunt.log.writeln('File ' + dest.cyan + ' updated.');
      done();
    });
  });
};
<commit_msg>update config<commit_after>module.exports = function (grunt) {
  'use strict';

  // Force use of Unix newlines
  grunt.util.linefeed = '\n';

  RegExp.quote = function (string) {
    return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
  };

  var fs = require('fs');
  var path = require('path');
  var npmShrinkwrap = require('npm-shrinkwrap');
  var generateGlyphiconsData = require('./grunt/bs-glyphicons-data-generator.js');
  var BsLessdocParser = require('./grunt/bs-lessdoc-parser.js');
  var getLessVarsData = function () {
    var filePath = path.join(__dirname, 'less/variables.less');
    var fileContent = fs.readFileSync(filePath, { encoding: 'utf8' });
    var parser = new BsLessdocParser(fileContent);
    return { sections: parser.parseFile() };
  };
  var generateRawFiles = require('./grunt/bs-raw-files-generator.js');
  var generateCommonJSModule = require('./grunt/bs-commonjs-generator.js');
  var configBridge = grunt.file.readJSON('./grunt/configBridge.json', { encoding: 'utf8' });

  Object.keys(configBridge.paths).forEach(function (key) {
    configBridge.paths[key].forEach(function (val, i, arr) {
      arr[i] = path.join('./docs/assets', val);
    });
  });

  // Project configuration.
  grunt.initConfig({

    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    // jqueryVersionCheck: configBridge.config.jqueryVersionCheck.join('\n'),

    // Task configuration.
    clean: {
      dist: 'dist',
      docs: 'docs/dist'
    },

    prefix: {
      options: {
        //均有默认配置
        keyClass: []
        ,prefix: 'sui-'
      },
      sui: {
        expand: true,
        cwd: 'less/',
        src: ['./*.less'],
        dest: 'less/',
        ext: '.less'
      }
    },

    jshint: {
      options: {
        jshintrc: 'js/.jshintrc'
      },
      grunt: {
        options: {
          jshintrc: 'grunt/.jshintrc'
        },
        src: ['Gruntfile.js', 'grunt/*.js']
      },
      core: {
        src: 'js/*.js'
      },
      test: {
        options: {
          jshintrc: 'js/tests/unit/.jshintrc'
        },
        src: 'js/tests/unit/*.js'
      },
      assets: {
        src: ['docs/assets/js/src/*.js', 'docs/assets/js/*.js', '!docs/assets/js/*.min.js']
      }
    },

    jscs: {
      options: {
        config: 'js/.jscsrc'
      },
      grunt: {
        src: '<%= jshint.grunt.src %>'
      },
      core: {
        src: '<%= jshint.core.src %>'
      },
      test: {
        src: '<%= jshint.test.src %>'
      },
      assets: {
        options: {
          requireCamelCaseOrUpperCaseIdentifiers: null
        },
        src: '<%= jshint.assets.src %>'
      }
    },

    concat: {
      options: {
      },
      sui: {
        src: [
          'js/transition.js',
          'js/alert.js',
          'js/button.js',
          'js/carousel.js',
          'js/collapse.js',
          'js/dropdown.js',
          'js/modal.js',
          'js/tooltip.js',
          'js/popover.js',
          'js/scrollspy.js',
          'js/tab.js',
          'js/affix.js'
        ],
        dest: 'dist/js/<%= pkg.name %>.js'
      }
    },

    uglify: {
      options: {
        preserveComments: 'some'
      },
      core: {
        src: '<%= concat.sui.dest %>',
        dest: 'dist/js/<%= pkg.name %>.min.js'
      },
      customize: {
        src: configBridge.paths.customizerJs,
        dest: 'docs/assets/js/customize.min.js'
      },
      docsJs: {
        src: configBridge.paths.docsJs,
        dest: 'docs/assets/js/docs.min.js'
      }
    },

    qunit: {
      options: {
        inject: 'js/tests/unit/phantom.js'
      },
      files: 'js/tests/index.html'
    },

    less: {
      compileCore: {
        options: {
          strictMath: true,
          sourceMap: true,
          outputSourceFiles: true,
          sourceMapURL: '<%= pkg.name %>.css.map',
          sourceMapFilename: 'dist/css/<%= pkg.name %>.css.map'
        },
        src: 'less/sui.less',
        dest: 'dist/css/<%= pkg.name %>.css'
      },
      compileTheme: {
        options: {
          strictMath: true,
          sourceMap: true,
          outputSourceFiles: true,
          sourceMapURL: '<%= pkg.name %>-theme.css.map',
          sourceMapFilename: 'dist/css/<%= pkg.name %>-theme.css.map'
        },
        src: 'less/theme.less',
        dest: 'dist/css/<%= pkg.name %>-theme.css'
      }
    },

    autoprefixer: {
      options: {
        browsers: configBridge.config.autoprefixerBrowsers
      },
      core: {
        options: {
          map: true
        },
        src: 'dist/css/<%= pkg.name %>.css'
      },
      theme: {
        options: {
          map: true
        },
        src: 'dist/css/<%= pkg.name %>-theme.css'
      },
      docs: {
        src: 'docs/assets/css/src/docs.css'
      },
      examples: {
        expand: true,
        cwd: 'docs/examples/',
        src: ['**/*.css'],
        dest: 'docs/examples/'
      }
    },

    csslint: {
      options: {
        csslintrc: 'less/.csslintrc'
      },
      dist: [
        'dist/css/sui.css',
        'dist/css/sui-theme.css'
      ],
      examples: [
        'docs/examples/**/*.css'
      ],
      docs: {
        options: {
          ids: false,
          'overqualified-elements': false
        },
        src: 'docs/assets/css/src/docs.css'
      }
    },

    cssmin: {
      options: {
        compatibility: 'ie8',
        keepSpecialComments: '*',
        advanced: false
      },
      minifyCore: {
        src: 'dist/css/<%= pkg.name %>.css',
        dest: 'dist/css/<%= pkg.name %>.min.css'
      },
      minifyTheme: {
        src: 'dist/css/<%= pkg.name %>-theme.css',
        dest: 'dist/css/<%= pkg.name %>-theme.min.css'
      },
      docs: {
        src: [
          'docs/assets/css/src/docs.css',
          'docs/assets/css/src/pygments-manni.css'
        ],
        dest: 'docs/assets/css/docs.min.css'
      }
    },

    csscomb: {
      options: {
        config: 'less/.csscomb.json'
      },
      dist: {
        expand: true,
        cwd: 'dist/css/',
        src: ['*.css', '!*.min.css'],
        dest: 'dist/css/'
      },
      examples: {
        expand: true,
        cwd: 'docs/examples/',
        src: '**/*.css',
        dest: 'docs/examples/'
      },
      docs: {
        src: 'docs/assets/css/src/docs.css',
        dest: 'docs/assets/css/src/docs.css'
      }
    },

    copy: {
      fonts: {
        src: 'fonts/*',
        dest: 'dist/'
      },
      docs: {
        src: 'dist/*/*',
        dest: 'docs/'
      }
    },

    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
        }
      }
    },

    jekyll: {
      options: {
        config: '_config.yml'
      },
      docs: {},
      github: {
        options: {
          raw: 'github: true'
        }
      }
    },

    jade: {
      options: {
        pretty: true,
        data: getLessVarsData
      },
      customizerVars: {
        src: 'docs/_jade/customizer-variables.jade',
        dest: 'docs/_includes/customizer-variables.html'
      },
      customizerNav: {
        src: 'docs/_jade/customizer-nav.jade',
        dest: 'docs/_includes/nav/customize.html'
      }
    },

    validation: {
      options: {
        charset: 'utf-8',
        doctype: 'HTML5',
        failHard: true,
        reset: true,
        relaxerror: [
          'Element img is missing required attribute src.',
          'Attribute autocomplete not allowed on element input at this point.',
          'Attribute autocomplete not allowed on element button at this point.',
          'Bad value separator for attribute role on element li.'
        ]
      },
      files: {
        src: '_gh_pages/**/*.html'
      }
    },

    watch: {
      src: {
        files: '<%= jshint.core.src %>',
        tasks: ['jshint:src', 'qunit', 'concat']
      },
      test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'qunit']
      },
      less: {
        files: 'less/**/*.less',
        tasks: 'less'
      }
    },

    sed: {
      versionNumber: {
        pattern: (function () {
          var old = grunt.option('oldver');
          return old ? RegExp.quote(old) : old;
        })(),
        replacement: grunt.option('newver'),
        recursive: true
      }
    },

    'saucelabs-qunit': {
      all: {
        options: {
          build: process.env.TRAVIS_JOB_ID,
          throttled: 10,
          maxRetries: 3,
          maxPollRetries: 4,
          urls: ['http://127.0.0.1:3000/js/tests/index.html'],
          browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
        }
      }
    },

    exec: {
      npmUpdate: {
        command: 'npm update'
      }
    },

    compress: {
      main: {
        options: {
          archive: 'sui-<%= pkg.version %>-dist.zip',
          mode: 'zip',
          level: 9,
          pretty: true
        },
        files: [
          {
            expand: true,
            cwd: 'dist/',
            src: ['**'],
            dest: 'sui-<%= pkg.version %>-dist'
          }
        ]
      }
    }

  });

  // These plugins provide necessary tasks.
  require('load-grunt-tasks')(grunt, { scope: 'devDependencies' });
  require('time-grunt')(grunt);

  // Docs HTML validation task
  grunt.registerTask('validate-html', ['jekyll:docs', 'validation']);

  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };

  // Test task.
  var testSubtasks = [];
  // Skip core tests if running a different subset of the test suite
  if (runSubset('core') &&
      // Skip core tests if this is a Savage build
      process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'csslint:dist', 'test-js', 'docs']);
  }
  // Skip HTML validation if running a different subset of the test suite
  if (runSubset('validate-html') &&
      // Skip HTML5 validator on Travis when [skip validator] is in the commit message
      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
    testSubtasks.push('validate-html');
  }
  // Only run Sauce Labs tests if there's a Sauce access key
  if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
      // Skip Sauce if running a different subset of the test suite
      runSubset('sauce-js-unit') &&
      // Skip Sauce on Travis when [skip sauce] is in the commit message
      isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
    testSubtasks.push('connect');
    testSubtasks.push('saucelabs-qunit');
  }
  grunt.registerTask('test', testSubtasks);
  grunt.registerTask('test-js', ['jshint:core', 'jshint:test', 'jshint:grunt', 'jscs:core', 'jscs:test', 'jscs:grunt', 'qunit']);

  // JS distribution task.
  grunt.registerTask('dist-js', ['concat', 'uglify:core', 'commonjs']);

  // CSS distribution task.
  grunt.registerTask('less-compile', ['less:compileCore', 'less:compileTheme']);
  grunt.registerTask('dist-css', ['less-compile', 'autoprefixer:core', 'autoprefixer:theme', 'csscomb:dist', 'cssmin:minifyCore', 'cssmin:minifyTheme']);

  // Full distribution task.
  grunt.registerTask('dist', ['clean:dist', 'dist-css', 'copy:fonts', 'dist-js']);

  // Default task.
  grunt.registerTask('default', ['clean:dist', 'copy:fonts', 'test']);

  // Version numbering task.
  // grunt change-version-number --oldver=A.B.C --newver=X.Y.Z
  // This can be overzealous, so its changes should always be manually reviewed!
  grunt.registerTask('change-version-number', 'sed');

  grunt.registerTask('build-glyphicons-data', function () { generateGlyphiconsData.call(this, grunt); });

  // task for building customizer
  grunt.registerTask('build-customizer', ['build-customizer-html', 'build-raw-files']);
  grunt.registerTask('build-customizer-html', 'jade');
  grunt.registerTask('build-raw-files', 'Add scripts/less files to customizer.', function () {
    generateRawFiles(grunt, '');
  });

  grunt.registerTask('commonjs', 'Generate CommonJS entrypoint module in dist dir.', function () {
    var srcFiles = grunt.config.get('concat.sui.src');
    var destFilepath = 'dist/js/npm.js';
    generateCommonJSModule(grunt, srcFiles, destFilepath);
  });

  // Docs task.
  grunt.registerTask('docs-css', ['autoprefixer:docs', 'autoprefixer:examples', 'csscomb:docs', 'csscomb:examples', 'cssmin:docs']);
  grunt.registerTask('lint-docs-css', ['csslint:docs', 'csslint:examples']);
  grunt.registerTask('docs-js', ['uglify:docsJs', 'uglify:customize']);
  grunt.registerTask('lint-docs-js', ['jshint:assets', 'jscs:assets']);
  grunt.registerTask('docs', ['docs-css', 'lint-docs-css', 'docs-js', 'lint-docs-js', 'clean:docs', 'copy:docs', 'build-glyphicons-data', 'build-customizer']);

  grunt.registerTask('prep-release', ['jekyll:github', 'compress']);

  grunt.loadNpmTasks('prefix-cssclass');
  grunt.registerTask('addprefix', ['prefix:sui']);

  // Task for updating the cached npm packages used by the Travis build (which are controlled by test-infra/npm-shrinkwrap.json).
  // This task should be run and the updated file should be committed whenever Bootstrap's dependencies change.
  grunt.registerTask('update-shrinkwrap', ['exec:npmUpdate', '_update-shrinkwrap']);
  grunt.registerTask('_update-shrinkwrap', function () {
    var done = this.async();
    npmShrinkwrap({ dev: true, dirname: __dirname }, function (err) {
      if (err) {
        grunt.fail.warn(err);
      }
      var dest = 'test-infra/npm-shrinkwrap.json';
      fs.renameSync('npm-shrinkwrap.json', dest);
      grunt.log.writeln('File ' + dest.cyan + ' updated.');
      done();
    });
  });
};
<|endoftext|>


<commit_before>"use strict";

String.prototype.replaceAll = function(search, replacement) {
  var target = this;
  return target.replace(new RegExp(search, 'g'), replacement);
};

String.prototype.deleteCharAt = function(pos) {
  var target = this;
  return (pos > 0) ? target.substring(0, pos - 1) + target.substring(pos) : target;
};

String.prototype.insertAt = function(pos, str) {
  var target = this;
  return target.substring(0, pos) + str + target.substring(pos);
};

function busy(enable) {
  var elem = document.getElementById('busy')
  elem.className = enable ? 'blink' : 'hide';
}

function isBusy() {
  var elem = document.getElementById('busy')
  return elem.className === 'blink';
}

function sendCommand(text, cb) {
  var xhr = new XMLHttpRequest();

  xhr.open('POST', '/byok3', true);
  xhr.setRequestHeader('Accept', 'text/plain');
  xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
  xhr.onload = function() {
    cb(null, xhr.responseText);
  };
  xhr.onerror = function(e) {
    cb("Unknown error occured: server response not received.", null);
  };

  xhr.send(text);
}

function ESC(ansicode) {
  return '\u001B[' + ansicode;
}

hterm.defaultStorage = new lib.Storage.Local();
var t = new hterm.Terminal();
t.onTerminalReady = function() {
  var maxLineLength = 256;
  var io = t.io.push();
  var input = '';
  var pos = 0;
  var currHist = 0;
  var history = [];
  var insertMode = true;

  io.onReadline = function(str) {
    busy(true);
    sendCommand(input, function(err, result) {
      t.io.print((result || err).replaceAll('\n', '\r\n'));
      busy(false);
    });
  };

  io.onVTKeystroke = io.sendString = function(str) {
    if (isBusy()) return;

    var chr = str.charCodeAt(0);
    if (chr === 13) {

      var histIdx = history.indexOf(input.trim());
      if (histIdx >= 0) {
        history.splice(histIdx, 1);
      }
      if (input.trim() !== "") {
        history.push(input.trim());
      }
      currHist = history.length;

      io.onReadline(input);
      io.println('');
      input = '';
      pos = 0;

    } else if (chr === 1) { // Ctrl-A
      pos = 0;

    } else if (chr === 5) { // Ctrl-E
      pos = input.length;

    } else if (chr === 127 && pos > 0) { // Backspace
      input = input.deleteCharAt(pos)
      pos -= 1;

    } else if (str === ESC("3~")) { // Delete
      input = input.deleteCharAt(pos + 1)

    } else if (str === ESC('A') && currHist > 0) { // Cursor up
      currHist -= 1;
      input = history[currHist];
      pos = input.length;

    } else if (str === ESC('B') && currHist < history.length) { // Cursor down
      currHist += 1;
      input = currHist < history.length ? history[currHist] : "";
      pos = input.length;

    } else if (str === ESC('C') && pos < input.length) { // Cursor right
      pos += 1;

    } else if (str === ESC('D') && pos > 0) { // Cursor left
      pos -= 1;

    } else if (chr >= 32 && chr < 127 && (pos + str.length) < maxLineLength) {
      if (insertMode) {
        input = input.insertAt(pos, str);
        pos += str.length;
      } else {
        // TODO - replace mode
      }
    }

    io.print(ESC('0G') + ESC('2K') + input + ESC((pos + 1) + 'G'));
  };

  sendCommand('', function(err, result) {
    t.io.print((result || err).replaceAll('\n', '\r\n'));
    busy(false);
  });
};

t.decorate(document.querySelector('#terminal'));
t.installKeyboard();<commit_msg>Improve secondary prompt indenting<commit_after>"use strict";

String.prototype.replaceAll = function(search, replacement) {
  var target = this;
  return target.replace(new RegExp(search, 'g'), replacement);
};

String.prototype.deleteCharAt = function(pos) {
  var target = this;
  return (pos > 0) ? target.substring(0, pos - 1) + target.substring(pos) : target;
};

String.prototype.insertAt = function(pos, str) {
  var target = this;
  return target.substring(0, pos) + str + target.substring(pos);
};

function busy(enable) {
  var elem = document.getElementById('busy')
  elem.className = enable ? 'blink' : 'hide';
}

function isBusy() {
  var elem = document.getElementById('busy')
  return elem.className === 'blink';
}

function sendCommand(text, cb) {
  var xhr = new XMLHttpRequest();

  xhr.open('POST', '/byok3', true);
  xhr.setRequestHeader('Accept', 'text/plain');
  xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
  xhr.onload = function() {
    cb(null, xhr.responseText);
  };
  xhr.onerror = function(e) {
    cb("Unknown error occured: server response not received.", null);
  };

  xhr.send(text);
}

function ESC(ansicode) {
  return '\u001B[' + ansicode;
}

hterm.defaultStorage = new lib.Storage.Local();
var t = new hterm.Terminal();
t.onTerminalReady = function() {
  var maxLineLength = 256;
  var io = t.io.push();
  var input = '';
  var pos = 0;
  var currHist = 0;
  var history = [];
  var insertMode = true;
  var offset = 0;

  io.onReadline = function(str) {
    busy(true);
    sendCommand(input, function(err, result) {
      t.io.print(ESC('0G') + (result || err).replaceAll('\n', '\r\n'));
      offset = result.lastIndexOf('|  ') < 0 ? 0 : 3;
      busy(false);
    });
  };

  io.onVTKeystroke = io.sendString = function(str) {
    if (isBusy()) return;

    var chr = str.charCodeAt(0);
    if (chr === 13) {

      var histIdx = history.indexOf(input.trim());
      if (histIdx >= 0) {
        history.splice(histIdx, 1);
      }
      if (input.trim() !== "") {
        history.push(input.trim());
      }
      currHist = history.length;

      io.onReadline(input);
      io.println('');
      input = '';
      pos = 0;

    } else if (chr === 1) { // Ctrl-A
      pos = 0;

    } else if (chr === 5) { // Ctrl-E
      pos = input.length;

    } else if (chr === 127 && pos > 0) { // Backspace
      input = input.deleteCharAt(pos)
      pos -= 1;

    } else if (str === ESC("3~")) { // Delete
      input = input.deleteCharAt(pos + 1)

    } else if (str === ESC('A') && currHist > 0) { // Cursor up
      currHist -= 1;
      input = history[currHist];
      pos = input.length;

    } else if (str === ESC('B') && currHist < history.length) { // Cursor down
      currHist += 1;
      input = currHist < history.length ? history[currHist] : "";
      pos = input.length;

    } else if (str === ESC('C') && pos < input.length) { // Cursor right
      pos += 1;

    } else if (str === ESC('D') && pos > 0) { // Cursor left
      pos -= 1;

    } else if (chr >= 32 && chr < 127 && (pos + str.length) < maxLineLength) {
      if (insertMode) {
        input = input.insertAt(pos, str);
        pos += str.length;
      } else {
        // TODO - replace mode
      }
    }

    io.print(ESC((offset + 1) + 'G') + ESC('K') + input + ESC((offset + pos + 1) + 'G'));
  };

  sendCommand('', function(err, result) {
    t.io.print((result || err).replaceAll('\n', '\r\n'));
    busy(false);
  });
};

t.decorate(document.querySelector('#terminal'));
t.installKeyboard();<|endoftext|>