<commit_before>import { Transform } from "readable-stream";
import { TYPE } from "./token";

const tokenParsers = {
  [TYPE.COLMETADATA]: require('./colmetadata-token-parser'),

  [TYPE.DONE]: require('./done-token-parser').doneParser,
  [TYPE.DONEINPROC]: require('./done-token-parser').doneInProcParser,
  [TYPE.DONEPROC]: require('./done-token-parser').doneProcParser,

  [TYPE.ENVCHANGE]: require('./env-change-token-parser'),
  [TYPE.ERROR]: require('./infoerror-token-parser').errorParser,
  [TYPE.INFO]: require('./infoerror-token-parser').infoParser,
  [TYPE.LOGINACK]: require('./loginack-token-parser'),
  [TYPE.ORDER]: require('./order-token-parser'),
  [TYPE.RETURNSTATUS]: require('./returnstatus-token-parser'),
  [TYPE.RETURNVALUE]: require('./returnvalue-token-parser'),
  [TYPE.ROW]: require('./row-token-parser'),
  [TYPE.NBCROW]: require('./nbcrow-token-parser'),
  [TYPE.SSPI]: require('./sspi-token-parser')
};

export default class Parser extends Transform {
  constructor(debug, colMetadata, options) {
    super({ objectMode: true });

    this.debug = debug;
    this.colMetadata = colMetadata;
    this.options = options;

    this.nextStep = undefined;
    this.buffer = new Buffer(0);
    this.position = 0;
  }

  _transform(input, encoding, done) {
    if (this.position === this.buffer.length) {
      this.buffer = input;
    } else {
      this.buffer = Buffer.concat([this.buffer.slice(this.position), input]);
    }
    this.position = 0;

    // This will be called once we need to wait for more data to
    // become available
    this.await = done;

    if (!this.next) {
      // Start the parser
      this.parseNextToken();
    } else {
      // Continue from the last location
      this.next();
    }
  }

  parseNextToken() {
    this.readUInt8((type) => {
      if (tokenParsers[type]) {
        tokenParsers[type](this, this.colMetadata, this.options, (token) => {
          if (token) {
            switch (token.name) {
              case 'COLMETADATA':
                this.colMetadata = token.columns;
            }

            this.push(token);
          }

          this.parseNextToken();
        });
      } else {
        this.emit('error', new Error("Unknown type: " + type));
      }
    });
  }

  awaitData(length, callback) {
    if (this.position + length <= this.buffer.length) {
      callback();
    } else {
      this.next = () => {
        this.awaitData(length, callback);
      };
      this.await();
    }
  }

  readInt8(callback) {
    this.awaitData(1, () => {
      const data = this.buffer.readInt8(this.position);
      this.position += 1;
      callback(data);
    });
  }

  readUInt8(callback) {
    this.awaitData(1, () => {
      const data = this.buffer.readUInt8(this.position);
      this.position += 1;
      callback(data);
    });
  }

  readInt16LE(callback) {
    this.awaitData(2, () => {
      const data = this.buffer.readInt16LE(this.position);
      this.position += 2;
      callback(data);
    });
  }

  readInt16BE(callback) {
    this.awaitData(2, () => {
      const data = this.buffer.readInt16BE(this.position);
      this.position += 2;
      callback(data);
    });
  }

  readUInt16LE(callback) {
    this.awaitData(2, () => {
      const data = this.buffer.readUInt16LE(this.position);
      this.position += 2;
      callback(data);
    });
  }

  readUInt16BE(callback) {
    this.awaitData(2, () => {
      const data = this.buffer.readUInt16BE(this.position);
      this.position += 2;
      callback(data);
    });
  }

  readInt32LE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readInt32LE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readInt32BE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readInt32BE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readUInt32LE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readUInt32LE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readUInt32BE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readUInt32BE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readInt64LE(callback) {
    this.awaitData(8, () => {
      const data = Math.pow(2, 32) * this.buffer.readInt32LE(this.position + 4) + (this.buffer[this.position + 4] & 0x80 === 0x80 ? 1 : -1) * this.buffer.readUInt32LE(this.position);
      this.position += 8;
      callback(data);
    });
  }

  readInt64BE(callback) {
    this.awaitData(8, () => {
      const data = Math.pow(2, 32) * this.buffer.readInt32BE(this.position) + (this.buffer[this.position] & 0x80 === 0x80 ? 1 : -1) * this.buffer.readUInt32BE(this.position + 4);
      this.position += 8;
      callback(data);
    });
  }

  readUInt64LE(callback) {
    this.awaitData(8, () => {
      const data = Math.pow(2, 32) * this.buffer.readUInt32LE(this.position + 4) + this.buffer.readUInt32LE(this.position);
      this.position += 8;
      callback(data);
    });
  }

  readUInt64BE(callback) {
    this.awaitData(8, () => {
      const data = Math.pow(2, 32) * this.buffer.readUInt32BE(this.position) + this.buffer.readUInt32BE(this.position + 4);
      this.position += 8;
      callback(data);
    });
  }

  readFloatLE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readFloatLE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readFloatBE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readFloatBE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readDoubleLE(callback) {
    this.awaitData(8, () => {
      const data = this.buffer.readDoubleLE(this.position);
      this.position += 8;
      callback(data);
    });
  }

  readDoubleBE(callback) {
    this.awaitData(8, () => {
      const data = this.buffer.readDoubleBE(this.position);
      this.position += 8;
      callback(data);
    });
  }

  readUInt24LE(callback) {
    this.awaitData(3, () => {
      const low = this.buffer.readUInt16LE(this.position);
      const high = this.buffer.readUInt8(this.position + 2);

      this.position += 3;

      callback(low | (high << 16));
    });
  }

  readUInt40LE(callback) {
    this.awaitData(5, () => {
      const low = this.buffer.readUInt32LE(this.position);
      const high = this.buffer.readUInt8(this.position + 4);

      this.position += 5;

      callback((0x100000000 * high) + low);
    });
  }

  readUNumeric64LE(callback) {
    this.awaitData(8, () => {
      const low = this.buffer.readUInt32LE(this.position);
      const high = this.buffer.readUInt32LE(this.position + 4);

      this.position += 8;

      callback((0x100000000 * high) + low);
    });
  }

  readUNumeric96LE(callback) {
    this.awaitData(12, () => {
      const dword1 = this.buffer.readUInt32LE(this.position);
      const dword2 = this.buffer.readUInt32LE(this.position + 4);
      const dword3 = this.buffer.readUInt32LE(this.position + 8);

      this.position += 12;

      callback(dword1 + (0x100000000 * dword2) + (0x100000000 * 0x100000000 * dword3));
    });
  }

  readUNumeric128LE(callback) {
    this.awaitData(16, () => {
      const dword1 = this.buffer.readUInt32LE(this.position);
      const dword2 = this.buffer.readUInt32LE(this.position + 4);
      const dword3 = this.buffer.readUInt32LE(this.position + 8);
      const dword4 = this.buffer.readUInt32LE(this.position + 12);

      this.position += 16;

      callback(dword1 + (0x100000000 * dword2) + (0x100000000 * 0x100000000 * dword3) + (0x100000000 * 0x100000000 * 0x100000000 * dword4));
    });
  }

  // Variable length data

  readBuffer(length, callback) {
    this.awaitData(length, () => {
      const data = this.buffer.slice(this.position, this.position + length);
      this.position += length;
      callback(data);
    });
  }

  // Read a Unicode String (BVARCHAR)
  readBVarChar(callback) {
    this.readUInt8((length) => {
      this.readBuffer(length * 2, (data) => {
        callback(data.toString("ucs2"));
      });
    });
  }

  // Read a Unicode String (USVARCHAR)
  readUsVarChar(callback) {
    this.readUInt16LE((length) => {
      this.readBuffer(length * 2, (data) => {
        callback(data.toString("ucs2"));
      });
    });
  }

  // Read binary data (BVARBYTE)
  readBVarByte(callback) {
    this.readUInt8((length) => {
      this.readBuffer(length, callback);
    });
  }

  // Read binary data (USVARBYTE)
  readUsVarByte(callback) {
    this.readUInt16LE((length) => {
      this.readBuffer(length, callback);
    });
  }
}
<commit_msg>Prevent class thrashing and resulting deoptimizations.<commit_after>import { Transform } from "readable-stream";
import { TYPE } from "./token";

