<commit_before>// Provides tasks to spawn and kill subprocesses needed for todos app.
// See also: javascript/api/grunt_tasks/integration_test_environment_setup.js

var assert = require('assert');
var fs = require('fs');
var spawn = require('child_process').spawn;

module.exports = function(grunt) {
  var subprocesses = [];  // List of subprocesses we've spawned.

  // Kills all subprocesses we've spawned.
  var tearDown = function() {
    subprocesses.forEach(function(p) {
      p.kill('SIGTERM');
    });
  };

  // Calls tearDown, then fails the current task.
  var fail = function(msg) {
    tearDown();
    grunt.fail.fatal(msg);
    done(false);
  };

  // Starts all subprocesses and sets some grunt.config vars.
  grunt.registerTask('subtask_spawnSubprocesses', function() {
    // Tell grunt that this is an async task.
    // Note, we never actually call done() -- see comment below.
    var done = this.async();

    // Define constants.
    var c = grunt.constants;
    var VEYRON_IDENTITY_BIN = c.VEYRON_BIN_DIR + '/identityd';
    var VEYRON_PROXY_BIN = c.VEYRON_BIN_DIR + '/proxy';
    var VEYRON_WSPR_BIN = c.VEYRON_BIN_DIR + '/wsprd';
    var STORE_BIN = c.VEYRON_BIN_DIR + '/todos_stored';
    var VEYRON_PROXY_ADDR = '127.0.0.1:' + c.VEYRON_PROXY_PORT;

    // Ensure binaries exist.
    var bins = [VEYRON_IDENTITY_BIN, VEYRON_PROXY_BIN, VEYRON_WSPR_BIN];
    bins.forEach(function(bin) {
      if (!fs.existsSync(bin)) {
        fail('Missing binary: ' + bin);
      }
    });

    // Start subprocesses.
    var startSubprocess = function(command, args) {
      var p = spawn(command, args);
      p.stderr.on('data', grunt.log.debug);
      subprocesses.push(p);
      return p;
    };
    var veyronIdentitySubprocess = startSubprocess(
        VEYRON_IDENTITY_BIN,
        ['-port=' + c.VEYRON_IDENTITY_PORT]);
    var veyronProxySubprocess = startSubprocess(
        VEYRON_PROXY_BIN,
        ['-log_dir=' + c.LOG_DIR, '-addr=' + VEYRON_PROXY_ADDR]);
    var veyronWsprSubprocess = startSubprocess(
        VEYRON_WSPR_BIN,
        ['-v=3', '-log_dir=' + c.LOG_DIR, '-port=' + c.VEYRON_WSPR_PORT,
         '-vproxy=' + VEYRON_PROXY_ADDR]);
    var storeSubprocess = startSubprocess(
        STORE_BIN, ['-log_dir=' + c.LOG_DIR]);

    // Capture todos_stored's endpoint, then write env.js file.
    storeSubprocess.stdout.on('data', function(data) {
      var endpoint;
      data.toString().split('\n').some(function(line) {
        line = line.replace('Endpoint: ', '').trim();
        if (line.match(/^@.*@$/)) {
          endpoint = line;
          return true;
        }
      });
      assert(endpoint, 'Failed to extract store endpoint');

      // Write JS file that sets env vars for client-side JS.
      var env = {
        VEYRON_WSPR_SERVER_URL: 'http://localhost:' + c.VEYRON_WSPR_PORT,
        STORE_ENDPOINT: endpoint
      };
      console.log(JSON.stringify(env, null, 2));
      var code = 'var env = JSON.parse(\'' + JSON.stringify(env) + '\');';
      // TODO(sadovsky): This is super hacky -- we shouldn't be writing files
      // into the webserver's dir. It'd be better to communicate these vars to
      // todos_appd via env vars or flags.
      grunt.file.write('todos_appd/js/env.js', code);

      console.log('Now run this: ./go/bin/todos_appd');
    });

    // Note, we do not call done() here, because we want to block until the user
    // hits Ctrl-C. We rely on the Node process exit handler to kill our spawned
    // subprocesses.
  });

  // Kills all subprocesses we started. Not currently used, since spawn blocks.
  grunt.registerTask('subtask_killSubprocesses', function() {
    tearDown();
  });

  process.on('exit', function() {
    tearDown();
  });
};
<commit_msg>TBR: fixes needed as a consequence of https://veyron-review.googlesource.com/#/c/2075/<commit_after>// Provides tasks to spawn and kill subprocesses needed for todos app.
// See also: javascript/api/grunt_tasks/integration_test_environment_setup.js

var assert = require('assert');
var fs = require('fs');
var spawn = require('child_process').spawn;

module.exports = function(grunt) {
  var subprocesses = [];  // List of subprocesses we've spawned.

  // Kills all subprocesses we've spawned.
  var tearDown = function() {
    subprocesses.forEach(function(p) {
      p.kill('SIGTERM');
    });
  };

  // Calls tearDown, then fails the current task.
  var fail = function(msg) {
    tearDown();
    grunt.fail.fatal(msg);
    done(false);
  };

  // Starts all subprocesses and sets some grunt.config vars.
  grunt.registerTask('subtask_spawnSubprocesses', function() {
    // Tell grunt that this is an async task.
    // Note, we never actually call done() -- see comment below.
    var done = this.async();

    // Define constants.
    var c = grunt.constants;
    var VEYRON_IDENTITY_BIN = c.VEYRON_BIN_DIR + '/identityd';
    var VEYRON_PROXY_BIN = c.VEYRON_BIN_DIR + '/proxyd';
    var VEYRON_WSPR_BIN = c.VEYRON_BIN_DIR + '/wsprd';
    var STORE_BIN = c.VEYRON_BIN_DIR + '/todos_stored';
    var VEYRON_PROXY_ADDR = '127.0.0.1:' + c.VEYRON_PROXY_PORT;

    // Ensure binaries exist.
    var bins = [VEYRON_IDENTITY_BIN, VEYRON_PROXY_BIN, VEYRON_WSPR_BIN];
    bins.forEach(function(bin) {
      if (!fs.existsSync(bin)) {
        fail('Missing binary: ' + bin);
      }
    });

    // Start subprocesses.
    var startSubprocess = function(command, args) {
      var p = spawn(command, args);
      p.stderr.on('data', grunt.log.debug);
      subprocesses.push(p);
      return p;
    };
    var veyronIdentitySubprocess = startSubprocess(
        VEYRON_IDENTITY_BIN,
        ['-port=' + c.VEYRON_IDENTITY_PORT]);
    var veyronProxySubprocess = startSubprocess(
        VEYRON_PROXY_BIN,
        ['-log_dir=' + c.LOG_DIR, '-addr=' + VEYRON_PROXY_ADDR]);
    var veyronWsprSubprocess = startSubprocess(
        VEYRON_WSPR_BIN,
        ['-v=3', '-log_dir=' + c.LOG_DIR, '-port=' + c.VEYRON_WSPR_PORT,
         '-vproxy=' + VEYRON_PROXY_ADDR]);
    var storeSubprocess = startSubprocess(
        STORE_BIN, ['-log_dir=' + c.LOG_DIR]);

    // Capture todos_stored's endpoint, then write env.js file.
    storeSubprocess.stdout.on('data', function(data) {
      var endpoint;
      data.toString().split('\n').some(function(line) {
        line = line.replace('Endpoint: ', '').trim();
        if (line.match(/^@.*@$/)) {
          endpoint = line;
          return true;
        }
      });
      assert(endpoint, 'Failed to extract store endpoint');

      // Write JS file that sets env vars for client-side JS.
      var env = {
        VEYRON_WSPR_SERVER_URL: 'http://localhost:' + c.VEYRON_WSPR_PORT,
        STORE_ENDPOINT: endpoint
      };
      console.log(JSON.stringify(env, null, 2));
      var code = 'var env = JSON.parse(\'' + JSON.stringify(env) + '\');';
      // TODO(sadovsky): This is super hacky -- we shouldn't be writing files
      // into the webserver's dir. It'd be better to communicate these vars to
      // todos_appd via env vars or flags.
      grunt.file.write('todos_appd/js/env.js', code);

      console.log('Now run this: ./go/bin/todos_appd');
    });

    // Note, we do not call done() here, because we want to block until the user
    // hits Ctrl-C. We rely on the Node process exit handler to kill our spawned
    // subprocesses.
  });

  // Kills all subprocesses we started. Not currently used, since spawn blocks.
  grunt.registerTask('subtask_killSubprocesses', function() {
    tearDown();
  });

  process.on('exit', function() {
    tearDown();
  });
};
<|endoftext|>


<commit_before>// Pick which scenario we run. Pass the 'SCENARIO' environment variable
// to change this. See 'benchmark-scenarios.js' for the list of
// scenarios.

var PARAMS = {};
// XXX settings now has public. could move stuff there and avoid this.
if (Meteor.isServer) {
  if (!Meteor.settings.params)
    throw new Error("Must set scenario with Meteor.settings");
  __meteor_runtime_config__.PARAMS = PARAMS = Meteor.settings.params;
} else {
  PARAMS = __meteor_runtime_config__.PARAMS;
}

// id for this client or server.
var processId = Random.id();
console.log("SSS", processId);

//////////////////////////////
// Helper Functions
//////////////////////////////

var random = function (n) {
  return Math.floor(Random.fraction() * n);
};

var randomChars =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split('');
var randomString = function (length) {
  // XXX make more efficient
  var ret = '';
  _.times(length, function () {
    ret += Random.choice(randomChars);
  });
  return ret;
};

var pickCollection = function () {
  return Random.choice(Collections);
};

var generateDoc = function () {
  var ret = {};
  ret.fromProcess = processId;
  _.times(PARAMS.documentNumFields, function (n) {
    ret['Field' + n] = randomString(PARAMS.documentSize/PARAMS.documentNumFields);
  });

  return ret;
};

//////////////////////////////
// Data
//////////////////////////////

var Collections = [];
_.times(PARAMS.numCollections, function (n) {
  Collections.push(new Meteor.Collection("Collection" + n));
});

if (Meteor.isServer) {
  // init
  Meteor.startup(function () {
    // clear all the collections.
    _.each(Collections, function (C) {
      // XXX no, don't remove!
      // C.remove({});
    });
  });

  // periodic db check. client list
  var currentClients = [];
  var totalDocs = 0;
  Meteor.setInterval(function () {
    var newClients = {};
    var newTotal = 0;
    // XXX explain 3
    var since = +(new Date) - 1000*PARAMS.insertsPerSecond * 5;
    _.each(Collections, function (C) {
      _.each(C.find({when: {$gt: since}}, {fields: {fromProcess: 1, when: 1}}).fetch(), function (d) {
        newTotal += 1;
        if (d.fromProcess && d.when > since)
          newClients[d.fromProcess] = true;
      });
    });
    currentClients = _.keys(newClients);
    totalDocs = newTotal;
    console.log(totalDocs, currentClients.length);
  }, 3*1000); // XXX hardcoded time

  // periodic document cleanup.
  if (PARAMS.maxAgeSeconds) {
    Meteor.setInterval(function () {
      var when = +(new Date) - PARAMS.maxAgeSeconds*1000;
      _.each(Collections, function (C) {
        C.remove({when: {$lt: when}});
      });
      // Clear out 5% of the DB each time, steady state. XXX parameterize?
    }, 1000*PARAMS.maxAgeSeconds / 20);
  }

  Meteor.publish("data", function (collection, process) {
    check(collection, Number);
    check(process, String);
    var C = Collections[collection];
    return C.find({toProcess: process});
  });

  Meteor.methods({
    'insert': function (doc) {
      check(doc, Object);
      check(doc.fromProcess, String);
      // pick a random destination. send to ourselves if there is no one
      // else. by having an entry in the db, we'll end up in the target
      // list.
      doc.toProcess = Random.choice(currentClients) || doc.fromProcess;

      doc.when = +(new Date);

      var C = pickCollection();
      C.insert(doc);
    },
    update: function (processId, field, value) {
      check([processId, field, value], [String]);
      var modifer = {};
      modifer[field] = value; // XXX injection attack?

      var C = pickCollection();
      // update one message.
      C.update({fromProcess: processId}, {$set: modifer}, {multi: false});
    }
  });

  // XXX publish stats
  // - currentClients.length
  // - serverId
  // - num ddp sessions
  // - total documents
}

if (Meteor.isClient) {
  // sub to data
  _.times(PARAMS.numCollections, function (n) {
    Meteor.subscribe("data", n, processId);
  });

  // templates
  Template.params.params = function () {
    return _.map(PARAMS, function (v, k) {
      return {key: k, value: v};
    });
  };

  Template.status.status = function () {
    return Meteor.status().status;
  };

  Template.status.updateRate = function () {
    return (Session.get('updateAvgs') || []).join(", ");
  };

  // XXX count of how many docs are in local collection. don't

  // do stuff periodically

  if (PARAMS.insertsPerSecond) {
    Meteor.setInterval(function () {
      Meteor.call('insert', generateDoc());
    }, 1000 / PARAMS.insertsPerSecond);
  }

  if (PARAMS.updatesPerSecond) {
    Meteor.setInterval(function () {
      Meteor.call('update',
                  processId,
                  'Field' + random(PARAMS.documentNumFields),
                  randomString(PARAMS.documentSize/PARAMS.documentNumFields)
                 );
    }, 1000 / PARAMS.updatesPerSecond);
  }

  // XXX make a method? don't use it for now.
  if (PARAMS.removesPerSecond) {
    Meteor.setInterval(function () {
      var C = pickCollection();
      var docs = C.find({}).fetch();
      var doc = Random.choice(docs);
      if (doc)
        C.remove(doc._id);
    }, 1000 / PARAMS.removesPerSecond);
  }

  // XXX very rough per client update rate. we need to measure this
  // better. ideally, on the server we could get the global update rate
  var updateCount = 0;
  var updateHistories = {1: [], 10: [], 100: [], 1000: []};
  var updateFunc = function () { updateCount += 1; };
  _.each(Collections, function (C) {
    C.find({}).observeChanges({
      added: updateFunc, changed: updateFunc, removed: updateFunc
    });
  });
  Meteor.setInterval(function () {
    _.each(updateHistories, function (h, max) {
      h.push(updateCount);
      if (h.length > max)
        h.shift();
    });
    Session.set('updateAvgs', _.map(updateHistories, function (h) {
      return _.reduce(h, function(memo, num) {
        return memo + num;
      }, 0) / h.length;
    }));;
    updateCount = 0;
  }, 1000);

}
<commit_msg>Comment fixups.<commit_after>
// Pick scenario from settings.
// XXX settings now has public. could move stuff there and avoid this.
var PARAMS = {};
if (Meteor.isServer) {
  if (!Meteor.settings.params)
    throw new Error("Must set scenario with Meteor.settings");
  __meteor_runtime_config__.PARAMS = PARAMS = Meteor.settings.params;
} else {
  PARAMS = __meteor_runtime_config__.PARAMS;
}

// id for this client or server.
var processId = Random.id();
console.log("processId", processId);

//////////////////////////////
// Helper Functions
//////////////////////////////

var random = function (n) {
  return Math.floor(Random.fraction() * n);
};

var randomChars =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split('');
var randomString = function (length) {
  // XXX make more efficient
  var ret = '';
  _.times(length, function () {
    ret += Random.choice(randomChars);
  });
  return ret;
};

var pickCollection = function () {
  return Random.choice(Collections);
};

var generateDoc = function () {
  var ret = {};
  ret.fromProcess = processId;
  _.times(PARAMS.documentNumFields, function (n) {
    ret['Field' + n] = randomString(PARAMS.documentSize/PARAMS.documentNumFields);
  });

  return ret;
};

//////////////////////////////
// Data
//////////////////////////////

var Collections = [];
_.times(PARAMS.numCollections, function (n) {
  Collections.push(new Meteor.Collection("Collection" + n));
});

if (Meteor.isServer) {

  // periodic db check. generate a client list.
  var currentClients = [];
  var totalDocs = 0;
  Meteor.setInterval(function () {
    var newClients = {};
    var newTotal = 0;
    // XXX hardcoded time
    var since = +(new Date) - 1000*PARAMS.insertsPerSecond * 5;
    _.each(Collections, function (C) {
      _.each(C.find({when: {$gt: since}}, {fields: {fromProcess: 1, when: 1}}).fetch(), function (d) {
        newTotal += 1;
        if (d.fromProcess && d.when > since)
          newClients[d.fromProcess] = true;
      });
    });
    currentClients = _.keys(newClients);
    totalDocs = newTotal;
    console.log(totalDocs, currentClients.length);
  }, 3*1000); // XXX hardcoded time

  // periodic document cleanup.
  if (PARAMS.maxAgeSeconds) {
    Meteor.setInterval(function () {
      var when = +(new Date) - PARAMS.maxAgeSeconds*1000;
      _.each(Collections, function (C) {
        C.remove({when: {$lt: when}});
      });
      // Clear out 5% of the DB each time, steady state. XXX parameterize?
    }, 1000*PARAMS.maxAgeSeconds / 20);
  }

  Meteor.publish("data", function (collection, process) {
    check(collection, Number);
    check(process, String);
    var C = Collections[collection];
    return C.find({toProcess: process});
  });

  Meteor.methods({
    'insert': function (doc) {
      check(doc, Object);
      check(doc.fromProcess, String);
      // pick a random destination. send to ourselves if there is no one
      // else. by having an entry in the db, we'll end up in the target
      // list.
      doc.toProcess = Random.choice(currentClients) || doc.fromProcess;

      doc.when = +(new Date);

      var C = pickCollection();
      C.insert(doc);
    },
    update: function (processId, field, value) {
      check([processId, field, value], [String]);
      var modifer = {};
      modifer[field] = value; // XXX injection attack?

      var C = pickCollection();
      // update one message.
      C.update({fromProcess: processId}, {$set: modifer}, {multi: false});
    }
  });

  // XXX publish stats
  // - currentClients.length
  // - serverId
  // - num ddp sessions
  // - total documents
}

if (Meteor.isClient) {
  // sub to data
  _.times(PARAMS.numCollections, function (n) {
    Meteor.subscribe("data", n, processId);
  });

  // templates
  Template.params.params = function () {
    return _.map(PARAMS, function (v, k) {
      return {key: k, value: v};
    });
  };

  Template.status.status = function () {
    return Meteor.status().status;
  };

  Template.status.updateRate = function () {
    return (Session.get('updateAvgs') || []).join(", ");
  };

  // XXX count of how many docs are in local collection. don't

  // do stuff periodically

  if (PARAMS.insertsPerSecond) {
    Meteor.setInterval(function () {
      Meteor.call('insert', generateDoc());
    }, 1000 / PARAMS.insertsPerSecond);
  }

  if (PARAMS.updatesPerSecond) {
    Meteor.setInterval(function () {
      Meteor.call('update',
                  processId,
                  'Field' + random(PARAMS.documentNumFields),
                  randomString(PARAMS.documentSize/PARAMS.documentNumFields)
                 );
    }, 1000 / PARAMS.updatesPerSecond);
  }

  // XXX make a method? don't use it for now.
  if (PARAMS.removesPerSecond) {
    Meteor.setInterval(function () {
      var C = pickCollection();
      var docs = C.find({}).fetch();
      var doc = Random.choice(docs);
      if (doc)
        C.remove(doc._id);
    }, 1000 / PARAMS.removesPerSecond);
  }

  // XXX very rough per client update rate. we need to measure this
  // better. ideally, on the server we could get the global update rate
  var updateCount = 0;
  var updateHistories = {1: [], 10: [], 100: [], 1000: []};
  var updateFunc = function () { updateCount += 1; };
  _.each(Collections, function (C) {
    C.find({}).observeChanges({
      added: updateFunc, changed: updateFunc, removed: updateFunc
    });
  });
  Meteor.setInterval(function () {
    _.each(updateHistories, function (h, max) {
      h.push(updateCount);
      if (h.length > max)
        h.shift();
    });
    Session.set('updateAvgs', _.map(updateHistories, function (h) {
      return _.reduce(h, function(memo, num) {
        return memo + num;
      }, 0) / h.length;
    }));;
    updateCount = 0;
  }, 1000);

}
<|endoftext|>


<commit_before>/*
 * dmuploader.js - Jquery File Uploader - 0.1
 * http://www.daniel.com.uy/projects/jquery-file-uploader/
 *
 * Copyright (c) 2013-2014 Daniel Morales
 * Dual licensed under the MIT and GPL licenses.
 * http://www.daniel.com.uy/doc/license/
 */