const tokenParsers = {
  [TYPE.COLMETADATA]: require('./colmetadata-token-parser'),

  [TYPE.DONE]: require('./done-token-parser').doneParser,
  [TYPE.DONEINPROC]: require('./done-token-parser').doneInProcParser,
  [TYPE.DONEPROC]: require('./done-token-parser').doneProcParser,

  [TYPE.ENVCHANGE]: require('./env-change-token-parser'),
  [TYPE.ERROR]: require('./infoerror-token-parser').errorParser,
  [TYPE.INFO]: require('./infoerror-token-parser').infoParser,
  [TYPE.LOGINACK]: require('./loginack-token-parser'),
  [TYPE.ORDER]: require('./order-token-parser'),
  [TYPE.RETURNSTATUS]: require('./returnstatus-token-parser'),
  [TYPE.RETURNVALUE]: require('./returnvalue-token-parser'),
  [TYPE.ROW]: require('./row-token-parser'),
  [TYPE.NBCROW]: require('./nbcrow-token-parser'),
  [TYPE.SSPI]: require('./sspi-token-parser')
};

export default class Parser extends Transform {
  constructor(debug, colMetadata, options) {
    super({ objectMode: true });

    this.debug = debug;
    this.colMetadata = colMetadata;
    this.options = options;

    this.nextStep = undefined;
    this.buffer = new Buffer(0);
    this.position = 0;
    this.await = undefined;
    this.next = undefined;
  }

  _transform(input, encoding, done) {
    if (this.position === this.buffer.length) {
      this.buffer = input;
    } else {
      this.buffer = Buffer.concat([this.buffer.slice(this.position), input]);
    }
    this.position = 0;

    // This will be called once we need to wait for more data to
    // become available
    this.await = done;

    if (!this.next) {
      // Start the parser
      this.parseNextToken();
    } else {
      // Continue from the last location
      this.next();
    }
  }

  parseNextToken() {
    this.readUInt8((type) => {
      if (tokenParsers[type]) {
        tokenParsers[type](this, this.colMetadata, this.options, (token) => {
          if (token) {
            switch (token.name) {
              case 'COLMETADATA':
                this.colMetadata = token.columns;
            }

            this.push(token);
          }

          this.parseNextToken();
        });
      } else {
        this.emit('error', new Error("Unknown type: " + type));
      }
    });
  }

  awaitData(length, callback) {
    if (this.position + length <= this.buffer.length) {
      callback();
    } else {
      this.next = () => {
        this.awaitData(length, callback);
      };
      this.await();
    }
  }

  readInt8(callback) {
    this.awaitData(1, () => {
      const data = this.buffer.readInt8(this.position);
      this.position += 1;
      callback(data);
    });
  }

  readUInt8(callback) {
    this.awaitData(1, () => {
      const data = this.buffer.readUInt8(this.position);
      this.position += 1;
      callback(data);
    });
  }

  readInt16LE(callback) {
    this.awaitData(2, () => {
      const data = this.buffer.readInt16LE(this.position);
      this.position += 2;
      callback(data);
    });
  }

  readInt16BE(callback) {
    this.awaitData(2, () => {
      const data = this.buffer.readInt16BE(this.position);
      this.position += 2;
      callback(data);
    });
  }

  readUInt16LE(callback) {
    this.awaitData(2, () => {
      const data = this.buffer.readUInt16LE(this.position);
      this.position += 2;
      callback(data);
    });
  }

  readUInt16BE(callback) {
    this.awaitData(2, () => {
      const data = this.buffer.readUInt16BE(this.position);
      this.position += 2;
      callback(data);
    });
  }

  readInt32LE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readInt32LE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readInt32BE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readInt32BE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readUInt32LE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readUInt32LE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readUInt32BE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readUInt32BE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readInt64LE(callback) {
    this.awaitData(8, () => {
      const data = Math.pow(2, 32) * this.buffer.readInt32LE(this.position + 4) + (this.buffer[this.position + 4] & 0x80 === 0x80 ? 1 : -1) * this.buffer.readUInt32LE(this.position);
      this.position += 8;
      callback(data);
    });
  }

  readInt64BE(callback) {
    this.awaitData(8, () => {
      const data = Math.pow(2, 32) * this.buffer.readInt32BE(this.position) + (this.buffer[this.position] & 0x80 === 0x80 ? 1 : -1) * this.buffer.readUInt32BE(this.position + 4);
      this.position += 8;
      callback(data);
    });
  }

  readUInt64LE(callback) {
    this.awaitData(8, () => {
      const data = Math.pow(2, 32) * this.buffer.readUInt32LE(this.position + 4) + this.buffer.readUInt32LE(this.position);
      this.position += 8;
      callback(data);
    });
  }

  readUInt64BE(callback) {
    this.awaitData(8, () => {
      const data = Math.pow(2, 32) * this.buffer.readUInt32BE(this.position) + this.buffer.readUInt32BE(this.position + 4);
      this.position += 8;
      callback(data);
    });
  }

  readFloatLE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readFloatLE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readFloatBE(callback) {
    this.awaitData(4, () => {
      const data = this.buffer.readFloatBE(this.position);
      this.position += 4;
      callback(data);
    });
  }

  readDoubleLE(callback) {
    this.awaitData(8, () => {
      const data = this.buffer.readDoubleLE(this.position);
      this.position += 8;
      callback(data);
    });
  }

  readDoubleBE(callback) {
    this.awaitData(8, () => {
      const data = this.buffer.readDoubleBE(this.position);
      this.position += 8;
      callback(data);
    });
  }

  readUInt24LE(callback) {
    this.awaitData(3, () => {
      const low = this.buffer.readUInt16LE(this.position);
      const high = this.buffer.readUInt8(this.position + 2);

      this.position += 3;

      callback(low | (high << 16));
    });
  }

  readUInt40LE(callback) {
    this.awaitData(5, () => {
      const low = this.buffer.readUInt32LE(this.position);
      const high = this.buffer.readUInt8(this.position + 4);

      this.position += 5;

      callback((0x100000000 * high) + low);
    });
  }

  readUNumeric64LE(callback) {
    this.awaitData(8, () => {
      const low = this.buffer.readUInt32LE(this.position);
      const high = this.buffer.readUInt32LE(this.position + 4);

      this.position += 8;

      callback((0x100000000 * high) + low);
    });
  }

  readUNumeric96LE(callback) {
    this.awaitData(12, () => {
      const dword1 = this.buffer.readUInt32LE(this.position);
      const dword2 = this.buffer.readUInt32LE(this.position + 4);
      const dword3 = this.buffer.readUInt32LE(this.position + 8);

      this.position += 12;

      callback(dword1 + (0x100000000 * dword2) + (0x100000000 * 0x100000000 * dword3));
    });
  }

  readUNumeric128LE(callback) {
    this.awaitData(16, () => {
      const dword1 = this.buffer.readUInt32LE(this.position);
      const dword2 = this.buffer.readUInt32LE(this.position + 4);
      const dword3 = this.buffer.readUInt32LE(this.position + 8);
      const dword4 = this.buffer.readUInt32LE(this.position + 12);

      this.position += 16;

      callback(dword1 + (0x100000000 * dword2) + (0x100000000 * 0x100000000 * dword3) + (0x100000000 * 0x100000000 * 0x100000000 * dword4));
    });
  }

  // Variable length data

  readBuffer(length, callback) {
    this.awaitData(length, () => {
      const data = this.buffer.slice(this.position, this.position + length);
      this.position += length;
      callback(data);
    });
  }

  // Read a Unicode String (BVARCHAR)
  readBVarChar(callback) {
    this.readUInt8((length) => {
      this.readBuffer(length * 2, (data) => {
        callback(data.toString("ucs2"));
      });
    });
  }

  // Read a Unicode String (USVARCHAR)
  readUsVarChar(callback) {
    this.readUInt16LE((length) => {
      this.readBuffer(length * 2, (data) => {
        callback(data.toString("ucs2"));
      });
    });
  }

  // Read binary data (BVARBYTE)
  readBVarByte(callback) {
    this.readUInt8((length) => {
      this.readBuffer(length, callback);
    });
  }

  // Read binary data (USVARBYTE)
  readUsVarByte(callback) {
    this.readUInt16LE((length) => {
      this.readBuffer(length, callback);
    });
  }
}
<|endoftext|>


<commit_before>var spawn = require('child_process').spawn;

function Spawn(stream, command, args, name, output) {

    var exited = false;
    var ended = false;
    var spawned_process;

    this.id = name;

    spawned_process = spawn(command, args);

    if (output) {
        spawned_process.stdout.pipe(output.stdin);
        spawned_process.stderr.pipe(output.stdin);
    }

    if (stream) {

        spawned_process.stdout.pipe(stream, {
            end: false
        });

        spawned_process.stderr.pipe(stream, {
            end: false
        });

        spawned_process.stdout.on('end', function() {
            ended = true;
            if (exited !== false) {
                stream.exit(exited);
            }
        });

        spawned_process.on('exit', function(code, signal) {
            exited = code;
            if (ended !== false) {
                stream.exit(exited);
            }
        });

        spawned_process.on('error', function(err) {
            stream.exit(1);
        });
    }

    return spawned_process;
}

module.exports = Spawn;
<commit_msg>Adding exit handler for process.<commit_after>var spawn = require('child_process').spawn;