(function($) {
  var pluginName = 'dmUploader';

  var FileStatus = {
    PENDING: 0,
    UPLOADING: 1,
    COMPLETED: 2,
    FAILED: 3,
    CANCELLED: 4 //(by the user)
  };

  // These are the plugin defaults values
  var defaults = {
    url: document.URL,
    method: 'POST',
    extraData: {},
    headers: {},
    maxFileSize: 0,
    allowedTypes: '*',
    extFilter: null,
    dataType: null,
    fileName: 'file',
    auto: true,
    skipChecks: false,
    onInit: function(){},
    onFallbackMode: function() {},
    onNewFile: function(){},
    onBeforeUpload: function(){},
    onComplete: function(){},
    onUploadProgress: function(){},
    onUploadSuccess: function(){},
    onUploadError: function(){},
    onFileTypeError: function(){},
    onFileSizeError: function(){},
    onFileExtError: function(){}
  };

  var DmUploader = function(element, options)
  {
    this.element = $(element);

    this.settings = $.extend({}, defaults, options);

    if(!this.settings.skipChecks && !this.checkBrowser()){
      return false;
    }

    this.init();

    return true;
  };

  var DmUploaderFile = function(file)
  {
    this.file = file;

    this.jqXHR = null;

    this.status = FileStatus.PENDING;

    this.id = -1;
  };

  DmUploaderFile.prototype.setId = function(id)
  {
    this.id = id;
  };

  DmUploaderFile.prototype.cancel = function()
  {
    switch (this.status){
      case FileStatus.PENDING:

        this.status = FileStatus.CANCELLED;
        break;
      case FileStatus.UPLOADING:

        this.status = FileStatus.CANCELLED;
        this.jqXHR.abort();
        break;
      default:
        return false;
    }

    return true;
  };

  DmUploaderFile.prototype.upload = function(widget, single)
  {
    var file = this;

    // Cancelled by the user?
    if (!single && (file.status == FileStatus.CANCELLED)){
      widget.processQueue();

      return false;
    }

    // Uploading, or completed files...
    if (file.status == FileStatus.UPLOADING ||
        file.status == FileStatus.COMPLETED){
      if (!single){
        widget.processQueue();
      }

      return false;
    }

    // Form Data
    var fd = new FormData();
    fd.append(widget.settings.fileName, file.file);

    // Append extra Form Data
    $.each(widget.settings.extraData, function(exKey, exVal){
      fd.append(exKey, exVal);
    });

    widget.settings.onBeforeUpload.call(widget.element, file.id);

    if (!single){
      widget.queueRunning = true;
    }

    file.status = FileStatus.UPLOADING;

    // Ajax Submit
    file.jqXHR = $.ajax({
      url: widget.settings.url,
      type: widget.settings.method,
      dataType: widget.settings.dataType,
      data: fd,
      headers: widget.settings.headers,
      cache: false,
      contentType: false,
      processData: false,
      forceSync: false,
      xhr: function(){
        var xhrobj = $.ajaxSettings.xhr();
        if(xhrobj.upload){
          xhrobj.upload.addEventListener('progress', function(event) {
            var percent = 0;
            var position = event.loaded || event.position;
            var total = event.total || event.totalSize;
            if(event.lengthComputable){
              percent = Math.ceil(position / total * 100);
            }

            widget.settings.onUploadProgress.call(widget.element, file.id, percent);
          }, false);
        }

        return xhrobj;
      },
      success: function (data){
        file.status = FileStatus.COMPLETED;
        widget.settings.onUploadSuccess.call(widget.element, file.id, data);
      },
      error: function (xhr, status, errMsg){
        // If the status is: cancelled (by the user) don't invoke the error callback
        if (file.status != FileStatus.CANCELLED){
          file.status = FileStatus.FAILED;
          widget.settings.onUploadError.call(widget.element, file.id, errMsg);
        }
      },
      complete: function(){
        if (widget.settings.auto){
          widget.processQueue();
        }
      }
    });

    return true;
  };

  // Public API methods
  DmUploader.prototype.methods = {
    cancel: function(id) {
      /* Stops(if uploading) and Remove the upload from Queue */
      if (id > (this.queue.length - 1)){
        return false;
      }

      return this.queue[id].cancel();
    },
    cancelAll: function() {
      /* Same as cancel, but for all pending uploads */
      var nCancels = 0;

      $.each(this.queue, function(id, file){
        if (file.cancel()){
          nCancels++;
        }
      });

      return nCancels;
    },
    start: function(id) {
      /* Start or re-try the upload */
      if (id > (this.queue.length - 1)){
        return false;
      }

      // If we are using a File queue only allow to start (or retry)
      // files that that were cancelled or failed (NOT files that are 'next' in
      // our queue.
      if (this.settings.auto && (id >= this.queuePos)){
        return false;
      }

      return this.queue[id].upload(this, true);
    },
    supportsProgress: function() {
      var xhrobj = $.ajaxSettings.xhr();

      return (xhrobj.upload !== undefined);
    },
    supportsDND: function() {
      return (this.checkEvent('drop', this.element) && !this.checkEvent('dragstart', this.element));
    }
  };

  DmUploader.prototype.checkBrowser = function()
  {
    if(window.FormData === undefined){
      this.settings.onFallbackMode.call(this.element, 'Browser doesn\'t support Form API');

      return false;
    }

    if(this.element.find('input[type=file]').length > 0){
      return true;
    }

    if (!this.checkEvent('drop', this.element) || !this.checkEvent('dragstart', this.element)){
      this.settings.onFallbackMode.call(this.element, 'Browser doesn\'t support Ajax Drag and Drop');

      return false;
    }

    return true;
  };

  DmUploader.prototype.checkEvent = function(eventName, element)
  {
    element = element || document.createElement('div');
    eventName = 'on' + eventName;

    var isSupported = eventName in element;

    if(!isSupported){
      if(!element.setAttribute){
        element = document.createElement('div');
      }
      if(element.setAttribute && element.removeAttribute){
        element.setAttribute(eventName, '');
        isSupported = typeof element[eventName] == 'function';

        if(typeof element[eventName] != 'undefined'){
          element[eventName] = undefined;
        }
        element.removeAttribute(eventName);
      }
    }

    element = null;
    return isSupported;
  };

  DmUploader.prototype.init = function()
  {
    var widget = this;

    widget.queue = [];
    widget.queuePos = -1;
    widget.queueRunning = false;

    // -- Drag and drop event
    widget.element.on('drop', function (evt){
      evt.preventDefault();
      var files = evt.originalEvent.dataTransfer.files;

      widget.queueFiles(files);
    });

    //-- Optional File input to make a clickable area
    widget.element.find('input[type=file]').on('change', function(evt){
      var files = evt.target.files;

      widget.queueFiles(files);

      $(this).val('');
    });

    this.settings.onInit.call(this.element);
  };

  DmUploader.prototype.queueFiles = function(files)
  {
    var j = this.queue.length;

    for (var i= 0; i < files.length; i++)
    {
      var file = files[i];

      // Check file size
      if((this.settings.maxFileSize > 0) &&
          (file.size > this.settings.maxFileSize)){

        this.settings.onFileSizeError.call(this.element, file);

        continue;
      }

      // Check file type
      if((this.settings.allowedTypes != '*') &&
          !file.type.match(this.settings.allowedTypes)){

        this.settings.onFileTypeError.call(this.element, file);

        continue;
      }

      // Check file extension
      if(this.settings.extFilter !== null){
        var extList = this.settings.extFilter.toLowerCase().split(';');

        var ext = file.name.toLowerCase().split('.').pop();

        if($.inArray(ext, extList) < 0){
          this.settings.onFileExtError.call(this.element, file);

          continue;
        }
      }

      var fileObj = new DmUploaderFile(file);
      this.queue.push(fileObj);

      var index = this.queue.length - 1;
      fileObj.setId(index);

      this.settings.onNewFile.call(this.element, index, file);
    }

    // Only start Queue if we haven't!
    if(this.queueRunning){
      return false;
    }

    // and only if new Files were succefully added
    if(this.queue.length == j){
      return false;
    }

    if (this.settings.auto){
      this.processQueue();
    }

    return true;
  };

  DmUploader.prototype.processQueue = function()
  {
    this.queuePos++;

    if(this.queuePos >= this.queue.length){
      this.settings.onComplete.call(this.element);

      // Wait until new files are droped
      this.queuePos = (this.queue.length - 1);

      this.queueRunning = false;

      return;
    }

    this.queue[this.queuePos].upload(this, false);
  };

  $.fn.dmUploader = function(args){
    // Now we are only able to initialize only one widget at the time
    if($(this).length != 1){
      $.error('Need one element to initialize jQuery.dmUploader');

      return false;
    }

    var plugin = $(this).data(pluginName);

    if(!plugin){
      $(this).data(pluginName, new DmUploader(this, args));
    } else if (plugin.methods[args]){
      return plugin.methods[args].apply(plugin, Array.prototype.slice.call(arguments, 1));
    } else {
      $.error('Method ' +  args + ' does not exist on jQuery.dmUploader');

      return false;
    }

    return true;
  };

  // -- Disable Document D&D events to prevent opening the file on browser when we drop them
  $(document).on('dragenter', function (e) { e.stopPropagation(); e.preventDefault(); });
  $(document).on('dragover', function (e) { e.stopPropagation(); e.preventDefault(); });
  $(document).on('drop', function (e) { e.stopPropagation(); e.preventDefault(); });
})(jQuery);<commit_msg>Adding some changes form master, this repo is offically messed up,... but still alive<commit_after>/*
 * dmuploader.js - Jquery File Uploader - 0.1
 * https://github.com/danielm/uploader
 *
 * Copyright (c) 2013-2017 Daniel Morales
 * Dual licensed under the MIT and GPL licenses.
 */

(function($) {
  var pluginName = 'dmUploader';

  var FileStatus = {
    PENDING: 0,
    UPLOADING: 1,
    COMPLETED: 2,
    FAILED: 3,
    CANCELLED: 4 //(by the user)
  };

  // These are the plugin defaults values
  var defaults = {
    url: document.URL,
    method: 'POST',
    extraData: {},
    headers: {},
    maxFileSize: 0,
    maxFiles: 0,
    allowedTypes: '*',
    extFilter: null,
    dataType: null,
    fileName: 'file',
    auto: true,
    skipChecks: false,
    onInit: function(){},
    onFallbackMode: function(message) {},
    onNewFile: function(id, file){},
    onBeforeUpload: function(id){},
    onComplete: function(){},
    onUploadProgress: function(id, percent){},
    onUploadSuccess: function(id, data){},
    onUploadError: function(id, message){},
    onFileTypeError: function(file){},
    onFileSizeError: function(file){},
    onFileExtError: function(file){},
    onFilesMaxError: function(file){}
  };

  var DmUploader = function(element, options)
  {
    this.element = $(element);

    this.settings = $.extend({}, defaults, options);

    if(!this.settings.skipChecks && !this.checkBrowser()){
      return false;
    }

    this.init();

    return true;
  };

  var DmUploaderFile = function(file)
  {
    this.file = file;

    this.jqXHR = null;

    this.status = FileStatus.PENDING;

    this.id = -1;
  };

  DmUploaderFile.prototype.setId = function(id)
  {
    this.id = id;
  };

  DmUploaderFile.prototype.cancel = function()
  {
    switch (this.status){
      case FileStatus.PENDING:

        this.status = FileStatus.CANCELLED;
        break;
      case FileStatus.UPLOADING:

        this.status = FileStatus.CANCELLED;
        this.jqXHR.abort();
        break;
      default:
        return false;
    }

    return true;
  };

  DmUploaderFile.prototype.upload = function(widget, single)
  {
    var file = this;

    // Cancelled by the user?
    if (!single && (file.status == FileStatus.CANCELLED)){
      widget.processQueue();

      return false;
    }

    // Uploading, or completed files...
    if (file.status == FileStatus.UPLOADING ||
        file.status == FileStatus.COMPLETED){
      if (!single){
        widget.processQueue();
      }

      return false;
    }

    // Form Data
    var fd = new FormData();
    fd.append(widget.settings.fileName, file.file);

    // Return from client function (default === undefined)
    var can_continue = widget.settings.onBeforeUpload.call(widget.element, file.id);

    // If the client function doesn't return FALSE then continue
    if( false === can_continue ) {
      return;
    }

    // Append extra Form Data
    $.each(widget.settings.extraData, function(exKey, exVal){
      fd.append(exKey, exVal);
    });

    if (!single){
      widget.queueRunning = true;
    }

    file.status = FileStatus.UPLOADING;

    // Ajax Submit
    file.jqXHR = $.ajax({
      url: widget.settings.url,
      type: widget.settings.method,
      dataType: widget.settings.dataType,
      data: fd,
      headers: widget.settings.headers,
      cache: false,
      contentType: false,
      processData: false,
      forceSync: false,
      xhr: function(){
        var xhrobj = $.ajaxSettings.xhr();
        if(xhrobj.upload){
          xhrobj.upload.addEventListener('progress', function(event) {
            var percent = 0;
            var position = event.loaded || event.position;
            var total = event.total || event.totalSize;
            if(event.lengthComputable){
              percent = Math.ceil(position / total * 100);
            }

            widget.settings.onUploadProgress.call(widget.element, file.id, percent);
          }, false);
        }

        return xhrobj;
      },
      success: function (data){
        file.status = FileStatus.COMPLETED;
        widget.settings.onUploadSuccess.call(widget.element, file.id, data);
      },
      error: function (xhr, status, errMsg){
        // If the status is: cancelled (by the user) don't invoke the error callback
        if (file.status != FileStatus.CANCELLED){
          file.status = FileStatus.FAILED;
          widget.settings.onUploadError.call(widget.element, file.id, errMsg);
        }
      },
      complete: function(){
        if (widget.settings.auto){
          widget.processQueue();
        }
      }
    });

    return true;
  };

  // Public API methods
  DmUploader.prototype.methods = {
    cancel: function(id) {
      /* Stops(if uploading) and Remove the upload from Queue */
      if (id > (this.queue.length - 1)){
        return false;
      }

      return this.queue[id].cancel();
    },
    cancelAll: function() {
      /* Same as cancel, but for all pending uploads */
      var nCancels = 0;

      $.each(this.queue, function(id, file){
        if (file.cancel()){
          nCancels++;
        }
      });

      return nCancels;
    },
    start: function(id) {
      /* Start or re-try the upload */
      if (id > (this.queue.length - 1)){
        return false;
      }

      // If we are using a File queue only allow to start (or retry)
      // files that that were cancelled or failed (NOT files that are 'next' in
      // our queue.
      if (this.settings.auto && (id >= this.queuePos)){
        return false;
      }

      return this.queue[id].upload(this, true);
    },
    supportsProgress: function() {
      var xhrobj = $.ajaxSettings.xhr();

      return (xhrobj.upload !== undefined);
    },
    supportsDND: function() {
      return (this.checkEvent('drop', this.element) && !this.checkEvent('dragstart', this.element));
    }
  };

  DmUploader.prototype.checkBrowser = function()
  {
    if(window.FormData === undefined){
      this.settings.onFallbackMode.call(this.element, 'Browser doesn\'t support Form API');

      return false;
    }

    if(this.element.find('input[type=file]').length > 0){
      return true;
    }

    if (!this.checkEvent('drop', this.element) || !this.checkEvent('dragstart', this.element)){
      this.settings.onFallbackMode.call(this.element, 'Browser doesn\'t support Ajax Drag and Drop');

      return false;
    }

    return true;
  };

  DmUploader.prototype.checkEvent = function(eventName, element)
  {
    element = element || document.createElement('div');
    eventName = 'on' + eventName;

    var isSupported = eventName in element;

    if(!isSupported){
      if(!element.setAttribute){
        element = document.createElement('div');
      }
      if(element.setAttribute && element.removeAttribute){
        element.setAttribute(eventName, '');
        isSupported = typeof element[eventName] == 'function';

        if(typeof element[eventName] != 'undefined'){
          element[eventName] = undefined;
        }
        element.removeAttribute(eventName);
      }
    }

    element = null;
    return isSupported;
  };

  DmUploader.prototype.init = function()
  {
    var widget = this;

    widget.queue = [];
    widget.queuePos = -1;
    widget.queueRunning = false;

    // -- Drag and drop event
    widget.element.on('drop', function (evt){
      evt.preventDefault();
      var files = evt.originalEvent.dataTransfer.files;

      widget.queueFiles(files);
    });

    //-- Optional File input to make a clickable area
    widget.element.find('input[type=file]').on('change', function(evt){
      var files = evt.target.files;

      widget.queueFiles(files);

      $(this).val('');
    });

    this.settings.onInit.call(this.element);
  };

  DmUploader.prototype.queueFiles = function(files)
  {
    var j = this.queue.length;

    for (var i= 0; i < files.length; i++)
    {
      var file = files[i];

      // Check file size
      if((this.settings.maxFileSize > 0) &&
          (file.size > this.settings.maxFileSize)){

        this.settings.onFileSizeError.call(this.element, file);

        continue;
      }

      // Check file type
      if((this.settings.allowedTypes != '*') &&
          !file.type.match(this.settings.allowedTypes)){

        this.settings.onFileTypeError.call(this.element, file);

        continue;
      }

      // Check file extension
      if(this.settings.extFilter !== null){
        var extList = this.settings.extFilter.toLowerCase().split(';');

        var ext = file.name.toLowerCase().split('.').pop();

        if($.inArray(ext, extList) < 0){
          this.settings.onFileExtError.call(this.element, file);

          continue;
        }
      }

      // Check max files
      if(this.settings.maxFiles !== null) {
        if(this.queue.length >= this.settings.maxFiles) {
          this.settings.onFilesMaxError.call(this.element, file);

          continue;
        }
      }

      var fileObj = new DmUploaderFile(file);
      this.queue.push(fileObj);

      var index = this.queue.length - 1;
      fileObj.setId(index);

      this.settings.onNewFile.call(this.element, index, file);
    }

    // Only start Queue if we haven't!
    if(this.queueRunning){
      return false;
    }

    // and only if new Files were succefully added
    if(this.queue.length == j){
      return false;
    }

    if (this.settings.auto){
      this.processQueue();
    }

    return true;
  };

  DmUploader.prototype.processQueue = function()
  {
    this.queuePos++;

    if(this.queuePos >= this.queue.length){
      this.settings.onComplete.call(this.element);

      // Wait until new files are droped
      this.queuePos = (this.queue.length - 1);

      this.queueRunning = false;

      return;
    }

    this.queue[this.queuePos].upload(this, false);
  };

  $.fn.dmUploader = function(args){
    // Now we are only able to initialize only one widget at the time
    if($(this).length != 1){
      $.error('Need one element to initialize jQuery.dmUploader');

      return false;
    }

    var plugin = $(this).data(pluginName);

    if(!plugin){
      $(this).data(pluginName, new DmUploader(this, args));
    } else if (plugin.methods[args]){
      return plugin.methods[args].apply(plugin, Array.prototype.slice.call(arguments, 1));
    } else {
      $.error('Method ' +  args + ' does not exist on jQuery.dmUploader');

      return false;
    }

    return true;
  };

  // -- Disable Document D&D events to prevent opening the file on browser when we drop them
  $(document).on('dragenter', function (e) { e.stopPropagation(); e.preventDefault(); });
  $(document).on('dragover', function (e) { e.stopPropagation(); e.preventDefault(); });
  $(document).on('drop', function (e) { e.stopPropagation(); e.preventDefault(); });
})(jQuery);<|endoftext|>