function Spawn(stream, command, args, name, output) {

    var exited = false;
    var ended = false;
    var spawned_process = spawn(command, args);

    this.id = name;

    if (output) {
        spawned_process.stdout.pipe(output.stdin);
        spawned_process.stderr.pipe(output.stdin);
    }

    if (stream) {

        spawned_process.stdout.pipe(stream, {
            end: false
        });

        spawned_process.stderr.pipe(stream, {
            end: false
        });

        spawned_process.stdout.on('end', function() {
            ended = true;
            if (exited !== false) {
                stream.exit(exited);
            }
        });

        spawned_process.on('exit', function(code, signal) {
            exited = code;
            if (ended !== false) {
                stream.exit(exited);
            }
        });

        spawned_process.on('error', function(err) {
            stream.exit(1);
        });
    }

    process.on('exit', function(code) {
        if (exited !== undefined) {
            spawned_process.exit(code);
        }
    });

    return spawned_process;
}

module.exports = Spawn;
<|endoftext|>


<commit_before>'use strict';
/**
 * Github FileBrowser configuration module.
 */

var m = require('mithril');
var $ = require('jquery');
var URI = require('URIjs');
var Fangorn = require('js/fangorn');
var waterbutler = require('js/waterbutler');
var $osf = require('js/osfHelpers');

// Cross browser key codes for the Command key
var commandKeys = [224, 17, 91, 93];

function _uploadUrl(item, file) {
    return waterbutler.buildTreeBeardUpload(item, file, {branch: item.data.branch});
}

// TODO: Refactor, repeating from core function too much
function _removeEvent (event, items) {
    var tb = this;
    function cancelDelete() {
        tb.modal.dismiss();
    }

    function runDelete (item) {
        tb.select('.tb-modal-footer .text-danger').html(' Deleting...').css('color', 'grey');;
        // delete from server, if successful delete from view
        $.ajax({
            url: waterbutler.buildTreeBeardDelete(item, {branch: item.data.branch, sha: item.data.extra.fileSha}),
            type : 'DELETE',
            beforeSend: $osf.setXHRAuthorization
        }).done(function (data) {
                // delete view
                tb.deleteNode(item.parentID, item.id);
                Fangorn.Utils.dismissToolbar.call(tb);
                tb.modal.dismiss();
                tb.clearMultiselect();

        }).fail(function (data) {
                tb.modal.dismiss();
                Fangorn.Utils.dismissToolbar.call(tb);
                item.notify.update('Delete failed.', 'danger', undefined, 3000);
                tb.clearMultiselect();
        });
    }

    function runDeleteMultiple(items){
        items.forEach(function(item){
            runDelete(item);
        });
    }

    // If there is only one item being deleted, don't complicate the issue:
    if(items.length === 1) {
        var parent = items[0].parent();
        var mithrilContentSingle = m('div', [
            m('h3.break-word', 'Delete "' + items[0].data.name + '"'),
            m('p', 'This action is irreversible.'),
            parent.children.length < 2 ? m('p', 'If a folder in Github has no children it will automatically be removed.') : ''
        ]);
        var mithrilButtonsSingle = m('div', [
            m('span.tb-modal-btn', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
            m('span.tb-modal-btn', { 'class' : 'text-danger', onclick : function() { runDelete(items[0]); }  }, 'Delete')
        ]);
        // This is already being checked before this step but will keep this edit permission check
        if(items[0].data.permissions.edit){
            tb.modal.update(mithrilContentSingle, mithrilButtonsSingle);
        }
    } else {
        // Check if all items can be deleted
        var canDelete = true;
        var deleteList = [];
        var noDeleteList = [];
        var mithrilContentMultiple;
        var mithrilButtonsMultiple;
        items.forEach(function(item, index, arr){
            if(!item.data.permissions.edit){
                canDelete = false;
                noDeleteList.push(item);
            } else {
                deleteList.push(item);
            }
        });
        // If all items can be deleted
        if(canDelete){
            mithrilContentMultiple = m('div', [
                    m('h3.break-word', 'Delete multiple files?'),
                    m('p', 'This action is irreversible.'),
                    deleteList.map(function(item){
                        return m('.fangorn-canDelete.text-success', item.data.name);
                    })
                ]);
            mithrilButtonsMultiple =  m('div', [
                    m('span.tb-modal-btn', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
                    m('span.tb-modal-btn', { 'class' : 'text-danger', onclick : function() { runDeleteMultiple.call(tb, deleteList); }  }, 'Delete All')
                ]);
        } else {
            mithrilContentMultiple = m('div', [
                    m('h3.break-word', 'Delete multiple files?'),
                    m('p', 'Some of these files can\'t be deleted but you can delete the ones highlighted with green. This action is irreversible.'),
                    deleteList.map(function(n){
                        return m('.fangorn-canDelete.text-success', n.data.name);
                    }),
                    noDeleteList.map(function(n){
                        return m('.fangorn-noDelete.text-warning', n.data.name);
                    })
                ]);
            mithrilButtonsMultiple =  m('div', [
                    m('span.tb-modal-btn', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
                    m('span.tb-modal-btn', { 'class' : 'text-danger', onclick : function() { runDeleteMultiple.call(tb, deleteList); }  }, 'Delete Some')
                ]);
        }
        tb.modal.update(mithrilContentMultiple, mithrilButtonsMultiple);
    }

    return true; // Let fangorn know this config option was used.
}

// Define Fangorn Button Actions
var _githubItemButtons = {
    view: function (ctrl, args, children) {
        var tb = args.treebeard;
        var item = args.item;
        var buttons = [];
        function _downloadEvent(event, item, col) {
            event.stopPropagation();
            window.location = waterbutler.buildTreeBeardDownload(item, {fileSha: item.data.extra.fileSha});
        }
        // Download Zip File
        if (item.kind === 'folder') {
            var branchArray = [];
            if (item.data.branches) {
                item.data.branch = item.data.branch || item.data.defaultBranch;
                for (var i = 0; i < item.data.branches.length; i++) {
                    var selected = item.data.branches[i] === item.data.branch ? 'selected' : '';
                    branchArray.push(m('option', {
                        selected: selected,
                        value: item.data.branches[i]
                    }, item.data.branches[i]));
                }
            }
            if (item.data.addonFullname) {
                buttons.push(
                    m.component(Fangorn.Components.dropdown, {
                        'label': 'Branch: ',
                        onchange: function (event) {
                            changeBranch.call(tb, item, event.target.value);
                        },
                        icon: 'fa fa-external-link',
                        className: 'text-info'
                    }, branchArray)
                );
            }
            if (tb.options.placement !== 'fileview') {
                // If File and FileRead are not defined dropzone is not supported and neither is uploads
                if (window.File && window.FileReader && item.data.permissions && item.data.permissions.edit) {
                    buttons.push(
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                Fangorn.ButtonEvents._uploadEvent.call(tb, event, item);
                            },
                            icon: 'fa fa-upload',
                            className: 'text-success'
                        }, 'Upload'),
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                tb.toolbarMode(Fangorn.Components.toolbarModes.ADDFOLDER);
                            },
                            icon: 'fa fa-plus',
                            className: 'text-success'
                        }, 'Create Folder')
                    );
                }
                if (item.data.addonFullname) {
                    buttons.push(
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                window.location = item.data.urls.zip;
                            },
                            icon: 'fa fa-download',
                            className: 'text-primary'
                        }, 'Download'),
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                window.open(item.data.urls.repo, '_blank');
                            },
                            icon: 'fa fa-external-link',
                            className: 'text-info'
                        }, 'Open')
                    );
                }
            }
        } else if (item.kind === 'file' && tb.options.placement !== 'fileview') {
            buttons.push(
                m.component(Fangorn.Components.button, {
                    onclick: function (event) {
                        _downloadEvent.call(tb, event, item);
                    },
                    icon: 'fa fa-download',
                    className: 'text-primary'
                }, 'Download')
            );
            if (item.data.permissions && item.data.permissions.view) {
                buttons.push(
                    m.component(Fangorn.Components.button, {
                        onclick: function(event) {
                            gotoFile.call(tb, item);
                        },
                        icon: 'fa fa-file-o',
                        className : 'text-info'
                    }, 'View'));
            }
            if (item.data.permissions && item.data.permissions.edit) {
                buttons.push(
                    m.component(Fangorn.Components.button, {
                        onclick: function (event) {
                            _removeEvent.call(tb, event, [item]);
                        },
                        icon: 'fa fa-trash',
                        className: 'text-danger'
                    }, 'Delete')
                );
            }
            if (item.data.permissions && item.data.permissions.view && !item.data.permissions.private) {
                buttons.push(
                    m('a.text-info.fangorn-toolbar-icon', {href: item.data.extra.webView}, [
                        m('i.fa.fa-external-link'),
                        m('span', 'View on GitHub')
                    ])
                );
            }
        }

        if(item.data.provider && !item.data.isAddonRoot && item.data.permissions && item.data.permissions.edit && tb.options.placement !== 'fileview') {
            buttons.push(
                m.component(Fangorn.Components.button, {
                    onclick: function() {
                        tb.toolbarMode(Fangorn.Components.toolbarModes.RENAME);
                    },
                    tooltip: 'Change the name of the item',
                    icon: 'fa fa-font',
                    className : 'text-info'
                }, 'Rename')
            );
        }

        return m('span', buttons); // Tell fangorn this function is used.
    }
};