<commit_before>var extractJSON = require('./extract-json');

function allTextNodes(nodes) {
  return Object.keys(nodes).reduce(function(result, key) {
    return result && nodes[key].nodeName === '#text'
  }, true)
}

function getPreWithSource() {
  var childNodes = document.body.childNodes;

  if (childNodes.length > 1 && allTextNodes(childNodes)) {
    if (process.env.NODE_ENV === 'development') {
      console.debug("[JSONViewer] Loaded from a multiple text nodes, normalizing");
    }

    document.body.normalize() // concatenates adjacent text nodes
  }

  if (childNodes.length === 1) {
    var childNode = childNodes[0];
    var nodeName = childNode.nodeName
    var textContent = childNode.textContent

    if (nodeName === "PRE") {
      return childNode;

    // if Content-Type is text/html
    } else if (nodeName === "#text" && textContent.trim().length > 0) {
      if (process.env.NODE_ENV === 'development') {
        console.debug("[JSONViewer] Loaded from a text node, this might have returned content-type: text/html");
      }

      var pre = document.createElement("pre");
      pre.textContent = textContent;
      document.body.removeChild(childNode);
      document.body.appendChild(pre);
      return pre;
    }
  }

  return null
}

function isJSON(jsonStr) {
  var str = jsonStr;
  if (!str || str.length === 0) {
    return false
  }

  str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
  str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
  str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '')
  return (/^[\],:{}\s]*$/).test(str)
}

function isJSONP(jsonStr) {
  return isJSON(extractJSON(jsonStr));
}

function checkIfJson(sucessCallback, element) {
  var pre = element || getPreWithSource();

  if (pre !== null &&
    pre !== undefined &&
    (isJSON(pre.textContent) || isJSONP(pre.textContent))) {

    sucessCallback(pre);
  }
}

module.exports = checkIfJson;
<commit_msg>Reorder the childNodes checks<commit_after>var extractJSON = require('./extract-json');

function allTextNodes(nodes) {
  return Object.keys(nodes).reduce(function(result, key) {
    return result && nodes[key].nodeName === '#text'
  }, true)
}

function getPreWithSource() {
  var childNodes = document.body.childNodes;

  if (childNodes.length === 0) {
    null
  }

  if (childNodes.length > 1 && allTextNodes(childNodes)) {
    if (process.env.NODE_ENV === 'development') {
      console.debug("[JSONViewer] Loaded from a multiple text nodes, normalizing");
    }

    document.body.normalize() // concatenates adjacent text nodes
  }

  var childNode = childNodes[0];
  var nodeName = childNode.nodeName
  var textContent = childNode.textContent

  if (nodeName === "PRE") {
    return childNode;
  }

  // if Content-Type is text/html
  if (nodeName === "#text" && textContent.trim().length > 0) {
    if (process.env.NODE_ENV === 'development') {
      console.debug("[JSONViewer] Loaded from a text node, this might have returned content-type: text/html");
    }

    var pre = document.createElement("pre");
    pre.textContent = textContent;
    document.body.removeChild(childNode);
    document.body.appendChild(pre);
    return pre;
  }

  return null
}

function isJSON(jsonStr) {
  var str = jsonStr;
  if (!str || str.length === 0) {
    return false
  }

  str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
  str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
  str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '')
  return (/^[\],:{}\s]*$/).test(str)
}

function isJSONP(jsonStr) {
  return isJSON(extractJSON(jsonStr));
}

function checkIfJson(sucessCallback, element) {
  var pre = element || getPreWithSource();

  if (pre !== null &&
    pre !== undefined &&
    (isJSON(pre.textContent) || isJSONP(pre.textContent))) {

    sucessCallback(pre);
  }
}

module.exports = checkIfJson;
<|endoftext|>


<commit_before>const {Plugin} = require("prosemirror-state")
const {Decoration, DecorationSet} = require("prosemirror-view")

const gecko = typeof navigator != "undefined" && /gecko\/\d/i.test(navigator.userAgent)
const linux = typeof navigator != "undefined" && /linux/i.test(navigator.platform)

function dropCursor(options) {
  function dispatch(view, data) {
    view.dispatch(view.state.tr.setMeta(plugin, data))
  }

  let timeout = null
  function scheduleRemoval(view) {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
    }, 1000)
  }

  let plugin = new Plugin({
    state: {
      init() { return null },
      apply(tr, prev, state) {
        // Firefox on Linux gets really confused an breaks dragging when we
        // mess with the nodes around the target node during a drag. So
        // disable this plugin there. See https://bugzilla.mozilla.org/show_bug.cgi?id=1323170
        if (gecko && linux) return null
        let command = tr.getMeta(plugin)
        if (!command) return prev
        if (command.type == "set") return pluginStateFor(state, command.pos, options)
        return null
      }
    },
    props: {
      handleDOMEvents: {
        dragover(view, event) {
          let active = plugin.getState(view.state)
          let pos = view.posAtCoords({left: event.clientX, top: event.clientY})
          if (pos && !active || active.pos != pos.pos) dispatch(view, {type: "setDropCursor", pos: pos.pos})
          scheduleRemoval(view)
          return false
        },

        dragend(view) {
          if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
          return false
        },

        drop(view) {
          if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
          return false
        },

        dragleave(view, event) {
          if (event.target == view.content) dispatch(view, {type: "remove"})
          return false
        }
      },
      decorations(state) {
        let active = plugin.getState(state)
        return active && active.deco
      }
    }
  })
  return plugin
}
exports.dropCursor = dropCursor

function style(options, side) {
  let width = (options && options.width) || 1
  let color = (options && options.color) || "black"
  return `border-${side}: ${width}px solid ${color}; margin-${side}: -${width}px`
}

function pluginStateFor(state, pos, options) {
  let $pos = state.doc.resolve(pos), deco
  if (!$pos.parent.isTextblock) {
    let before, after
    if (before = $pos.nodeBefore)
      deco = Decoration.node(pos - before.nodeSize, pos, {nodeName: "div", style: style(options, "right")})
    else if (after = $pos.nodeAfter)
      deco = Decoration.node(pos, pos + after.nodeSize, {nodeName: "div", style: style(options, "left")})
  }
  if (!deco) {
    let node = document.createElement("span")
    node.textContent = "\u200b"
    node.style.cssText = style(options, "left") + "; display: inline-block; pointer-events: none"
    deco = Decoration.widget(pos, node)
  }
  return {pos, deco: DecorationSet.create(state.doc, [deco])}
}
<commit_msg>Fix broken use of &&/|| precedence<commit_after>const {Plugin} = require("prosemirror-state")
const {Decoration, DecorationSet} = require("prosemirror-view")

const gecko = typeof navigator != "undefined" && /gecko\/\d/i.test(navigator.userAgent)
const linux = typeof navigator != "undefined" && /linux/i.test(navigator.platform)