function changeBranch(item, ref){
    item.data.branch = ref;
    this.updateFolder(null, item);
}

function _resolveLazyLoad(item) {
    return waterbutler.buildTreeBeardMetadata(item, {ref: item.data.branch});
}

function _fangornLazyLoadOnLoad (tree, event) {
    var tb = this;
    tree.children.forEach(function(item) {
        Fangorn.Utils.inheritFromParent(item, tree, ['branch']);
    });
    Fangorn.Utils.setCurrentFileID.call(tb, tree, window.contextVars.node.id, window.contextVars.file);
    if(!event && tb.isMultiselected(tb.currentFileID)){
        Fangorn.Utils.scrollToFile.call(tb, tb.currentFileID);
    }
}

function gotoFile (item) {
    var tb = this;
    var fileurl = new URI(item.data.nodeUrl)
        .segment('files')
        .segment(item.data.provider)
        .segment(item.data.path.substring(1))
        .search({branch: item.data.branch})
        .toString();
    if(commandKeys.indexOf(tb.pressedKey) !== -1) {
        window.open(fileurl, '_blank');
    } else {
        window.open(fileurl, '_self');
    }
}

function _fangornGithubTitle(item, col)  {
    var tb = this;
    if (item.data.isAddonRoot && item.connected === false) { // as opposed to undefined, avoids unnecessary setting of this value
        return Fangorn.Utils.connectCheckTemplate.call(this, item);
    }

    if (item.data.addonFullname) {
        var urlParams = $osf.urlParams();

        if (!item.data.branch) {
            if (urlParams.branch && urlParams.branch != item.data.branch) {
                item.data.branch = urlParams.branch;
            }
        }
        var branch = item.data.branch || item.data.defaultBranch;

        return m('span',[
            m('github-name', item.data.name + ' (' + branch + ')')
        ]);
    } else {
        if (item.kind === 'file' && item.data.permissions.view) {
            return m('span',[
                m('github-name.fg-file-links', {
                    onclick: function() {
                        gotoFile.call(tb, item);
                    }
                }, item.data.name)]);
        } else {
            return m('span', item.data.name);
        }
    }
}

function _fangornColumns (item) {
    var tb = this;
    var selectClass = '';
    var node = item.parent().parent();
    var columns = [];
    columns.push({
        data : 'name',
        folderIcons : true,
        filter: true,
        custom : _fangornGithubTitle
    });

    if(tb.options.placement === 'project-files') {
        columns.push(
        {
            data  : 'downloads',
            filter : false,
            css : ''
        });
    }
    return columns;
}

function _fangornFolderIcons(item){
    if(item.data.iconUrl){
        return m('img',{src:item.data.iconUrl, style:{width:'16px', height:'auto'}}, ' ');
    }
    return undefined;
}

function _fangornUploadComplete(item){
    var index = this.returnIndex(item.id);
}

function _fangornUploadSuccess(file, item, response) {
    if (response) {
        response.branch = item.parent().data.branch;
    }
}

// Register configuration
Fangorn.config.github = {
    // Handle changing the branch select
    uploadUrl: _uploadUrl,
    lazyload: _resolveLazyLoad,
    resolveRows: _fangornColumns,
    folderIcon: _fangornFolderIcons,
    onUploadComplete: _fangornUploadComplete,
    lazyLoadOnLoad: _fangornLazyLoadOnLoad,
    uploadSuccess: _fangornUploadSuccess,
    itemButtons: _githubItemButtons,
    removeEvent : _removeEvent
};
<commit_msg>Add "delete" button to file view for GitHub folders [OSF-5732] (#5443)<commit_after>'use strict';
/**
 * Github FileBrowser configuration module.
 */

var m = require('mithril');
var $ = require('jquery');
var URI = require('URIjs');
var Fangorn = require('js/fangorn');
var waterbutler = require('js/waterbutler');
var $osf = require('js/osfHelpers');

// Cross browser key codes for the Command key
var commandKeys = [224, 17, 91, 93];

function _uploadUrl(item, file) {
    return waterbutler.buildTreeBeardUpload(item, file, {branch: item.data.branch});
}

// TODO: Refactor, repeating from core function too much
function _removeEvent (event, items) {
    var tb = this;
    function cancelDelete() {
        tb.modal.dismiss();
    }

    function runDelete (item) {
        tb.select('.tb-modal-footer .text-danger').html(' Deleting...').css('color', 'grey');;
        // delete from server, if successful delete from view
        $.ajax({
            url: waterbutler.buildTreeBeardDelete(item, {branch: item.data.branch, sha: item.data.extra.fileSha}),
            type : 'DELETE',
            beforeSend: $osf.setXHRAuthorization
        }).done(function (data) {
                // delete view
                tb.deleteNode(item.parentID, item.id);
                Fangorn.Utils.dismissToolbar.call(tb);
                tb.modal.dismiss();
                tb.clearMultiselect();

        }).fail(function (data) {
                tb.modal.dismiss();
                Fangorn.Utils.dismissToolbar.call(tb);
                item.notify.update('Delete failed.', 'danger', undefined, 3000);
                tb.clearMultiselect();
        });
    }

    function runDeleteMultiple(items){
        items.forEach(function(item){
            runDelete(item);
        });
    }

    // If there is only one item being deleted, don't complicate the issue:
    if(items.length === 1) {
        var parent = items[0].parent();
        var mithrilContentSingle = m('div', [
            m('h3.break-word', 'Delete "' + items[0].data.name + '"'),
            m('p', 'This action is irreversible.'),
            parent.children.length < 2 ? m('p', 'If a folder in Github has no children it will automatically be removed.') : ''
        ]);
        var mithrilButtonsSingle = m('div', [
            m('span.tb-modal-btn', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
            m('span.tb-modal-btn', { 'class' : 'text-danger', onclick : function() { runDelete(items[0]); }  }, 'Delete')
        ]);
        // This is already being checked before this step but will keep this edit permission check
        if(items[0].data.permissions.edit){
            tb.modal.update(mithrilContentSingle, mithrilButtonsSingle);
        }
    } else {
        // Check if all items can be deleted
        var canDelete = true;
        var deleteList = [];
        var noDeleteList = [];
        var mithrilContentMultiple;
        var mithrilButtonsMultiple;
        items.forEach(function(item, index, arr){
            if(!item.data.permissions.edit){
                canDelete = false;
                noDeleteList.push(item);
            } else {
                deleteList.push(item);
            }
        });
        // If all items can be deleted
        if(canDelete){
            mithrilContentMultiple = m('div', [
                    m('h3.break-word', 'Delete multiple files?'),
                    m('p', 'This action is irreversible.'),
                    deleteList.map(function(item){
                        return m('.fangorn-canDelete.text-success', item.data.name);
                    })
                ]);
            mithrilButtonsMultiple =  m('div', [
                    m('span.tb-modal-btn', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
                    m('span.tb-modal-btn', { 'class' : 'text-danger', onclick : function() { runDeleteMultiple.call(tb, deleteList); }  }, 'Delete All')
                ]);
        } else {
            mithrilContentMultiple = m('div', [
                    m('h3.break-word', 'Delete multiple files?'),
                    m('p', 'Some of these files can\'t be deleted but you can delete the ones highlighted with green. This action is irreversible.'),
                    deleteList.map(function(n){
                        return m('.fangorn-canDelete.text-success', n.data.name);
                    }),
                    noDeleteList.map(function(n){
                        return m('.fangorn-noDelete.text-warning', n.data.name);
                    })
                ]);
            mithrilButtonsMultiple =  m('div', [
                    m('span.tb-modal-btn', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
                    m('span.tb-modal-btn', { 'class' : 'text-danger', onclick : function() { runDeleteMultiple.call(tb, deleteList); }  }, 'Delete Some')
                ]);
        }
        tb.modal.update(mithrilContentMultiple, mithrilButtonsMultiple);
    }

    return true; // Let fangorn know this config option was used.
}

// Define Fangorn Button Actions
var _githubItemButtons = {
    view: function (ctrl, args, children) {
        var tb = args.treebeard;
        var item = args.item;
        var buttons = [];
        function _downloadEvent(event, item, col) {
            event.stopPropagation();
            window.location = waterbutler.buildTreeBeardDownload(item, {fileSha: item.data.extra.fileSha});
        }
        // Download Zip File
        if (item.kind === 'folder') {
            var branchArray = [];
            if (item.data.branches) {
                item.data.branch = item.data.branch || item.data.defaultBranch;
                for (var i = 0; i < item.data.branches.length; i++) {
                    var selected = item.data.branches[i] === item.data.branch ? 'selected' : '';
                    branchArray.push(m('option', {
                        selected: selected,
                        value: item.data.branches[i]
                    }, item.data.branches[i]));
                }
            }
            if (item.data.addonFullname) {
                buttons.push(
                    m.component(Fangorn.Components.dropdown, {
                        'label': 'Branch: ',
                        onchange: function (event) {
                            changeBranch.call(tb, item, event.target.value);
                        },
                        icon: 'fa fa-external-link',
                        className: 'text-info'
                    }, branchArray)
                );
            }
            if (tb.options.placement !== 'fileview') {
                // If File and FileRead are not defined dropzone is not supported and neither is uploads
                if (window.File && window.FileReader && item.data.permissions && item.data.permissions.edit) {
                    buttons.push(
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                Fangorn.ButtonEvents._uploadEvent.call(tb, event, item);
                            },
                            icon: 'fa fa-upload',
                            className: 'text-success'
                        }, 'Upload'),
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                tb.toolbarMode(Fangorn.Components.toolbarModes.ADDFOLDER);
                            },
                            icon: 'fa fa-plus',
                            className: 'text-success'
                        }, 'Create Folder'),
                        m.component(Fangorn.Components.button, {
                           onclick: function (event) {
                               _removeEvent.call(tb, event, [item]);
                           },
                           icon: 'fa fa-trash',
                           className: 'text-danger'
                       }, 'Delete Folder')
                    );
                }
                if (item.data.addonFullname) {
                    buttons.push(
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                window.location = item.data.urls.zip;
                            },
                            icon: 'fa fa-download',
                            className: 'text-primary'
                        }, 'Download'),
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                window.open(item.data.urls.repo, '_blank');
                            },
                            icon: 'fa fa-external-link',
                            className: 'text-info'
                        }, 'Open')
                    );
                }
            }
        } else if (item.kind === 'file' && tb.options.placement !== 'fileview') {
            buttons.push(
                m.component(Fangorn.Components.button, {
                    onclick: function (event) {
                        _downloadEvent.call(tb, event, item);
                    },
                    icon: 'fa fa-download',
                    className: 'text-primary'
                }, 'Download')
            );
            if (item.data.permissions && item.data.permissions.view) {
                buttons.push(
                    m.component(Fangorn.Components.button, {
                        onclick: function(event) {
                            gotoFile.call(tb, item);
                        },
                        icon: 'fa fa-file-o',
                        className : 'text-info'
                    }, 'View'));
            }
            if (item.data.permissions && item.data.permissions.edit) {
                buttons.push(
                    m.component(Fangorn.Components.button, {
                        onclick: function (event) {
                            _removeEvent.call(tb, event, [item]);
                        },
                        icon: 'fa fa-trash',
                        className: 'text-danger'
                    }, 'Delete')
                );
            }
            if (item.data.permissions && item.data.permissions.view && !item.data.permissions.private) {
                buttons.push(
                    m('a.text-info.fangorn-toolbar-icon', {href: item.data.extra.webView}, [
                        m('i.fa.fa-external-link'),
                        m('span', 'View on GitHub')
                    ])
                );
            }
        }

        if(item.data.provider && !item.data.isAddonRoot && item.data.permissions && item.data.permissions.edit && tb.options.placement !== 'fileview') {
            buttons.push(
                m.component(Fangorn.Components.button, {
                    onclick: function() {
                        tb.toolbarMode(Fangorn.Components.toolbarModes.RENAME);
                    },
                    tooltip: 'Change the name of the item',
                    icon: 'fa fa-font',
                    className : 'text-info'
                }, 'Rename')
            );
        }

        return m('span', buttons); // Tell fangorn this function is used.
    }
};

function changeBranch(item, ref){
    item.data.branch = ref;
    this.updateFolder(null, item);
}

function _resolveLazyLoad(item) {
    return waterbutler.buildTreeBeardMetadata(item, {ref: item.data.branch});
}

function _fangornLazyLoadOnLoad (tree, event) {
    var tb = this;
    tree.children.forEach(function(item) {
        Fangorn.Utils.inheritFromParent(item, tree, ['branch']);
    });
    Fangorn.Utils.setCurrentFileID.call(tb, tree, window.contextVars.node.id, window.contextVars.file);
    if(!event && tb.isMultiselected(tb.currentFileID)){
        Fangorn.Utils.scrollToFile.call(tb, tb.currentFileID);
    }
}

function gotoFile (item) {
    var tb = this;
    var fileurl = new URI(item.data.nodeUrl)
        .segment('files')
        .segment(item.data.provider)
        .segment(item.data.path.substring(1))
        .search({branch: item.data.branch})
        .toString();
    if(commandKeys.indexOf(tb.pressedKey) !== -1) {
        window.open(fileurl, '_blank');
    } else {
        window.open(fileurl, '_self');
    }
}

function _fangornGithubTitle(item, col)  {
    var tb = this;
    if (item.data.isAddonRoot && item.connected === false) { // as opposed to undefined, avoids unnecessary setting of this value
        return Fangorn.Utils.connectCheckTemplate.call(this, item);
    }

    if (item.data.addonFullname) {
        var urlParams = $osf.urlParams();

        if (!item.data.branch) {
            if (urlParams.branch && urlParams.branch != item.data.branch) {
                item.data.branch = urlParams.branch;
            }
        }
        var branch = item.data.branch || item.data.defaultBranch;

        return m('span',[
            m('github-name', item.data.name + ' (' + branch + ')')
        ]);
    } else {
        if (item.kind === 'file' && item.data.permissions.view) {
            return m('span',[
                m('github-name.fg-file-links', {
                    onclick: function() {
                        gotoFile.call(tb, item);
                    }
                }, item.data.name)]);
        } else {
            return m('span', item.data.name);
        }
    }
}

function _fangornColumns (item) {
    var tb = this;
    var selectClass = '';
    var node = item.parent().parent();
    var columns = [];
    columns.push({
        data : 'name',
        folderIcons : true,
        filter: true,
        custom : _fangornGithubTitle
    });

    if(tb.options.placement === 'project-files') {
        columns.push(
        {
            data  : 'downloads',
            filter : false,
            css : ''
        });
    }
    return columns;
}

function _fangornFolderIcons(item){
    if(item.data.iconUrl){
        return m('img',{src:item.data.iconUrl, style:{width:'16px', height:'auto'}}, ' ');
    }
    return undefined;
}

function _fangornUploadComplete(item){
    var index = this.returnIndex(item.id);
}

function _fangornUploadSuccess(file, item, response) {
    if (response) {
        response.branch = item.parent().data.branch;
    }
}

// Register configuration
Fangorn.config.github = {
    // Handle changing the branch select
    uploadUrl: _uploadUrl,
    lazyload: _resolveLazyLoad,
    resolveRows: _fangornColumns,
    folderIcon: _fangornFolderIcons,
    onUploadComplete: _fangornUploadComplete,
    lazyLoadOnLoad: _fangornLazyLoadOnLoad,
    uploadSuccess: _fangornUploadSuccess,
    itemButtons: _githubItemButtons,
    removeEvent : _removeEvent
};
<|endoftext|>


<commit_before>// @flow
/**
 * search for dom elements on your page
 * @module holmes
 */