function dropCursor(options) {
  function dispatch(view, data) {
    view.dispatch(view.state.tr.setMeta(plugin, data))
  }

  let timeout = null
  function scheduleRemoval(view) {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
    }, 1000)
  }

  let plugin = new Plugin({
    state: {
      init() { return null },
      apply(tr, prev, state) {
        // Firefox on Linux gets really confused an breaks dragging when we
        // mess with the nodes around the target node during a drag. So
        // disable this plugin there. See https://bugzilla.mozilla.org/show_bug.cgi?id=1323170
        if (gecko && linux) return null
        let command = tr.getMeta(plugin)
        if (!command) return prev
        if (command.type == "set") return pluginStateFor(state, command.pos, options)
        return null
      }
    },
    props: {
      handleDOMEvents: {
        dragover(view, event) {
          let active = plugin.getState(view.state)
          let pos = view.posAtCoords({left: event.clientX, top: event.clientY})
          if (pos && (!active || active.pos != pos.pos))
            dispatch(view, {type: "setDropCursor", pos: pos.pos})
          scheduleRemoval(view)
          return false
        },

        dragend(view) {
          if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
          return false
        },

        drop(view) {
          if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
          return false
        },

        dragleave(view, event) {
          if (event.target == view.content) dispatch(view, {type: "remove"})
          return false
        }
      },
      decorations(state) {
        let active = plugin.getState(state)
        return active && active.deco
      }
    }
  })
  return plugin
}
exports.dropCursor = dropCursor

function style(options, side) {
  let width = (options && options.width) || 1
  let color = (options && options.color) || "black"
  return `border-${side}: ${width}px solid ${color}; margin-${side}: -${width}px`
}

function pluginStateFor(state, pos, options) {
  let $pos = state.doc.resolve(pos), deco
  if (!$pos.parent.isTextblock) {
    let before, after
    if (before = $pos.nodeBefore)
      deco = Decoration.node(pos - before.nodeSize, pos, {nodeName: "div", style: style(options, "right")})
    else if (after = $pos.nodeAfter)
      deco = Decoration.node(pos, pos + after.nodeSize, {nodeName: "div", style: style(options, "left")})
  }
  if (!deco) {
    let node = document.createElement("span")
    node.textContent = "\u200b"
    node.style.cssText = style(options, "left") + "; display: inline-block; pointer-events: none"
    deco = Decoration.widget(pos, node)
  }
  return {pos, deco: DecorationSet.create(state.doc, [deco])}
}
<|endoftext|>


<commit_before>import {Plugin} from "prosemirror-state"
import {dropPoint} from "prosemirror-transform"

// :: (options: ?Object) → Plugin
// Create a plugin that, when added to a ProseMirror instance,
// causes a decoration to show up at the drop position when something
// is dragged over the editor.
//
//   options::- These options are supported:
//
//     color:: ?string
//     The color of the cursor. Defaults to `black`.
//
//     width:: ?number
//     The precise width of the cursor in pixels. Defaults to 1.
//
//     class:: ?string
//     A CSS class name to add to the cursor element.
export function dropCursor(options = {}) {
  return new Plugin({
    view(editorView) { return new DropCursorView(editorView, options) }
  })
}

class DropCursorView {
  constructor(editorView, options) {
    this.editorView = editorView
    this.width = options.width || 1
    this.color = options.color || "black"
    this.class = options.class
    this.predicate = options.predicate || function () { return true }
    this.cursorPos = null
    this.element = null
    this.timeout = null

    this.handlers = ["dragover", "dragend", "drop", "dragleave"].map(name => {
      let handler = e => this[name](e)
      editorView.dom.addEventListener(name, handler)
      return {name, handler}
    })
  }

  destroy() {
    this.handlers.forEach(({name, handler}) => this.editorView.dom.removeEventListener(name, handler))
  }

  update(editorView, prevState) {
    if (this.cursorPos != null && prevState.doc != editorView.state.doc) {
      if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null)
      else this.updateOverlay()
    }
  }

  setCursor(pos) {
    if (pos == this.cursorPos) return
    this.cursorPos = pos
    if (pos == null) {
      this.element.parentNode.removeChild(this.element)
      this.element = null
    } else {
      this.updateOverlay()
    }
  }

  updateOverlay() {
    let $pos = this.editorView.state.doc.resolve(this.cursorPos), rect
    if (!$pos.parent.inlineContent) {
      let before = $pos.nodeBefore, after = $pos.nodeAfter
      if (before || after) {
        let nodeRect = this.editorView.nodeDOM(this.cursorPos - (before ?  before.nodeSize : 0)).getBoundingClientRect()
        let top = before ? nodeRect.bottom : nodeRect.top
        if (before && after)
          top = (top + this.editorView.nodeDOM(this.cursorPos).getBoundingClientRect().top) / 2
        rect = {left: nodeRect.left, right: nodeRect.right, top: top - this.width / 2, bottom: top + this.width / 2}
      }
    }
    if (!rect) {
      let coords = this.editorView.coordsAtPos(this.cursorPos)
      rect = {left: coords.left - this.width / 2, right: coords.left + this.width / 2, top: coords.top, bottom: coords.bottom}
    }

    let parent = this.editorView.dom.offsetParent
    if (!this.element) {
      this.element = parent.appendChild(document.createElement("div"))
      if (this.class) this.element.className = this.class
      this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none; background-color: " + this.color
    }
    let parentLeft, parentTop
    if (!parent || parent == document.body && getComputedStyle(parent).position == "static") {
      parentLeft = -pageXOffset
      parentTop = -pageYOffset
    } else {
      let rect = parent.getBoundingClientRect()
      parentLeft = rect.left - parent.scrollLeft
      parentTop = rect.top - parent.scrollTop
    }
    this.element.style.left = (rect.left - parentLeft) + "px"
    this.element.style.top = (rect.top - parentTop) + "px"
    this.element.style.width = (rect.right - rect.left) + "px"
    this.element.style.height = (rect.bottom - rect.top) + "px"
  }

  scheduleRemoval(timeout) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => this.setCursor(null), timeout)
  }

  dragover(event) {
    if (!this.editorView.editable) return
    let pos = this.editorView.posAtCoords({left: event.clientX, top: event.clientY})

    const node = this.editorView.state.doc.nodeAt(pos.inside === -1 ? pos.pos : pos.inside)
    if (node && node.type.spec.disableDropCursor && this.predicate()) return

    if (pos) {
      let target = pos.pos
      if (this.editorView.dragging && this.editorView.dragging.slice) {
        target = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice)
        if (target == null) return this.setCursor(null)
      }
      this.setCursor(target)
      this.scheduleRemoval(5000)
    }
  }

  dragend() {
    this.scheduleRemoval(20)
  }

  drop() {
    this.scheduleRemoval(20)
  }

  dragleave(event) {
    if (event.target == this.editorView.dom || !this.editorView.dom.contains(event.relatedTarget))
      this.setCursor(null)
  }
}
<commit_msg>Shifts predicate responsibility to Node<commit_after>import {Plugin} from "prosemirror-state"
import {dropPoint} from "prosemirror-transform"

// :: (options: ?Object) → Plugin
// Create a plugin that, when added to a ProseMirror instance,
// causes a decoration to show up at the drop position when something
// is dragged over the editor.
//
//   options::- These options are supported:
//
//     color:: ?string
//     The color of the cursor. Defaults to `black`.
//
//     width:: ?number
//     The precise width of the cursor in pixels. Defaults to 1.
//
//     class:: ?string
//     A CSS class name to add to the cursor element.
export function dropCursor(options = {}) {
  return new Plugin({
    view(editorView) { return new DropCursorView(editorView, options) }
  })
}

class DropCursorView {
  constructor(editorView, options) {
    this.editorView = editorView
    this.width = options.width || 1
    this.color = options.color || "black"
    this.class = options.class
    this.cursorPos = null
    this.element = null
    this.timeout = null

    this.handlers = ["dragover", "dragend", "drop", "dragleave"].map(name => {
      let handler = e => this[name](e)
      editorView.dom.addEventListener(name, handler)
      return {name, handler}
    })
  }

  destroy() {
    this.handlers.forEach(({name, handler}) => this.editorView.dom.removeEventListener(name, handler))
  }

  update(editorView, prevState) {
    if (this.cursorPos != null && prevState.doc != editorView.state.doc) {
      if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null)
      else this.updateOverlay()
    }
  }

  setCursor(pos) {
    if (pos == this.cursorPos) return
    this.cursorPos = pos
    if (pos == null) {
      this.element.parentNode.removeChild(this.element)
      this.element = null
    } else {
      this.updateOverlay()
    }
  }

  updateOverlay() {
    let $pos = this.editorView.state.doc.resolve(this.cursorPos), rect
    if (!$pos.parent.inlineContent) {
      let before = $pos.nodeBefore, after = $pos.nodeAfter
      if (before || after) {
        let nodeRect = this.editorView.nodeDOM(this.cursorPos - (before ?  before.nodeSize : 0)).getBoundingClientRect()
        let top = before ? nodeRect.bottom : nodeRect.top
        if (before && after)
          top = (top + this.editorView.nodeDOM(this.cursorPos).getBoundingClientRect().top) / 2
        rect = {left: nodeRect.left, right: nodeRect.right, top: top - this.width / 2, bottom: top + this.width / 2}
      }
    }
    if (!rect) {
      let coords = this.editorView.coordsAtPos(this.cursorPos)
      rect = {left: coords.left - this.width / 2, right: coords.left + this.width / 2, top: coords.top, bottom: coords.bottom}
    }

    let parent = this.editorView.dom.offsetParent
    if (!this.element) {
      this.element = parent.appendChild(document.createElement("div"))
      if (this.class) this.element.className = this.class
      this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none; background-color: " + this.color
    }
    let parentLeft, parentTop
    if (!parent || parent == document.body && getComputedStyle(parent).position == "static") {
      parentLeft = -pageXOffset
      parentTop = -pageYOffset
    } else {
      let rect = parent.getBoundingClientRect()
      parentLeft = rect.left - parent.scrollLeft
      parentTop = rect.top - parent.scrollTop
    }
    this.element.style.left = (rect.left - parentLeft) + "px"
    this.element.style.top = (rect.top - parentTop) + "px"
    this.element.style.width = (rect.right - rect.left) + "px"
    this.element.style.height = (rect.bottom - rect.top) + "px"
  }

  scheduleRemoval(timeout) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => this.setCursor(null), timeout)
  }

  dragover(event) {
    if (!this.editorView.editable) return
    let pos = this.editorView.posAtCoords({left: event.clientX, top: event.clientY})

    const node = this.editorView.state.doc.nodeAt(pos.inside === -1 ? pos.pos : pos.inside);
    const disableDropCursor = (node && node.type.spec.disableDropCursor) ? node.type.spec.disableDropCursor : false;
    const isDropCursorDisabled = typeof disableDropCursor === 'function' ? disableDropCursor(this.editorView) : disableDropCursor;

    if (isDropCursorDisabled) return;

    if (pos) {
      let target = pos.pos
      if (this.editorView.dragging && this.editorView.dragging.slice) {
        target = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice)
        if (target == null) return this.setCursor(null)
      }
      this.setCursor(target)
      this.scheduleRemoval(5000)
    }
  }

  dragend() {
    this.scheduleRemoval(20)
  }

  drop() {
    this.scheduleRemoval(20)
  }

  dragleave(event) {
    if (event.target == this.editorView.dom || !this.editorView.dom.contains(event.relatedTarget))
      this.setCursor(null)
  }
}
<|endoftext|>


<commit_before>if (typeof window !== 'undefined') {

var _findInChildren = function ( element, elementToFind ) {
    for ( var i = 0; i < element.children.length; ++i ) {
        var childEL = element.children[i];
        if ( childEL === elementToFind )
            return true;

        if ( childEL.children.length > 0 )
            if ( _findInChildren( childEL, elementToFind ) )
                return true;
    }
    return false;
};

//
FIRE.find = function ( elements, elementToFind ) {
    if ( Array.isArray(elements) ||
         elements instanceof NodeList ||
         elements instanceof HTMLCollection )
    {
        for ( var i = 0; i < elements.length; ++i ) {
            var element = elements[i];
            if ( element === elementToFind )
                return true;

            if ( _findInChildren ( element, elementToFind ) )
                return true;
        }
        return false;
    }

    // if this is a single element
    if ( elements === elementToFind )
        return true;

    return _findInChildren( elements, elementToFind );
};

//
FIRE.getParentTabIndex = function ( element ) {
    var parent = element.parentElement;
    while ( parent ) {
        if ( parent.tabIndex !== null &&
             parent.tabIndex !== undefined &&
             parent.tabIndex !== -1 )
            return parent.tabIndex;

        parent = parent.parentElement;
    }
    return 0;
};

FIRE.getFirstFocusableChild = function ( element ) {
    if ( element.tabIndex !== null &&
         element.tabIndex !== undefined &&
         element.tabIndex !== -1 )
    {
        return element;
    }

    var el = null;
    for ( var i = 0; i < element.children.length; ++i ) {
        el = FIRE.getFirstFocusableChild(element.children[i]);
        if ( el !== null )
            return el;
    }
    if ( element.shadowRoot ) {
        el = FIRE.getFirstFocusableChild(element.shadowRoot);
        if ( el !== null )
            return el;
    }

    return null;
};

//
FIRE.getTrimRect = function (img, trimThreshold) {
    var canvas, ctx;
    if (img instanceof Image || img instanceof HTMLImageElement) {
        // create temp canvas
        canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;
        ctx = canvas.getContext('2d');
        ctx.drawImage( img, 0, 0, img.width, img.height );
    }
    else {
        canvas = img;
        ctx = canvas.getContext('2d');
    }
    var pixelBuffer = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

    // get trim
    return _doGetTrimRect(pixelBuffer, img.width, img.height, trimThreshold);
};

var _dragGhost = null;
FIRE.addDragGhost = function ( cursor ) {
    // add drag-ghost
    if ( _dragGhost === null ) {
        _dragGhost = document.createElement('div');
        _dragGhost.classList.add('drag-ghost');
        _dragGhost.style.position = 'fixed';
        _dragGhost.style.zIndex = '999';
        _dragGhost.style.left = '0';
        _dragGhost.style.top = '0';
        _dragGhost.style.width = window.innerWidth + 'px';
        _dragGhost.style.height = window.innerHeight + 'px';
        _dragGhost.oncontextmenu = function() { return false; };
    }
    _dragGhost.style.cursor = cursor;
    document.body.appendChild(_dragGhost);
};

FIRE.removeDragGhost = function () {
    if ( _dragGhost !== null ) {
        _dragGhost.style.cursor = 'auto';
        if ( _dragGhost.parentNode !== null ) {
            _dragGhost.parentNode.removeChild(_dragGhost);
        }
    }
};

var _readDir = function (dirReader, callback) {
    var onReadDir = function (entries) {
        if (!entries.length) {
            return; // readed
        }
        else {
            // recursive directory read
            _readEntries(entries, callback);

            // Keep calling readEntries() until no more results are returned.
            // This is needed to get all directory entries as one
            // call of readEntries may not return all items. Works a
            // bit like stream reader.
            _readDir(dirReader, callback);
        }
    };
    dirReader.readEntries(onReadDir);
};
// Recursive directory read
var _readEntries = function (entries, callback) {
    var files = [];
    var processingFile = 0;
    var onLoadFile = function (file) {
        --processingFile;
        files.push(file);
        if (processingFile === 0) {
            callback(files);
        }
    };
    var dirReader;
    for (var i = 0; i < entries.length; i++) {
        if (entries[i].isDirectory) {
            dirReader = entries[i].createReader();
            _readDir(dirReader, callback);
        }
        else {
            ++processingFile;
            entries[i].file(onLoadFile);
        }
    }
};

// 获得浏览器拖进来的文件，当包含文件夹时，callback将被多次调用
// recursive read all the files and (sub-)folders which dragged and dropped to browser
FIRE.getDraggingFiles = function (event, callback) {
    //var paths = [];
    //for (var i = 0; i < files.length; i++) {
    //    paths.push(files[i].path);
    //}
    //files = FIRE.readDirRecursively(paths);
    var items = event.dataTransfer.items;
    if (!items) {
        callback(event.dataTransfer.files);
        return;
    }
    var files = [];
    var entry;
    for (var i = 0; i < items.length; i++) {
        if (items[i].getAsEntry) {
            entry = items[i].getAsEntry();
        }
        else if (items[i].webkitGetAsEntry) {
            entry = items[i].webkitGetAsEntry();
        }
        else {
            entry = null;
        }
        if (entry !== null && entry.isDirectory) {
            _readEntries([entry], callback);
        }
        else {
            files.push(event.dataTransfer.files[i]);
        }
    }
    if (files.length > 0) {
        callback(files);
    }
};

// not supported by IE
var _downloadDataUrl = function (url, filename) {
    var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
    a.href = url;
    a.download = filename;
    a.click();
};

window.navigator.saveBlob = window.navigator.saveBlob || window.navigator.msSaveBlob;
window.URL = window.URL || window.webkitURL;

FIRE.downloadBlob = function (blob, filename) {
    if (window.navigator.saveBlob) {
        window.navigator.saveBlob(blob, filename);
    }
    else {
        var dataURL = window.URL.createObjectURL(blob);
        _downloadDataUrl(dataURL, filename);
        window.URL.revokeObjectURL(dataURL);    // Chrome中可立刻revokeObjectURL，其它浏览器需要进一步测试
    }
};

FIRE.downloadCanvas = function (canvas, filename) {
    canvas.toBlob = canvas.toBlob || canvas.msToBlob;
    if (canvas.toBlob && window.navigator.saveBlob) {
        window.navigator.saveBlob(canvas.toBlob(), filename);
    }
    else {
        var dataURL = canvas.toDataURL("image/png");
        _downloadDataUrl(dataURL, filename);
    }
};

FIRE.imgDataUrlToBase64 = function (dataUrl) {
    return dataUrl.replace(/^data:image\/\w+;base64,/, "");
};

}<commit_msg>move some dom function from core to editor-ui<commit_after>if (typeof window !== 'undefined') {

// TODO: move these functions to fireball-x/editor/utils

//
FIRE.getTrimRect = function (img, trimThreshold) {
    var canvas, ctx;
    if (img instanceof Image || img instanceof HTMLImageElement) {
        // create temp canvas
        canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;
        ctx = canvas.getContext('2d');
        ctx.drawImage( img, 0, 0, img.width, img.height );
    }
    else {
        canvas = img;
        ctx = canvas.getContext('2d');
    }
    var pixelBuffer = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

    // get trim
    return _doGetTrimRect(pixelBuffer, img.width, img.height, trimThreshold);
};

var _readDir = function (dirReader, callback) {
    var onReadDir = function (entries) {
        if (!entries.length) {
            return; // readed
        }
        else {
            // recursive directory read
            _readEntries(entries, callback);

            // Keep calling readEntries() until no more results are returned.
            // This is needed to get all directory entries as one
            // call of readEntries may not return all items. Works a
            // bit like stream reader.
            _readDir(dirReader, callback);
        }
    };
    dirReader.readEntries(onReadDir);
};
// Recursive directory read
var _readEntries = function (entries, callback) {
    var files = [];
    var processingFile = 0;
    var onLoadFile = function (file) {
        --processingFile;
        files.push(file);
        if (processingFile === 0) {
            callback(files);
        }
    };
    var dirReader;
    for (var i = 0; i < entries.length; i++) {
        if (entries[i].isDirectory) {
            dirReader = entries[i].createReader();
            _readDir(dirReader, callback);
        }
        else {
            ++processingFile;
            entries[i].file(onLoadFile);
        }
    }
};

// 获得浏览器拖进来的文件，当包含文件夹时，callback将被多次调用
// recursive read all the files and (sub-)folders which dragged and dropped to browser
FIRE.getDraggingFiles = function (event, callback) {
    //var paths = [];
    //for (var i = 0; i < files.length; i++) {
    //    paths.push(files[i].path);
    //}
    //files = FIRE.readDirRecursively(paths);
    var items = event.dataTransfer.items;
    if (!items) {
        callback(event.dataTransfer.files);
        return;
    }
    var files = [];
    var entry;
    for (var i = 0; i < items.length; i++) {
        if (items[i].getAsEntry) {
            entry = items[i].getAsEntry();
        }
        else if (items[i].webkitGetAsEntry) {
            entry = items[i].webkitGetAsEntry();
        }
        else {
            entry = null;
        }
        if (entry !== null && entry.isDirectory) {
            _readEntries([entry], callback);
        }
        else {
            files.push(event.dataTransfer.files[i]);
        }
    }
    if (files.length > 0) {
        callback(files);
    }
};

// not supported by IE
var _downloadDataUrl = function (url, filename) {
    var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
    a.href = url;
    a.download = filename;
    a.click();
};

window.navigator.saveBlob = window.navigator.saveBlob || window.navigator.msSaveBlob;
window.URL = window.URL || window.webkitURL;

FIRE.downloadBlob = function (blob, filename) {
    if (window.navigator.saveBlob) {
        window.navigator.saveBlob(blob, filename);
    }
    else {
        var dataURL = window.URL.createObjectURL(blob);
        _downloadDataUrl(dataURL, filename);
        window.URL.revokeObjectURL(dataURL);    // Chrome中可立刻revokeObjectURL，其它浏览器需要进一步测试
    }
};

FIRE.downloadCanvas = function (canvas, filename) {
    canvas.toBlob = canvas.toBlob || canvas.msToBlob;
    if (canvas.toBlob && window.navigator.saveBlob) {
        window.navigator.saveBlob(canvas.toBlob(), filename);
    }
    else {
        var dataURL = canvas.toDataURL("image/png");
        _downloadDataUrl(dataURL, filename);
    }
};

FIRE.imgDataUrlToBase64 = function (dataUrl) {
    return dataUrl.replace(/^data:image\/\w+;base64,/, "");
};

}
<|endoftext|>


<commit_before>"use strict";

import endpoint from "./endpoint";
import * as requests from "./requests";

/**
 * Abstract representation of a exhibition.
 */
export default class Exhibition {

  /**
   * Constructor.
   *
   * @param  {Tenant} tenant     The tenant instance.
   * @param  {String} exhibitionId  The exhibition id.
   */
  constructor(tenant, exhibitionId) {

    /**
     * The tenant.
     * @type {Tenant}
     */
    this.tenant = tenant;

    /**
     * The value id.
     * @type {String}
     */
    this.exhibitionId = exhibitionId;
  }

  /**
   * Retrieves exhibition.
   *
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  get(options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibition", this.exhibitionId)
      },
      options
    );
  }

  /**
   * Updates the value with the specified properties.
   *
   * @param {String} visibility     The exhibition visibility
   * @param  {Object} options       The options object.
   * @return {Promise<Object, Error>}
   */
  changeExhibitionVisibility(visibility, options = {}) {
    return this.tenant.execute(
      requests.changeExhibitionVisibility(this.exhibitionId, visibility),
      options
    );
  }

  /**
   * Updates the description of the exhibition.
   *
   * @param {String} name           The exhibition name.
   * @param {String} description    The exhibition description.
   * @param {String} pictureUri     The exhibition picture.
   * @param  {Object} options       The options object.
   * @return {Promise<Object, Error>}
   */
  update(name, description, pictureUri, options = {}) {
    return this.tenant.execute(
      requests.updateExhibition(this.exhibitionId, name, description, pictureUri),
      options
    );
  }

  /**
   * Delete current exhibition
   *
   * @param  {Object} options             The options object.
   * @returns {Promise.<Object, Error>}
   */
  remove(options = {}) {
    return this.tenant.execute(
      requests.deleteExhibition(this.exhibitionId),
      options
    );
  }