(function (root, factory) {
  'use strict';

  /* $FlowIssue - doesn't work with umd */
  if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    // AMD. Register as an anonymous module.
    define([], function () { // eslint-disable-line no-undef
      /* $FlowIssue - doesn't work with umd */
      return (root.holmes = factory(document)); // eslint-disable-line no-undef, no-return-assign
    });
  } else if (typeof exports === 'object') { // eslint-disable-line no-undef
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = factory(document); // eslint-disable-line no-undef
  } else {
    // Browser globals
    /* $FlowIssue - doesn't work with umd */
    root.holmes = factory(document);
  }
})(this, (function (document) {
  // UMD Definition above, do not remove this line

  // To get to know more about the Universal Module Definition
  // visit: https://github.com/umdjs/umd

  'use strict';

  /**
   * search for dom elements on your page
   * @alias module:holmes
   * @constructor
   * @param {string} [options.input='input[type=search]']
   *   A querySelector to find the input
   * @param {string} options.find
   *   A querySelectorAll rule to find each of the find terms
   * @param {string=} options.placeholder
   *   Text to show when there are no results (innerHTML)
   * @param {bool} [options.mark=false]
   *   Whether to <mark></mark> the matching text
   *   Text to show when there are no results (innerHTML)
   * @param {string} [options.class.visible=false]
   *   class to add to matched items
   * @param {string} [options.class.hidden='hidden']
   *   class to add to non-matched items
   * @param {boolean} [options.dynamic=false]
   *   Whether to query for the content of the elements on every input.
   *   If this is false, then only when initializing the script will
   *   fetch the content of the elements to search in. If this is true
   *   then it will refresh on every input event.
   * @param {boolean} [options.contenteditable=false]
   *   DEPRECATED (now handled automatically) whether the input is a contenteditable or
   *   not. By default it's assumed that it's <input>, true here
   *   will use <div contenteditable>
   * @param {boolean} [options.instant=false]
   *   By default Holmes waits for the DOMContentLoaded event to fire
   *   before listening. This is to make sure that all content is available. However
   *   if you exactly know when all your content is available (ajax, your own event or
   *   other situations), you can put this option on true.
   * @param {number} [options.minCharacters=0] The minimum amount of characters to be typed before
   *   Holmes starts searching. Beware that this also counts when backspacing.
   * @param {onChange} [options.onHidden]
   *   Callback for when an item is hidden.
   * @param {onChange} [options.onVisible]
   *   Callback for when an item is visible again.
   * @param {onChange} [options.onEmpty]
   *   Callback for when no items were found.
   * @param {onChange} [options.onFound]
   *   Callback for when items are found after being empty.
   * @param {function} [options.onInput]
   *   Callback for every input.
   */
  function holmes(options) {
    var empty = false;

    if (typeof options !== 'object') {
      throw new Error('The options need to be given inside an object like this:\nholmes({\n\tfind:".result"\n});\nsee also https://haroen.me/holmes/doc/module-holmes.html');
    }

    // if holmes.prototype.options.find is missing, the searching won't work so we'll thrown an exceptions
    if (typeof options.find !== 'string') {
      throw new Error('A find argument is needed. That should be a querySelectorAll for each of the items you want to match individually. You should have something like: \nholmes({\n\tfind:".result"\n});\nsee also https://haroen.me/holmes/doc/module-holmes.html');
    }

    /**
     * Options
     * @type {Object}
     */
    holmes.prototype.options = {
      input: 'input[type=search]',
      placeholder: '',
      placeholderNode: null,
      mark: false,
      class: {
        visible: '',
        hidden: 'hidden'
      },
      dynamic: false,
      instant: false,
      minCharacters: 0,
      find: ''
    };

    /**
     * Merges two objects
     * @param  {Object} Obj1 Object to merge
     * @param  {Object} Obj2 Object to merge
     */
    holmes.prototype._mergeObj = function (Obj1, Obj2) {
      if (!(Obj1 instanceof Object) || !(Obj2 instanceof Object)) {
        throw new Error('One of both arguments isn\'t an object.');
      }
      Object.keys(Obj1).forEach(function (k) {
        if (typeof Obj2[k] === typeof Obj1[k]) {
          if (Obj2[k] instanceof Object) {
            holmes.prototype._mergeObj(Obj1[k], Obj2[k]);
          } else {
            Obj1[k] = Obj2[k];
          }
        }
      });
    };

    // set default options
    holmes.prototype._mergeObj(holmes.prototype.options, options);

    /**
     * Start an event listener with the specified options
     */
    holmes.prototype.start = function () {
      holmes.prototype.running = true;

      /**
       * The input element
       * @type {NodeList}
       */
      holmes.prototype.input = document.querySelector(holmes.prototype.options.input);

      /**
       * All of the elements that are searched
       * @type {NodeList}
       */
      holmes.prototype.elements = document.querySelectorAll(holmes.prototype.options.find);

      /**
       * amount of elements to search
       * @type {Number}
       */
      holmes.prototype.elementsLength = holmes.prototype.elements.length;

      /**
       * all of the elements that are searched as an array
       * @type {Array}
       */
      holmes.prototype.elementsArray = Array.prototype.slice.call(holmes.prototype.elements);

      /**
       * The amount of elements that are hidden
       * @type {Number}
       */
      holmes.prototype.hidden = 0;

      // create a container for a placeholder if needed
      if (holmes.prototype.options.placeholder !== '') {
        /**
         * Placeholder element
         * @type {Element}
         */
        holmes.prototype.placeholderNode = document.createElement('div');
        holmes.prototype.placeholderNode.id = 'holmes-placeholder';
        holmes.prototype.placeholderNode.classList.add(holmes.prototype.options.class.hidden);
        holmes.prototype.placeholderNode.innerHTML = holmes.prototype.options.placeholder;
        if (holmes.prototype.elements[0].parentNode instanceof Element) {
          holmes.prototype.elements[0].parentNode.appendChild(holmes.prototype.placeholderNode);
        } else {
          throw new Error('The Holmes placeholder could\'t be put; the elements had no parent.');
        }
      }

      // if a visible class is given, give it to everything
      if (holmes.prototype.options.class.visible) {
        holmes.prototype.elementsArray.forEach((function (element) {
          element.classList.add(holmes.prototype.options.class.visible);
        }));
      }

      // listen for input
      holmes.prototype.input.addEventListener('input', inputHandler);
    };

    /**
     * The current search input in lower case
     * @return {String} the input as a string
     */
    function inputString() {
      if (holmes.prototype.input instanceof HTMLInputElement) {
        return holmes.prototype.input.value.toLowerCase();
      }
      if (holmes.prototype.input.contentEditable) {
        return holmes.prototype.input.textContent.toLowerCase();
      }
      throw new Error('The Holmes input was no <input> or contenteditable.');
    }

    /**
     * hide an element
     * @param  {HTMLElement} element the element to hide
     */
    function hideElement(element) {
      if (holmes.prototype.options.class.visible) {
        element.classList.remove(holmes.prototype.options.class.visible);
      }
      if (!element.classList.contains(holmes.prototype.options.class.hidden)) {
        element.classList.add(holmes.prototype.options.class.hidden);
        holmes.prototype.hidden++;

        if (typeof holmes.prototype.options.onHidden === 'function') {
          holmes.prototype.options.onHidden(element);
        }
      }
      if (holmes.prototype.options.mark) {
        element.innerHTML = element.innerHTML.replace(/<\/?mark>/g, '');
      }
    }

    /**
     * show an element
     * @param  {HTMLElement} element the element to show
     */
    function showElement(element) {
      if (holmes.prototype.options.class.visible) {
        element.classList.add(holmes.prototype.options.class.visible);
      }
      if (element.classList.contains(holmes.prototype.options.class.hidden)) {
        element.classList.remove(holmes.prototype.options.class.hidden);
        holmes.prototype.hidden--;

        if (typeof holmes.prototype.options.onVisible === 'function') {
          holmes.prototype.options.onVisible(element);
        }
      }

      // if we need to mark it:
      // remove all  tags
      // add new  tags around the text
      if (holmes.prototype.options.mark) {
        element.innerHTML = element.innerHTML.replace(/<\/?mark>/g, '');
        if (holmes.prototype.searchString.length) {
          element.innerHTML = element.innerHTML.replace(holmes.prototype.regex, '$1');
        }
      }
    }

    /**
     * input event handler
     */
    function inputHandler() {
      // by default the value isn't found
      var found = false;

      /**
       * Lowercase string holmes searces for
       * @type {string}
       */
      holmes.prototype.searchString = inputString();

      // if a minimum of characters is required
      // check if that limit has been reached
      if (holmes.prototype.options.minCharacters) {
        if (holmes.prototype.searchString.length !== 0) {
          if (holmes.prototype.options.minCharacters > holmes.prototype.searchString.length) {
            return;
          }
        }
      }

      // if the dynamic option is enabled, then we should query
      // for the contents of `elements` on every input
      if (holmes.prototype.options.dynamic) {
        holmes.prototype.elements = document.querySelectorAll(holmes.prototype.options.find);
        holmes.prototype.elementsLength = holmes.prototype.elements.length;
        holmes.prototype.elementsArray = Array.prototype.slice.call(holmes.prototype.elements);
      }

      // loop over all the elements
      // in case this should become dynamic, query for the elements here
      if (holmes.prototype.options.mark) {
        holmes.prototype.regex = new RegExp('(' + holmes.prototype.searchString + ')(?![^<]*>)', 'gi');
      }

      holmes.prototype.elementsArray.forEach((function (element) {
        // if the current element doesn't contain the search string
        // add the hidden class and remove the visbible class
        if (element.textContent.toLowerCase().indexOf(holmes.prototype.searchString) === -1) {
          hideElement(element);
        } else {
          showElement(element);

          if (empty && typeof holmes.prototype.options.onFound === 'function') {
            holmes.prototype.options.onFound(holmes.prototype.placeholderNode);
          }
          empty = false;
          // the element is now found at least once
          found = true;
        }
      }));

      if (typeof holmes.prototype.options.onInput === 'function') {
        holmes.prototype.options.onInput(holmes.prototype.searchString);
      }

      // No results were found and last time we checked it wasn't empty
      if (!found && !empty) {
        empty = true;

        if (holmes.prototype.options.placeholder !== '') {
          holmes.prototype.placeholderNode.classList.remove(holmes.prototype.options.class.hidden);
        }
        if (typeof holmes.prototype.options.onEmpty === 'function') {
          holmes.prototype.options.onEmpty(holmes.prototype.placeholderNode);
        }
      } else if (!empty) {
        if (holmes.prototype.options.placeholder !== '') {
          holmes.prototype.placeholderNode.classList.add(holmes.prototype.options.class.hidden);
        }
      }
    }

    // whether to start immediately or wait on the load of DOMContent
    if (typeof holmes.prototype.options.instant !== 'boolean') {
      holmes.prototype.options.instant = false;
    }

    if (holmes.prototype.options.instant) {
      holmes.prototype.start(options);
    } else {
      window.addEventListener('DOMContentLoaded', function () {
        holmes.prototype.start(options);
      });
    }

    /**
     * remove the current event listener
     * @see holmes.prototype.start
     * @return {Promise} resolves when the event is removed
     */
    holmes.prototype.stop = function () {
      return new Promise(function (resolve, reject) {
        try {
          holmes.prototype.input.removeEventListener('input', inputHandler);

          // remove placeholderNode
          if (holmes.prototype.placeholderNode.parentNode) {
            holmes.prototype.placeholderNode.parentNode.removeChild(holmes.prototype.placeholderNode);
          } else {
            throw new Error('The Holmes placeholderNode has no parent.');
          }

          // remove marks
          if (holmes.prototype.options.mark) {
            holmes.prototype.elementsArray.forEach(function (element) {
              element.innerHTML = element.innerHTML.replace(/<\/?mark>/g, '');
            });
          }

          // done
          holmes.prototype.running = false;
          resolve('This instance of Holmes has been stopped.');
        } catch (err) {
          reject(err);
        }
      });
    };

    /**
     * empty the search string programmatically.
     * This avoids having to send a new `input` event
     */
    holmes.prototype.clear = function () {
      if (holmes.prototype.input instanceof HTMLInputElement) {
        holmes.prototype.input.value = '';
      } else if (holmes.prototype.input.contentEditable) {
        holmes.prototype.input.textContent = '';
      } else {
        throw new Error('The Holmes input was no <input> or contenteditable.');
      }
      // if a visible class is given, give it to everything
      if (holmes.prototype.options.class.visible) {
        holmes.prototype.elementsArray.forEach(function (element) {
          element.classList.remove(holmes.prototype.options.class.hidden);
          element.classList.add(holmes.prototype.options.class.visible);
        });
      } else {
        holmes.prototype.elementsArray.forEach(function (element) {
          element.classList.remove(holmes.prototype.options.class.hidden);
        });
      }
      if (holmes.prototype.options.placeholder !== '') {
        holmes.prototype.placeholderNode.classList.add(holmes.prototype.options.class.hidden);
        if (holmes.prototype.options.class.visible) {
          holmes.prototype.placeholderNode.classList.remove(holmes.prototype.options.class.visible);
        }
      }

      holmes.prototype.hidden = 0;
    };

    /**
     * Show the amount of elements, and those hidden and visible
     * @return {object} all matching elements, the amount of hidden and the amount of visible elements
     */
    holmes.prototype.count = function () {
      return {
        all: holmes.prototype.elementsLength,
        hidden: holmes.prototype.hidden,
        visible: holmes.prototype.elementsLength - holmes.prototype.hidden
      };
    };
  }

  /**
   * Callback used for changes in item en list states.
   * @callback onChange
   * @param {object} [element]
   *   Element affected by the event. This is the item found by
   *   onVisible and onHidden and the placeholder
   *   (or undefined) for onEmpty and
   *   onFound.
   */

  return holmes;
}));
<commit_msg>placeholderNode isn't needed in the options object, it's in the prototype<commit_after>// @flow
/**
 * search for dom elements on your page
 * @module holmes
 */
(function (root, factory) {
  'use strict';

  /* $FlowIssue - doesn't work with umd */
  if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    // AMD. Register as an anonymous module.
    define([], function () { // eslint-disable-line no-undef
      /* $FlowIssue - doesn't work with umd */
      return (root.holmes = factory(document)); // eslint-disable-line no-undef, no-return-assign
    });
  } else if (typeof exports === 'object') { // eslint-disable-line no-undef
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = factory(document); // eslint-disable-line no-undef
  } else {
    // Browser globals
    /* $FlowIssue - doesn't work with umd */
    root.holmes = factory(document);
  }
})(this, (function (document) {
  // UMD Definition above, do not remove this line

  // To get to know more about the Universal Module Definition
  // visit: https://github.com/umdjs/umd

  'use strict';

  /**
   * search for dom elements on your page
   * @alias module:holmes
   * @constructor
   * @param {string} [options.input='input[type=search]']
   *   A querySelector to find the input
   * @param {string} options.find
   *   A querySelectorAll rule to find each of the find terms
   * @param {string=} options.placeholder
   *   Text to show when there are no results (innerHTML)
   * @param {bool} [options.mark=false]
   *   Whether to <mark></mark> the matching text
   *   Text to show when there are no results (innerHTML)
   * @param {string} [options.class.visible=false]
   *   class to add to matched items
   * @param {string} [options.class.hidden='hidden']
   *   class to add to non-matched items
   * @param {boolean} [options.dynamic=false]
   *   Whether to query for the content of the elements on every input.
   *   If this is false, then only when initializing the script will
   *   fetch the content of the elements to search in. If this is true
   *   then it will refresh on every input event.
   * @param {boolean} [options.contenteditable=false]
   *   DEPRECATED (now handled automatically) whether the input is a contenteditable or
   *   not. By default it's assumed that it's <input>, true here
   *   will use <div contenteditable>
   * @param {boolean} [options.instant=false]
   *   By default Holmes waits for the DOMContentLoaded event to fire
   *   before listening. This is to make sure that all content is available. However
   *   if you exactly know when all your content is available (ajax, your own event or
   *   other situations), you can put this option on true.
   * @param {number} [options.minCharacters=0] The minimum amount of characters to be typed before
   *   Holmes starts searching. Beware that this also counts when backspacing.
   * @param {onChange} [options.onHidden]
   *   Callback for when an item is hidden.
   * @param {onChange} [options.onVisible]
   *   Callback for when an item is visible again.
   * @param {onChange} [options.onEmpty]
   *   Callback for when no items were found.
   * @param {onChange} [options.onFound]
   *   Callback for when items are found after being empty.
   * @param {function} [options.onInput]
   *   Callback for every input.
   */
  function holmes(options) {
    var empty = false;

    if (typeof options !== 'object') {
      throw new Error('The options need to be given inside an object like this:\nholmes({\n\tfind:".result"\n});\nsee also https://haroen.me/holmes/doc/module-holmes.html');
    }

    // if holmes.prototype.options.find is missing, the searching won't work so we'll thrown an exceptions
    if (typeof options.find !== 'string') {
      throw new Error('A find argument is needed. That should be a querySelectorAll for each of the items you want to match individually. You should have something like: \nholmes({\n\tfind:".result"\n});\nsee also https://haroen.me/holmes/doc/module-holmes.html');
    }

    /**
     * Options
     * @type {Object}
     */
    holmes.prototype.options = {
      input: 'input[type=search]',
      placeholder: '',
      mark: false,
      class: {
        visible: '',
        hidden: 'hidden'
      },
      dynamic: false,
      instant: false,
      minCharacters: 0,
      find: ''
    };

    /**
     * Merges two objects
     * @param  {Object} Obj1 Object to merge
     * @param  {Object} Obj2 Object to merge
     */
    holmes.prototype._mergeObj = function (Obj1, Obj2) {
      if (!(Obj1 instanceof Object) || !(Obj2 instanceof Object)) {
        throw new Error('One of both arguments isn\'t an object.');
      }
      Object.keys(Obj1).forEach(function (k) {
        if (typeof Obj2[k] === typeof Obj1[k]) {
          if (Obj2[k] instanceof Object) {
            holmes.prototype._mergeObj(Obj1[k], Obj2[k]);
          } else {
            Obj1[k] = Obj2[k];
          }
        }
      });
    };

    // set default options
    holmes.prototype._mergeObj(holmes.prototype.options, options);

    /**
     * Start an event listener with the specified options
     */
    holmes.prototype.start = function () {
      holmes.prototype.running = true;

      /**
       * The input element
       * @type {NodeList}
       */
      holmes.prototype.input = document.querySelector(holmes.prototype.options.input);

      /**
       * All of the elements that are searched
       * @type {NodeList}
       */
      holmes.prototype.elements = document.querySelectorAll(holmes.prototype.options.find);

      /**
       * amount of elements to search
       * @type {Number}
       */
      holmes.prototype.elementsLength = holmes.prototype.elements.length;

      /**
       * all of the elements that are searched as an array
       * @type {Array}
       */
      holmes.prototype.elementsArray = Array.prototype.slice.call(holmes.prototype.elements);

      /**
       * The amount of elements that are hidden
       * @type {Number}
       */
      holmes.prototype.hidden = 0;

      // create a container for a placeholder if needed
      if (holmes.prototype.options.placeholder !== '') {
        /**
         * Placeholder element
         * @type {Element}
         */
        holmes.prototype.placeholderNode = document.createElement('div');
        holmes.prototype.placeholderNode.id = 'holmes-placeholder';
        holmes.prototype.placeholderNode.classList.add(holmes.prototype.options.class.hidden);
        holmes.prototype.placeholderNode.innerHTML = holmes.prototype.options.placeholder;
        if (holmes.prototype.elements[0].parentNode instanceof Element) {
          holmes.prototype.elements[0].parentNode.appendChild(holmes.prototype.placeholderNode);
        } else {
          throw new Error('The Holmes placeholder could\'t be put; the elements had no parent.');
        }
      }

      // if a visible class is given, give it to everything
      if (holmes.prototype.options.class.visible) {
        holmes.prototype.elementsArray.forEach((function (element) {
          element.classList.add(holmes.prototype.options.class.visible);
        }));
      }

      // listen for input
      holmes.prototype.input.addEventListener('input', inputHandler);
    };

    /**
     * The current search input in lower case
     * @return {String} the input as a string
     */
    function inputString() {
      if (holmes.prototype.input instanceof HTMLInputElement) {
        return holmes.prototype.input.value.toLowerCase();
      }
      if (holmes.prototype.input.contentEditable) {
        return holmes.prototype.input.textContent.toLowerCase();
      }
      throw new Error('The Holmes input was no <input> or contenteditable.');
    }

    /**
     * hide an element
     * @param  {HTMLElement} element the element to hide
     */
    function hideElement(element) {
      if (holmes.prototype.options.class.visible) {
        element.classList.remove(holmes.prototype.options.class.visible);
      }
      if (!element.classList.contains(holmes.prototype.options.class.hidden)) {
        element.classList.add(holmes.prototype.options.class.hidden);
        holmes.prototype.hidden++;

        if (typeof holmes.prototype.options.onHidden === 'function') {
          holmes.prototype.options.onHidden(element);
        }
      }
      if (holmes.prototype.options.mark) {
        element.innerHTML = element.innerHTML.replace(/<\/?mark>/g, '');
      }
    }

    /**
     * show an element
     * @param  {HTMLElement} element the element to show
     */
    function showElement(element) {
      if (holmes.prototype.options.class.visible) {
        element.classList.add(holmes.prototype.options.class.visible);
      }
      if (element.classList.contains(holmes.prototype.options.class.hidden)) {
        element.classList.remove(holmes.prototype.options.class.hidden);
        holmes.prototype.hidden--;

        if (typeof holmes.prototype.options.onVisible === 'function') {
          holmes.prototype.options.onVisible(element);
        }
      }

      // if we need to mark it:
      // remove all  tags
      // add new  tags around the text
      if (holmes.prototype.options.mark) {
        element.innerHTML = element.innerHTML.replace(/<\/?mark>/g, '');
        if (holmes.prototype.searchString.length) {
          element.innerHTML = element.innerHTML.replace(holmes.prototype.regex, '$1');
        }
      }
    }

    /**
     * input event handler
     */
    function inputHandler() {
      // by default the value isn't found
      var found = false;

      /**
       * Lowercase string holmes searces for
       * @type {string}
       */
      holmes.prototype.searchString = inputString();

      // if a minimum of characters is required
      // check if that limit has been reached
      if (holmes.prototype.options.minCharacters) {
        if (holmes.prototype.searchString.length !== 0) {
          if (holmes.prototype.options.minCharacters > holmes.prototype.searchString.length) {
            return;
          }
        }
      }

      // if the dynamic option is enabled, then we should query
      // for the contents of `elements` on every input
      if (holmes.prototype.options.dynamic) {
        holmes.prototype.elements = document.querySelectorAll(holmes.prototype.options.find);
        holmes.prototype.elementsLength = holmes.prototype.elements.length;
        holmes.prototype.elementsArray = Array.prototype.slice.call(holmes.prototype.elements);
      }

      // loop over all the elements
      // in case this should become dynamic, query for the elements here
      if (holmes.prototype.options.mark) {
        holmes.prototype.regex = new RegExp('(' + holmes.prototype.searchString + ')(?![^<]*>)', 'gi');
      }

      holmes.prototype.elementsArray.forEach((function (element) {
        // if the current element doesn't contain the search string
        // add the hidden class and remove the visbible class
        if (element.textContent.toLowerCase().indexOf(holmes.prototype.searchString) === -1) {
          hideElement(element);
        } else {
          showElement(element);

          if (empty && typeof holmes.prototype.options.onFound === 'function') {
            holmes.prototype.options.onFound(holmes.prototype.placeholderNode);
          }
          empty = false;
          // the element is now found at least once
          found = true;
        }
      }));

      if (typeof holmes.prototype.options.onInput === 'function') {
        holmes.prototype.options.onInput(holmes.prototype.searchString);
      }

      // No results were found and last time we checked it wasn't empty
      if (!found && !empty) {
        empty = true;

        if (holmes.prototype.options.placeholder !== '') {
          holmes.prototype.placeholderNode.classList.remove(holmes.prototype.options.class.hidden);
        }
        if (typeof holmes.prototype.options.onEmpty === 'function') {
          holmes.prototype.options.onEmpty(holmes.prototype.placeholderNode);
        }
      } else if (!empty) {
        if (holmes.prototype.options.placeholder !== '') {
          holmes.prototype.placeholderNode.classList.add(holmes.prototype.options.class.hidden);
        }
      }
    }

    // whether to start immediately or wait on the load of DOMContent
    if (typeof holmes.prototype.options.instant !== 'boolean') {
      holmes.prototype.options.instant = false;
    }

    if (holmes.prototype.options.instant) {
      holmes.prototype.start(options);
    } else {
      window.addEventListener('DOMContentLoaded', function () {
        holmes.prototype.start(options);
      });
    }

    /**
     * remove the current event listener
     * @see holmes.prototype.start
     * @return {Promise} resolves when the event is removed
     */
    holmes.prototype.stop = function () {
      return new Promise(function (resolve, reject) {
        try {
          holmes.prototype.input.removeEventListener('input', inputHandler);

          // remove placeholderNode
          if (holmes.prototype.placeholderNode.parentNode) {
            holmes.prototype.placeholderNode.parentNode.removeChild(holmes.prototype.placeholderNode);
          } else {
            throw new Error('The Holmes placeholderNode has no parent.');
          }

          // remove marks
          if (holmes.prototype.options.mark) {
            holmes.prototype.elementsArray.forEach(function (element) {
              element.innerHTML = element.innerHTML.replace(/<\/?mark>/g, '');
            });
          }

          // done
          holmes.prototype.running = false;
          resolve('This instance of Holmes has been stopped.');
        } catch (err) {
          reject(err);
        }
      });
    };

    /**
     * empty the search string programmatically.
     * This avoids having to send a new `input` event
     */
    holmes.prototype.clear = function () {
      if (holmes.prototype.input instanceof HTMLInputElement) {
        holmes.prototype.input.value = '';
      } else if (holmes.prototype.input.contentEditable) {
        holmes.prototype.input.textContent = '';
      } else {
        throw new Error('The Holmes input was no <input> or contenteditable.');
      }
      // if a visible class is given, give it to everything
      if (holmes.prototype.options.class.visible) {
        holmes.prototype.elementsArray.forEach(function (element) {
          element.classList.remove(holmes.prototype.options.class.hidden);
          element.classList.add(holmes.prototype.options.class.visible);
        });
      } else {
        holmes.prototype.elementsArray.forEach(function (element) {
          element.classList.remove(holmes.prototype.options.class.hidden);
        });
      }
      if (holmes.prototype.options.placeholder !== '') {
        holmes.prototype.placeholderNode.classList.add(holmes.prototype.options.class.hidden);
        if (holmes.prototype.options.class.visible) {
          holmes.prototype.placeholderNode.classList.remove(holmes.prototype.options.class.visible);
        }
      }

      holmes.prototype.hidden = 0;
    };

    /**
     * Show the amount of elements, and those hidden and visible
     * @return {object} all matching elements, the amount of hidden and the amount of visible elements
     */
    holmes.prototype.count = function () {
      return {
        all: holmes.prototype.elementsLength,
        hidden: holmes.prototype.hidden,
        visible: holmes.prototype.elementsLength - holmes.prototype.hidden
      };
    };
  }

  /**
   * Callback used for changes in item en list states.
   * @callback onChange
   * @param {object} [element]
   *   Element affected by the event. This is the item found by
   *   onVisible and onHidden and the placeholder
   *   (or undefined) for onEmpty and
   *   onFound.
   */

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