  /**
   * Retrieves exhibition clusters.
   *
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  getPublishedClusters(options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibitionClusters", this.exhibitionId)
      },
      options
    ).then((response) => {
          if (response["_embedded"]) {
            response["_embedded"]["be:exhibition_clusters"].forEach((cluster) => {
              cluster.users = response["_embedded"]["be:user"];
            });
            return response["_embedded"]["be:exhibition_clusters"];
          } else {
            return [];
          }
    });
  }

  /**
   * Retrieves exhibition cluster.
   * @param  {String} clusterHeadId      The id of the cluster head.
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  getPublishedCluster(clusterHeadId, options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibitionCluster", this.exhibitionId, clusterHeadId)
      },
      options
    ).then((response) => {
      response.users = response["_embedded"]["be:user"];
      return response;
    });
  }

  /**
   * Retrieves exhibition instances.
   *
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  getPublishedInstances(options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibitionInstances", this.exhibitionId)
      },
      options
    ).then((response) => {
      if (response["_embedded"]) {
        response["_embedded"]["be:instance"].forEach((instance) => {
          instance.users = response["_embedded"]["be:user"];
        });
        return response["_embedded"]["be:instance"];
      } else {
        return [];
      }
    });
  }

  /**
   * Retrieves exhibition instances.
   * @param  {String} instanceId      The id of the instance.
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  getPublishedInstance(instanceId, options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibitionInstance", this.exhibitionId, instanceId)
      },
      options
    ).then((response) => {
      response.users = response["_embedded"]["be:user"];
      return response;
    });
  }
}

<commit_msg>Correct indentation<commit_after>"use strict";

import endpoint from "./endpoint";
import * as requests from "./requests";

/**
 * Abstract representation of a exhibition.
 */
export default class Exhibition {

  /**
   * Constructor.
   *
   * @param  {Tenant} tenant     The tenant instance.
   * @param  {String} exhibitionId  The exhibition id.
   */
  constructor(tenant, exhibitionId) {

    /**
     * The tenant.
     * @type {Tenant}
     */
    this.tenant = tenant;

    /**
     * The value id.
     * @type {String}
     */
    this.exhibitionId = exhibitionId;
  }

  /**
   * Retrieves exhibition.
   *
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  get(options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibition", this.exhibitionId)
      },
      options
    );
  }

  /**
   * Updates the value with the specified properties.
   *
   * @param {String} visibility     The exhibition visibility
   * @param  {Object} options       The options object.
   * @return {Promise<Object, Error>}
   */
  changeExhibitionVisibility(visibility, options = {}) {
    return this.tenant.execute(
      requests.changeExhibitionVisibility(this.exhibitionId, visibility),
      options
    );
  }

  /**
   * Updates the description of the exhibition.
   *
   * @param {String} name           The exhibition name.
   * @param {String} description    The exhibition description.
   * @param {String} pictureUri     The exhibition picture.
   * @param  {Object} options       The options object.
   * @return {Promise<Object, Error>}
   */
  update(name, description, pictureUri, options = {}) {
    return this.tenant.execute(
      requests.updateExhibition(this.exhibitionId, name, description, pictureUri),
      options
    );
  }

  /**
   * Delete current exhibition
   *
   * @param  {Object} options             The options object.
   * @returns {Promise.<Object, Error>}
   */
  remove(options = {}) {
    return this.tenant.execute(
      requests.deleteExhibition(this.exhibitionId),
      options
    );
  }

  /**
   * Retrieves exhibition clusters.
   *
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  getPublishedClusters(options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibitionClusters", this.exhibitionId)
      },
      options
    ).then((response) =>
      {
        if (response["_embedded"]) {
          response["_embedded"]["be:exhibition_clusters"].forEach((cluster) => {
            cluster.users = response["_embedded"]["be:user"];
          });
          return response["_embedded"]["be:exhibition_clusters"];
        } else {
          return [];
        }
      });
  }

  /**
   * Retrieves exhibition cluster.
   * @param  {String} clusterHeadId      The id of the cluster head.
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  getPublishedCluster(clusterHeadId, options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibitionCluster", this.exhibitionId, clusterHeadId)
      },
      options
    ).then((response) => {
      response.users = response["_embedded"]["be:user"];
      return response;
    });
  }

  /**
   * Retrieves exhibition instances.
   *
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  getPublishedInstances(options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibitionInstances", this.exhibitionId)
      },
      options
    ).then((response) => {
      if (response["_embedded"]) {
        response["_embedded"]["be:instance"].forEach((instance) => {
          instance.users = response["_embedded"]["be:user"];
        });
        return response["_embedded"]["be:instance"];
      } else {
        return [];
      }
    });
  }

  /**
   * Retrieves exhibition instances.
   * @param  {String} instanceId      The id of the instance.
   * @param  {Object} options         The options object.
   * @return {Promise<Object, Error>}
   */
  getPublishedInstance(instanceId, options = {}) {
    return this.tenant.execute(
      {
        path: endpoint("exhibitionInstance", this.exhibitionId, instanceId)
      },
      options
    ).then((response) => {
      response.users = response["_embedded"]["be:user"];
      return response;
    });
  }
}

<|endoftext|>


<commit_before>// Vex Flow - Note head implementation
// Mohit Muthanna <mohit@muthanna.com>
//
// Copyright Mohit Muthanna 2010
//
// Requires vex.js.

Vex.Flow.NoteHead = (function() {
  var NoteHead = function(head_options) {
    if (arguments.length > 0) this.init(head_options);
  };

  // Draw slashnote head manually. No glyph exists for this.
  function drawSlashNoteHead(ctx, duration, x, y) {
    var width = 15 + (Vex.Flow.STEM_WIDTH / 2);
    ctx.setLineWidth(Vex.Flow.STEM_WIDTH);
    ctx.beginPath();
    ctx.moveTo(x, y + 11);
    ctx.lineTo(x, y + 1);
    ctx.lineTo(x + width, y - 10);
    ctx.lineTo(x + width, y);
    ctx.lineTo(x, y + 11);
    ctx.closePath();

    // only fill if quarter note or smaller
    if (duration != 1 &&
        duration != 2 &&
        duration != "h" &&
        duration != "w") {
      ctx.fill();
    } else {
      ctx.stroke();
    }
  }

  NoteHead.prototype = {
    init: function(head_options) {
      this.x = head_options.x || 0;
      this.y = head_options.y || 0;
      this.note_type = head_options.note_type;
      this.duration = head_options.duration;
      this.displaced = head_options.displaced || false;
      this.stem_direction = head_options.stem_direction || Vex.Flow.StaveNote.STEM_UP;

      // Get glyph code based on duration and note type. This could be
      // regular notes, rests, or other custom codes.
      this.glyph = Vex.Flow.durationToGlyph(this.duration, this.note_type);
      if (!this.glyph) {
        throw new Vex.RuntimeError("BadArguments",
            "No glyph found for duration '" + this.duration +
            "' and type '" + this.note_type + "'");
      }

      this.width = this.glyph.head_width;
      this.glyph_code = this.glyph.code_head;
      this.absolute_x = this.x + (this.displaced ? this.width * this.stem_direction : 0);
      if (head_options.custom_glyph_code) {
        this.glyph_code = head_options.custom_glyph_code;
        this.absolute_x = this.x + head_options.x_shift;
      }
      this.glyph_font_scale = head_options.glyph_font_scale;
      this.context = null;
      this.key_style = null;
      this.slashed = head_options.slashed;
    },

    getCategory: function() { return "notehead"; },
    setContext: function(context) { this.context = context; return this;},
    getWidth: function() { return this.width; },
    getAbsoluteX: function() { return this.absolute_x; },

    getBoundingBox: function() {
      throw new Vex.RERR("NotImplemented", "getBoundingBox() not implemented.");
    },

    applyKeyStyle: function(key_style, context) {
      if (key_style.shadowColor) context.setShadowColor(key_style.shadowColor);
      if (key_style.shadowBlur) context.setShadowBlur(key_style.shadowBlur);
      if (key_style.fillStyle) context.setFillStyle(key_style.fillStyle);
      if (key_style.strokeStyle) context.setStrokeStyle(key_style.strokeStyle);
    },

    draw: function() {
      if (!this.context) throw new Vex.RERR("NoCanvasContext",
          "Can't draw without a canvas context.");

      var ctx = this.context;
      var head_x = this.absolute_x;
      var y = this.y;

      // Begin and end positions for head.
      var stem_direction = this.stem_direction;
      var glyph_font_scale = this.glyph_font_scale;
      var key_style = this.key_style;

      if (this.note_type == "s") {
        var displacement = 0; // Vex.Flow.STEM_WIDTH / 2;
        drawSlashNoteHead(ctx, this.duration,
          head_x + (stem_direction == 1 ? -displacement : displacement), y);
      } else {
        if (key_style) {
          ctx.save();
          this.applyKeyStyle(key_style, ctx);
          Vex.Flow.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
          ctx.restore();
        } else {
          Vex.Flow.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
        }
      }
    }
  };

  return NoteHead;
}());
<commit_msg>Fixes key style bug<commit_after>// Vex Flow - Note head implementation
// Mohit Muthanna <mohit@muthanna.com>
//
// Copyright Mohit Muthanna 2010
//
// Requires vex.js.

Vex.Flow.NoteHead = (function() {
  var NoteHead = function(head_options) {
    if (arguments.length > 0) this.init(head_options);
  };

  // Draw slashnote head manually. No glyph exists for this.
  function drawSlashNoteHead(ctx, duration, x, y) {
    var width = 15 + (Vex.Flow.STEM_WIDTH / 2);
    ctx.setLineWidth(Vex.Flow.STEM_WIDTH);
    ctx.beginPath();
    ctx.moveTo(x, y + 11);
    ctx.lineTo(x, y + 1);
    ctx.lineTo(x + width, y - 10);
    ctx.lineTo(x + width, y);
    ctx.lineTo(x, y + 11);
    ctx.closePath();

    // only fill if quarter note or smaller
    if (duration != 1 &&
        duration != 2 &&
        duration != "h" &&
        duration != "w") {
      ctx.fill();
    } else {
      ctx.stroke();
    }
  }

  NoteHead.prototype = {
    init: function(head_options) {
      this.x = head_options.x || 0;
      this.y = head_options.y || 0;
      this.note_type = head_options.note_type;
      this.duration = head_options.duration;
      this.displaced = head_options.displaced || false;
      this.stem_direction = head_options.stem_direction || Vex.Flow.StaveNote.STEM_UP;

      // Get glyph code based on duration and note type. This could be
      // regular notes, rests, or other custom codes.
      this.glyph = Vex.Flow.durationToGlyph(this.duration, this.note_type);
      if (!this.glyph) {
        throw new Vex.RuntimeError("BadArguments",
            "No glyph found for duration '" + this.duration +
            "' and type '" + this.note_type + "'");
      }

      this.width = this.glyph.head_width;
      this.glyph_code = this.glyph.code_head;
      this.absolute_x = this.x + (this.displaced ? this.width * this.stem_direction : 0);
      if (head_options.custom_glyph_code) {
        this.glyph_code = head_options.custom_glyph_code;
        this.absolute_x = this.x + head_options.x_shift;
      }
      this.glyph_font_scale = head_options.glyph_font_scale;
      this.context = null;
      this.key_style = head_options.key_style;
      this.slashed = head_options.slashed;
    },

    getCategory: function() { return "notehead"; },
    setContext: function(context) { this.context = context; return this;},
    getWidth: function() { return this.width; },
    getAbsoluteX: function() { return this.absolute_x; },

    getBoundingBox: function() {
      throw new Vex.RERR("NotImplemented", "getBoundingBox() not implemented.");
    },

    applyKeyStyle: function(key_style, context) {
      if (key_style.shadowColor) context.setShadowColor(key_style.shadowColor);
      if (key_style.shadowBlur) context.setShadowBlur(key_style.shadowBlur);
      if (key_style.fillStyle) context.setFillStyle(key_style.fillStyle);
      if (key_style.strokeStyle) context.setStrokeStyle(key_style.strokeStyle);
    },

    draw: function() {
      if (!this.context) throw new Vex.RERR("NoCanvasContext",
          "Can't draw without a canvas context.");

      var ctx = this.context;
      var head_x = this.absolute_x;
      var y = this.y;

      // Begin and end positions for head.
      var stem_direction = this.stem_direction;
      var glyph_font_scale = this.glyph_font_scale;
      var key_style = this.key_style;

      if (this.note_type == "s") {
        var displacement = 0; // Vex.Flow.STEM_WIDTH / 2;
        drawSlashNoteHead(ctx, this.duration,
          head_x + (stem_direction == 1 ? -displacement : displacement), y);
      } else {
        if (key_style) {
          ctx.save();
          this.applyKeyStyle(key_style, ctx);
          Vex.Flow.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
          ctx.restore();
        } else {
          Vex.Flow.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
        }
      }
    }
  };

  return NoteHead;
}());
<|endoftext|>


<commit_before>"use strict";

/**
 * Format Bar
 * --
 * Displayed on focus on a text area.
 * Renders with all available options for the editor instance
 */

var _ = require('./lodash');

var config = require('./config');
var utils = require('./utils');
var Dom = require('./packages/dom');
var Events = require('./packages/events');

const FORMAT_BUTTON_TEMPLATE = require("./templates/format-button");

var FormatBar = function(options, mediator, editor) {
  this.editor = editor;
  this.options = Object.assign({}, config.defaults.formatBar, options || {});
  this.commands = this.options.commands;
  this.mediator = mediator;
  this.isShown = false;

  this._ensureElement();
  this._bindFunctions();
  this._bindMediatedEvents();

  this.initialize.apply(this, arguments);
};

Object.assign(FormatBar.prototype, require('./function-bind'), require('./mediated-events'), require('./events'), require('./renderable'), {

  className: 'st-format-bar',

  bound: ["onFormatButtonClick", "renderBySelection", "hide"],

  eventNamespace: 'formatter',

  mediatedEvents: {
    'position': 'renderBySelection',
    'show': 'show',
    'hide': 'hide'
  },

  initialize: function() {

    var buttons = this.commands.reduce(function(memo, format) {
      return memo += FORMAT_BUTTON_TEMPLATE(format);
    }, "");

    this.el.insertAdjacentHTML("beforeend", buttons);

    // We use mousedown rather than click as that allows us to keep focus on the contenteditable field.
    Events.delegate(this.el, '.st-format-btn', 'mousedown', this.onFormatButtonClick);
    Events.delegate(this.el, '.st-format-btn', 'click', (e) => e.preventDefault());
  },

  hide: function() {
    this.isShown = false;

    this.el.classList.remove('st-format-bar--is-ready');
    Dom.remove(this.el);
  },

  show: function() {
    if(this.isShown){
      return;
    }

    this.isShown = true;

    this.editor.outer.appendChild(this.el);
    this.el.classList.add('st-format-bar--is-ready');
  },

  remove: function(){ Dom.remove(this.el); },

  renderBySelection: function() {
    this.highlightSelectedButtons();
    this.show();
    this.calculatePosition();
  },

  calculatePosition: function() {
    var selection = window.getSelection(),
        range = selection.getRangeAt(0),
        boundary = range.getBoundingClientRect(),
        coords = {},
        outer = this.editor.outer,
        outerBoundary = outer.getBoundingClientRect();

    coords.top = (boundary.top - outerBoundary.top) + 'px';
    coords.left = (((boundary.left + boundary.right) / 2) -
      (this.el.offsetWidth / 2) - outerBoundary.left) + 'px';

    this.el.style.top = coords.top;
    this.el.style.left = coords.left;
  },

  highlightSelectedButtons: function() {
    var block = utils.getBlockBySelection();
    [].forEach.call(this.el.querySelectorAll(".st-format-btn"), function(btn) {
      var cmd = btn.getAttribute('data-cmd');
      btn.classList.toggle("st-format-btn--is-active",
                      block.queryTextBlockCommandState(cmd));
      btn = null;
    });
  },

  onFormatButtonClick: function(ev) {
    ev.preventDefault();
    ev.stopPropagation();

    var block = utils.getBlockBySelection();
    if (_.isUndefined(block)) {
      throw "Associated block not found";
    }

    var btn = ev.currentTarget,
        cmd = btn.getAttribute('data-cmd');

    if (_.isUndefined(cmd)) {
      return false;
    }

    block.execTextBlockCommand(cmd);

    this.highlightSelectedButtons();

    // Re-select the contenteditable field.
    document.activeElement.focus();

    return false;
  }

});

module.exports = FormatBar;
<commit_msg>Format bar ie fixes<commit_after>"use strict";

/**
 * Format Bar
 * --
 * Displayed on focus on a text area.
 * Renders with all available options for the editor instance
 */

var _ = require('./lodash');

var config = require('./config');
var utils = require('./utils');
var Dom = require('./packages/dom');
var Events = require('./packages/events');

const FORMAT_BUTTON_TEMPLATE = require("./templates/format-button");

var FormatBar = function(options, mediator, editor) {
  this.editor = editor;
  this.options = Object.assign({}, config.defaults.formatBar, options || {});
  this.commands = this.options.commands;
  this.mediator = mediator;
  this.isShown = false;

  this._ensureElement();
  this._bindFunctions();
  this._bindMediatedEvents();

  this.initialize.apply(this, arguments);
};

Object.assign(FormatBar.prototype, require('./function-bind'), require('./mediated-events'), require('./events'), require('./renderable'), {

  className: 'st-format-bar',

  bound: ["onFormatButtonClick", "renderBySelection", "hide"],

  eventNamespace: 'formatter',

  mediatedEvents: {
    'position': 'renderBySelection',
    'show': 'show',
    'hide': 'hide'
  },

  initialize: function() {

    var buttons = this.commands.reduce(function(memo, format) {
      return memo += FORMAT_BUTTON_TEMPLATE(format);
    }, "");

    this.el.insertAdjacentHTML("beforeend", buttons);

    // We use mousedown rather than click as that allows us to keep focus on the contenteditable field.
    Events.delegate(this.el, '.st-format-btn', 'mousedown', this.onFormatButtonClick);
    Events.delegate(this.el, '.st-format-btn', 'click', (e) => e.preventDefault());
  },

  hide: function() {
    this.block = undefined;
    this.isShown = false;

    this.el.classList.remove('st-format-bar--is-ready');
    Dom.remove(this.el);
  },

  show: function() {
    if(this.isShown){
      return;
    }

    this.isShown = true;

    this.editor.outer.appendChild(this.el);
    this.el.classList.add('st-format-bar--is-ready');
  },

  remove: function(){ Dom.remove(this.el); },

  renderBySelection: function(block) {
    this.block = block;
    this.highlightSelectedButtons();
    this.show();
    this.calculatePosition();
  },

  calculatePosition: function() {
    var selection = window.getSelection(),
        range = selection.getRangeAt(0),
        boundary = range.getBoundingClientRect(),
        coords = {},
        outer = this.editor.outer,
        outerBoundary = outer.getBoundingClientRect();

    coords.top = (boundary.top - outerBoundary.top) + 'px';
    coords.left = (((boundary.left + boundary.right) / 2) -
      (this.el.offsetWidth / 2) - outerBoundary.left) + 'px';

    this.el.style.top = coords.top;
    this.el.style.left = coords.left;
  },

  highlightSelectedButtons: function() {
    var block = utils.getBlockBySelection();
    [].forEach.call(this.el.querySelectorAll(".st-format-btn"), (btn) => {
      var cmd = btn.getAttribute('data-cmd');
      btn.classList.toggle("st-format-btn--is-active",
                      this.block.queryTextBlockCommandState(cmd));
      btn = null;
    });
  },

  onFormatButtonClick: function(ev) {
    ev.preventDefault();
    ev.stopPropagation();

    var block = utils.getBlockBySelection();
    if (_.isUndefined(this.block)) {
      throw "Associated block not found";
    }

    var btn = ev.currentTarget,
        cmd = btn.getAttribute('data-cmd');

    if (_.isUndefined(cmd)) {
      return false;
    }

    this.block.execTextBlockCommand(cmd);

    this.highlightSelectedButtons();

    // Re-select the contenteditable field.
    document.activeElement.focus();

    return false;
  }

});

module.exports = FormatBar;<|endoftext|>


<commit_before>var EventEmitter = require('../lib/eventemitter.js');
var Path = require('./path.js');
var Intercom = require('../lib/intercom.js');

/**
 * FSWatcher based on node.js' FSWatcher
 * see https://github.com/joyent/node/blob/master/lib/fs.js
 */
function FSWatcher() {
  EventEmitter.call(this);
  var self = this;
  var recursive = false;
  var recursivePathPrefix;
  var filename;

  function onchange(path) {
    // Watch for exact filename, or parent path when recursive is true.
    if(filename === path || (recursive && path.indexOf(recursivePathPrefix) === 0)) {
      self.trigger('change', 'change', path);
    }
  }

  // We support, but ignore the second arg, which node.js uses.
  self.start = function(filename_, persistent_, recursive_) {
    // Bail if we've already started (and therefore have a filename);
    if(filename) {
      return;
    }

    if(Path.isNull(filename_)) {
      throw new Error('Path must be a string without null bytes.');
    }

    // TODO: get realpath for symlinks on filename...

    // Filer's Path.normalize strips trailing slashes, which we use here.
    // See https://github.com/js-platform/filer/issues/105
    filename = Path.normalize(filename_);

    // Whether to watch beneath this path or not
    recursive = recursive_ === true;
    // If recursive, construct a path prefix portion for comparisons later
    // (i.e., '/path' becomes '/path/' so we can search within a filename for the
    // prefix). We also take care to allow for '/' on its own.
    if(recursive) {
      recursivePathPrefix = filename === '/' ? '/' : filename + '/';
    }

    var intercom = Intercom.getInstance();
    intercom.on('change', onchange);
  };

  self.close = function() {
    var intercom = Intercom.getInstance();
    intercom.off('change', onchange);
    self.removeAllListeners('change');
  };
}
FSWatcher.prototype = new EventEmitter();
FSWatcher.prototype.constructor = FSWatcher;

module.exports = FSWatcher;
<commit_msg>Fixed #707 Replaced var with const and let and added strict mode (#712)<commit_after>'using strict';

const EventEmitter = require('../lib/eventemitter.js');
const Path = require('./path.js');
const Intercom = require('../lib/intercom.js');

/**
 * FSWatcher based on node.js' FSWatcher
 * see https://github.com/joyent/node/blob/master/lib/fs.js
 */
function FSWatcher() {
  EventEmitter.call(this);
  const self = this;
  let recursive = false;
  let recursivePathPrefix;
  let filename;

  function onchange(path) {
    // Watch for exact filename, or parent path when recursive is true.
    if(filename === path || (recursive && path.indexOf(recursivePathPrefix) === 0)) {
      self.trigger('change', 'change', path);
    }
  }

  // We support, but ignore the second arg, which node.js uses.
  self.start = function(filename_, persistent_, recursive_) {
    // Bail if we've already started (and therefore have a filename);
    if(filename) {
      return;
    }

    if(Path.isNull(filename_)) {
      throw new Error('Path must be a string without null bytes.');
    }

    // TODO: get realpath for symlinks on filename...

    // Filer's Path.normalize strips trailing slashes, which we use here.
    // See https://github.com/js-platform/filer/issues/105
    filename = Path.normalize(filename_);

    // Whether to watch beneath this path or not
    recursive = recursive_ === true;
    // If recursive, construct a path prefix portion for comparisons later
    // (i.e., '/path' becomes '/path/' so we can search within a filename for the
    // prefix). We also take care to allow for '/' on its own.
    if(recursive) {
      recursivePathPrefix = filename === '/' ? '/' : filename + '/';
    }

    const intercom = Intercom.getInstance();
    intercom.on('change', onchange);
  };

  self.close = function() {
    const intercom = Intercom.getInstance();
    intercom.off('change', onchange);
    self.removeAllListeners('change');
  };
}
FSWatcher.prototype = new EventEmitter();
FSWatcher.prototype.constructor = FSWatcher;

module.exports = FSWatcher;
<|endoftext|>