<commit_before>/**
 * Module dependencies.
 */

var express = require("express");
var app = express();
var paper = require('paper');
paper.setup(new paper.Canvas(1920, 1080));
var socket = require('socket.io');
var ueberDB = require("ueberDB");
var db = new ueberDB.database("dirty", {"filename" : "var/dirty.db"});
var async = require('async');
var fs = require('fs');

app.configure(function(){
  app.use(express.static(__dirname + '/'));
});

/**
 * A setting, just one
 */

var port = 3000;

/** Below be dragons
 *
 */

// SESSIONS
app.use(express.cookieParser());
app.use(express.session({secret: 'secret', key: 'express.sid'}));

// DEV MODE
app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

// PRODUCTON MODE
app.configure('production', function(){
  app.use(express.errorHandler());
});

// ROUTES
// Index page
app.get('/', function(req, res){
  res.sendfile(__dirname + '/src/static/html/index.html');
});

// Drawings
app.get('/d/*', function(req, res){
  res.sendfile(__dirname + '/src/static/html/draw.html');
});

// Front-end tests
app.get('/tests/frontend/specs_list.js', function(req, res){

  async.parallel({
    coreSpecs: function(callback){
      exports.getCoreTests(callback);
    },
    pluginSpecs: function(callback){
      exports.getPluginTests(callback);
    }
  },
  function(err, results){
    var files = results.coreSpecs; // push the core specs to a file object
    files = files.concat(results.pluginSpecs); // add the plugin Specs to the core specs
    //console.debug("Sent browser the following test specs:", files.sort());
    console.log("Sent browser the following test specs:", files.sort());
    res.send("var specs_list = " + JSON.stringify(files.sort()) + ";\n");
  });

});

// Used for front-end tests
var url2FilePath = function(url){
  var subPath = url.substr("/tests/frontend".length);
  if (subPath == ""){
    subPath = "index.html"
  }
  subPath = subPath.split("?")[0];

  var filePath = path.normalize(npm.root + "/../tests/frontend/")
  filePath += subPath.replace("..", "");
  return filePath;
}

// Used for front-end tests
app.get('/tests/frontend/specs/*', function (req, res) {
  var specFilePath = url2FilePath(req.url);
  var specFileName = path.basename(specFilePath);

  fs.readFile(specFilePath, function(err, content){
    if(err){ return res.send(500); }

    content = "describe(" + JSON.stringify(specFileName) + ", function(){   " + content + "   });";

    res.send(content);
  });
});

// Used for front-end tests
app.get('/tests/frontend/*', function (req, res) {
  var filePath = url2FilePath(req.url);
  res.sendfile(filePath);
});

// Used for front-end tests
app.get('/tests/frontend', function (req, res) {
  res.redirect('/tests/frontend/');
});

// Used for front-end tests
exports.getPluginTests = function(callback){
  var pluginSpecs = [];
  var plugins = fs.readdirSync('node_modules');
  plugins.forEach(function(plugin){
    if(fs.existsSync("node_modules/"+plugin+"/static/tests/frontend/specs")){ // if plugins exists
      var specFiles = fs.readdirSync("node_modules/"+plugin+"/static/tests/frontend/specs/");
      async.forEach(specFiles, function(spec){ // for each specFile push it to pluginSpecs
         pluginSpecs.push("/static/plugins/"+plugin+"/static/tests/frontend/specs/" + spec);
      },
      function(err){
         // blow up if something bad happens!
      });
    }
  });
  callback(null, pluginSpecs);
}

// Used for front-end tests
exports.getCoreTests = function(callback){
  fs.readdir('tests/frontend/specs', function(err, coreSpecs){ // get the core test specs
    if(err){ return res.send(500); }
    callback(null, coreSpecs);
  });
}

// Static files IE Javascript and CSS
app.use("/static", express.static(__dirname + '/src/static'));

// LISTEN FOR REQUESTS
var server = app.listen(port);
var io = socket.listen(server);

// SOCKET IO
io.sockets.on('connection', function (socket) {

  socket.on('disconnect', function () {
    disconnect(socket);
  });

  // EVENT: User stops drawing something
  // Having room as a parameter is not good for secure rooms
  socket.on('draw:progress', function (room, uid, co_ordinates) {
    if (!projects[room] || !projects[room].project) {
      loadError(socket);
      return;
    }
    io.sockets.in(room).emit('draw:progress', uid, co_ordinates);
    progress_external_path(room, JSON.parse(co_ordinates), uid);
  });

  // EVENT: User stops drawing something
  // Having room as a parameter is not good for secure rooms
  socket.on('draw:end', function (room, uid, co_ordinates) {
    if (!projects[room] || !projects[room].project) {
      loadError(socket);
      return;
    }
    io.sockets.in(room).emit('draw:end', uid, co_ordinates);
    end_external_path(room, JSON.parse(co_ordinates), uid);
  });

  // User joins a room
  socket.on('subscribe', function(data) {
    subscribe(socket, data);
  });

  // User clears canvas
  socket.on('canvas:clear', function(room) {
    if (!projects[room] || !projects[room].project) {
      loadError(socket);
      return;
    }
    clearCanvas(room);
    io.sockets.in(room).emit('canvas:clear');
  });

  // User removes an item
  socket.on('item:remove', function(room, uid, itemName) {
    removeItem(room, uid, itemName);
  });

  // User moves one or more items on their canvas - progress
  socket.on('item:move:progress', function(room, uid, itemNames, delta) {
    moveItemsProgress(room, uid, itemNames, delta);
  });

  // User moves one or more items on their canvas - end
  socket.on('item:move:end', function(room, uid, itemNames, delta) {
    moveItemsEnd(room, uid, itemNames, delta);
  });

  // User adds a raster image
  socket.on('image:add', function(room, uid, data, position, name) {
    addImage(room, uid, data, position, name);
  });

});

var projects = {};
var closeTimer = {}; // setTimeout function for closing a project when
// there are no active connections
// Subscribe a client to a room
function subscribe(socket, data) {
  var room = data.room;

  // Subscribe the client to the room
  socket.join(room);

  // If the close timer is set, cancel it
  if (closeTimer[room]) {
    clearTimeout(closeTimer[room]);
  }

  // Create Paperjs instance for this room if it doesn't exist
  var project = projects[room];
  if (!project) {
    projects[room] = {};
    // Use the view from the default project. This project is the default
    // one created when paper is instantiated. Nothing is ever written to
    // this project as each room has its own project. We share the View
    // object but that just helps it "draw" stuff to the invisible server
    // canvas.
    projects[room].project = new paper.Project(paper.projects[0].view);
    projects[room].external_paths = {};
    loadFromDB(room, socket);
  } else { // Project exists in memory, no need to load from database
    loadFromMemory(room, socket);
  }

  // Broadcast to room the new user count
  var active_connections = io.sockets.manager.rooms['/' + room].length;
  io.sockets.in(room).emit('user:connect', active_connections);

}

// Try to load room from database
function loadFromDB(room, socket) {
  if (projects[room] && projects[room].project) {
    var project = projects[room].project;
    db.init(function (err) {
      if(err) {
        console.error(err);
      }
      db.get(room, function(err, value) {
        if (value && project && project instanceof paper.Project && project.activeLayer) {
          socket.emit('loading:start');
          // Clear default layer as importing JSON adds a new layer.
          // We want the project to always only have one layer.
          project.activeLayer.remove();
          project.importJSON(value.project);
          socket.emit('project:load', value);
        }
        socket.emit('loading:end');
        db.close(function(){});
      });
    });
  } else {
    loadError(socket);
  }
}

// Send current project to new client
function loadFromMemory(room, socket) {
  var project = projects[room].project;
  if (!project) { // Additional backup check, just in case
    loadFromDB(room, socket);
    return;
  }
  socket.emit('loading:start');
  var value = project.exportJSON();
  socket.emit('project:load', {project: value});
  socket.emit('loading:end');
}

// When a client disconnects, unsubscribe him from
// the rooms he subscribed to
function disconnect(socket) {
  // Get a list of rooms for the client
  var rooms = io.sockets.manager.roomClients[socket.id];

  // Unsubscribe from the rooms
  for(var room in rooms) {
    if(room && rooms[room]) {
      unsubscribe(socket, { room: room.replace('/','') });
    }
  }

}

// Unsubscribe a client from a room
function unsubscribe(socket, data) {
  var room = data.room;

  // Remove the client from socket.io room
  // This is optional for the disconnect event, we do it anyway
  // because we want to broadcast the new room population
  socket.leave(room);

  // Broadcast to room the new user count
  if (io.sockets.manager.rooms['/' + room]) {
    var active_connections = io.sockets.manager.rooms['/' + room].length;
    io.sockets.in(room).emit('user:disconnect', active_connections);
  } else {

    // Wait a few seconds before closing the project to finish pending writes to pad
    closeTimer[room] = setTimeout(function() {
      // Iff no one left in room, remove Paperjs instance
      // from the array to free up memory
      var project = projects[room].project;
      // All projects share one View, calling remove() on one project destroys the View
      // for all projects. Set to false first.
      project.view = false;
      project.remove();
      projects[room] = undefined;
    }, 5000);
  }

}

function loadError(socket) {
  socket.emit('project:load:error');
}

// Ends a path
var end_external_path = function (room, points, artist) {

  var project = projects[room].project;
  project.activate();
  var path = projects[room].external_paths[artist];

  if (path) {

    // Close the path
    path.add(new paper.Point(points.end[1], points.end[2]));
    path.closed = true;
    path.smooth();
    project.view.draw();

    // Remove the old data
    projects[room].external_paths[artist] = false;

  }

  writeProjectToDB(room);
};

// Continues to draw a path in real time
progress_external_path = function (room, points, artist) {

  var project = projects[room].project;
  project.activate();
  var path = projects[room].external_paths[artist];

  // The path hasn't already been started
  // So start it
  if (!path) {

    projects[room].external_paths[artist] = new paper.Path();
    path = projects[room].external_paths[artist];

    // Starts the path
    var start_point = new paper.Point(points.start[1], points.start[2]);
    var color = new paper.Color(points.rgba.red, points.rgba.green, points.rgba.blue, points.rgba.opacity);
    path.fillColor = color;
    path.name = points.name;
    path.add(start_point);

  }

  // Draw all the points along the length of the path
  var paths = points.path;
  var length = paths.length;
  for (var i = 0; i < length; i++) {

    path.add(new paper.Point(paths[i].top[1], paths[i].top[2]));
    path.insert(0, new paper.Point(paths[i].bottom[1], paths[i].bottom[2]));

  }

  path.smooth();
  project.view.draw();

};

function writeProjectToDB(room) {
  var project = projects[room].project;
  var json = project.exportJSON();
  db.init(function (err) {
    if(err) {
      console.error(err);
    }
    db.set(room, {project: json});
  });
}

function clearCanvas(room) {
  var project = projects[room].project;

  if (project && project.activeLayer && project.activeLayer.hasChildren()) {
    // Remove all but the active layer
    if (project.layers.length > 1) {
      var activeLayerID = project.activeLayer._id;
      for (var i=0; i<project.layers.length; i++) {
        if (project.layers[i]._id != activeLayerID) {
          project.layers[i].remove();
          i--;
        }
      }
    }
    // Remove all of the children from the active layer
    if (paper.project.activeLayer && paper.project.activeLayer.hasChildren()) {
      paper.project.activeLayer.removeChildren();
    }
    writeProjectToDB(room);
  }
}

// Remove an item from the canvas
function removeItem(room, artist, itemName) {
  var project = projects[room].project;
  if (project && project.activeLayer && project.activeLayer._namedChildren[itemName] && project.activeLayer._namedChildren[itemName][0]) {
    project.activeLayer._namedChildren[itemName][0].remove();
    io.sockets.in(room).emit('item:remove', artist, itemName);
    writeProjectToDB(room);
  }
}

// Move one or more existing items on the canvas
function moveItemsProgress(room, artist, itemNames, delta) {
  var project = projects[room].project;
  if (project && project.activeLayer) {
    for (x in itemNames) {
      var itemName = itemNames[x];
      var namedChildren = project.activeLayer._namedChildren;
      if (namedChildren && namedChildren[itemName] && namedChildren[itemName][0]) {
        project.activeLayer._namedChildren[itemName][0].position.x += delta[1];
        project.activeLayer._namedChildren[itemName][0].position.y += delta[2];
      }
    }
    if (itemNames) {
      io.sockets.in(room).emit('item:move', artist, itemNames, delta);
    }
  }
}

// Move one or more existing items on the canvas
// and write to DB
function moveItemsEnd(room, artist, itemNames, delta) {
  var project = projects[room].project;
  if (project && project.activeLayer) {
    for (x in itemNames) {
      var itemName = itemNames[x];
      var namedChildren = project.activeLayer._namedChildren;
      if (namedChildren && namedChildren[itemName] && namedChildren[itemName][0]) {
        project.activeLayer._namedChildren[itemName][0].position.x += delta[1];
        project.activeLayer._namedChildren[itemName][0].position.y += delta[2];
      }
    }
    if (itemNames) {
      io.sockets.in(room).emit('item:move', artist, itemNames, delta);
    }
    writeProjectToDB(room);
  }
}

// Add image to canvas
function addImage(room, artist, data, position, name) {
  var project = projects[room].project;
  if (project && project.activeLayer) {
    var image = JSON.parse(data);
    var raster = new paper.Raster(image);
    raster.position = new paper.Point(position[1], position[2]);
    raster.name = name;
    io.sockets.in(room).emit('image:add', artist, data, position, name);
    writeProjectToDB(room);
  }
}<commit_msg>Added extra check for undefined variables<commit_after>/**
 * Module dependencies.
 */

var express = require("express");
var app = express();
var paper = require('paper');
paper.setup(new paper.Canvas(1920, 1080));
var socket = require('socket.io');
var ueberDB = require("ueberDB");
var db = new ueberDB.database("dirty", {"filename" : "var/dirty.db"});
var async = require('async');
var fs = require('fs');

app.configure(function(){
  app.use(express.static(__dirname + '/'));
});

/**
 * A setting, just one
 */

var port = 3000;

/** Below be dragons
 *
 */

// SESSIONS
app.use(express.cookieParser());
app.use(express.session({secret: 'secret', key: 'express.sid'}));

// DEV MODE
app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

// PRODUCTON MODE
app.configure('production', function(){
  app.use(express.errorHandler());
});

// ROUTES
// Index page
app.get('/', function(req, res){
  res.sendfile(__dirname + '/src/static/html/index.html');
});

// Drawings
app.get('/d/*', function(req, res){
  res.sendfile(__dirname + '/src/static/html/draw.html');
});

// Front-end tests
app.get('/tests/frontend/specs_list.js', function(req, res){

  async.parallel({
    coreSpecs: function(callback){
      exports.getCoreTests(callback);
    },
    pluginSpecs: function(callback){
      exports.getPluginTests(callback);
    }
  },
  function(err, results){
    var files = results.coreSpecs; // push the core specs to a file object
    files = files.concat(results.pluginSpecs); // add the plugin Specs to the core specs
    //console.debug("Sent browser the following test specs:", files.sort());
    console.log("Sent browser the following test specs:", files.sort());
    res.send("var specs_list = " + JSON.stringify(files.sort()) + ";\n");
  });

});

// Used for front-end tests
var url2FilePath = function(url){
  var subPath = url.substr("/tests/frontend".length);
  if (subPath == ""){
    subPath = "index.html"
  }
  subPath = subPath.split("?")[0];

  var filePath = path.normalize(npm.root + "/../tests/frontend/")
  filePath += subPath.replace("..", "");
  return filePath;
}

// Used for front-end tests
app.get('/tests/frontend/specs/*', function (req, res) {
  var specFilePath = url2FilePath(req.url);
  var specFileName = path.basename(specFilePath);

  fs.readFile(specFilePath, function(err, content){
    if(err){ return res.send(500); }

    content = "describe(" + JSON.stringify(specFileName) + ", function(){   " + content + "   });";

    res.send(content);
  });
});

// Used for front-end tests
app.get('/tests/frontend/*', function (req, res) {
  var filePath = url2FilePath(req.url);
  res.sendfile(filePath);
});

// Used for front-end tests
app.get('/tests/frontend', function (req, res) {
  res.redirect('/tests/frontend/');
});

// Used for front-end tests
exports.getPluginTests = function(callback){
  var pluginSpecs = [];
  var plugins = fs.readdirSync('node_modules');
  plugins.forEach(function(plugin){
    if(fs.existsSync("node_modules/"+plugin+"/static/tests/frontend/specs")){ // if plugins exists
      var specFiles = fs.readdirSync("node_modules/"+plugin+"/static/tests/frontend/specs/");
      async.forEach(specFiles, function(spec){ // for each specFile push it to pluginSpecs
         pluginSpecs.push("/static/plugins/"+plugin+"/static/tests/frontend/specs/" + spec);
      },
      function(err){
         // blow up if something bad happens!
      });
    }
  });
  callback(null, pluginSpecs);
}

// Used for front-end tests
exports.getCoreTests = function(callback){
  fs.readdir('tests/frontend/specs', function(err, coreSpecs){ // get the core test specs
    if(err){ return res.send(500); }
    callback(null, coreSpecs);
  });
}

// Static files IE Javascript and CSS
app.use("/static", express.static(__dirname + '/src/static'));

// LISTEN FOR REQUESTS
var server = app.listen(port);
var io = socket.listen(server);

// SOCKET IO
io.sockets.on('connection', function (socket) {

  socket.on('disconnect', function () {
    disconnect(socket);
  });

  // EVENT: User stops drawing something
  // Having room as a parameter is not good for secure rooms
  socket.on('draw:progress', function (room, uid, co_ordinates) {
    if (!projects[room] || !projects[room].project) {
      loadError(socket);
      return;
    }
    io.sockets.in(room).emit('draw:progress', uid, co_ordinates);
    progress_external_path(room, JSON.parse(co_ordinates), uid);
  });

  // EVENT: User stops drawing something
  // Having room as a parameter is not good for secure rooms
  socket.on('draw:end', function (room, uid, co_ordinates) {
    if (!projects[room] || !projects[room].project) {
      loadError(socket);
      return;
    }
    io.sockets.in(room).emit('draw:end', uid, co_ordinates);
    end_external_path(room, JSON.parse(co_ordinates), uid);
  });

  // User joins a room
  socket.on('subscribe', function(data) {
    subscribe(socket, data);
  });

  // User clears canvas
  socket.on('canvas:clear', function(room) {
    if (!projects[room] || !projects[room].project) {
      loadError(socket);
      return;
    }
    clearCanvas(room);
    io.sockets.in(room).emit('canvas:clear');
  });

  // User removes an item
  socket.on('item:remove', function(room, uid, itemName) {
    removeItem(room, uid, itemName);
  });

  // User moves one or more items on their canvas - progress
  socket.on('item:move:progress', function(room, uid, itemNames, delta) {
    moveItemsProgress(room, uid, itemNames, delta);
  });

  // User moves one or more items on their canvas - end
  socket.on('item:move:end', function(room, uid, itemNames, delta) {
    moveItemsEnd(room, uid, itemNames, delta);
  });

  // User adds a raster image
  socket.on('image:add', function(room, uid, data, position, name) {
    addImage(room, uid, data, position, name);
  });

});

var projects = {};
var closeTimer = {}; // setTimeout function for closing a project when
// there are no active connections
// Subscribe a client to a room
function subscribe(socket, data) {
  var room = data.room;

  // Subscribe the client to the room
  socket.join(room);

  // If the close timer is set, cancel it
  if (closeTimer[room]) {
    clearTimeout(closeTimer[room]);
  }

  // Create Paperjs instance for this room if it doesn't exist
  var project = projects[room];
  if (!project) {
    projects[room] = {};
    // Use the view from the default project. This project is the default
    // one created when paper is instantiated. Nothing is ever written to
    // this project as each room has its own project. We share the View
    // object but that just helps it "draw" stuff to the invisible server
    // canvas.
    projects[room].project = new paper.Project(paper.projects[0].view);
    projects[room].external_paths = {};
    loadFromDB(room, socket);
  } else { // Project exists in memory, no need to load from database
    loadFromMemory(room, socket);
  }

  // Broadcast to room the new user count
  var active_connections = io.sockets.manager.rooms['/' + room].length;
  io.sockets.in(room).emit('user:connect', active_connections);

}

// Try to load room from database
function loadFromDB(room, socket) {
  if (projects[room] && projects[room].project) {
    var project = projects[room].project;
    db.init(function (err) {
      if(err) {
        console.error(err);
      }
      db.get(room, function(err, value) {
        if (value && project && project instanceof paper.Project && project.activeLayer) {
          socket.emit('loading:start');
          // Clear default layer as importing JSON adds a new layer.
          // We want the project to always only have one layer.
          project.activeLayer.remove();
          project.importJSON(value.project);
          socket.emit('project:load', value);
        }
        socket.emit('loading:end');
        db.close(function(){});
      });
    });
  } else {
    loadError(socket);
  }
}

// Send current project to new client
function loadFromMemory(room, socket) {
  var project = projects[room].project;
  if (!project) { // Additional backup check, just in case
    loadFromDB(room, socket);
    return;
  }
  socket.emit('loading:start');
  var value = project.exportJSON();
  socket.emit('project:load', {project: value});
  socket.emit('loading:end');
}

// When a client disconnects, unsubscribe him from
// the rooms he subscribed to
function disconnect(socket) {
  // Get a list of rooms for the client
  var rooms = io.sockets.manager.roomClients[socket.id];

  // Unsubscribe from the rooms
  for(var room in rooms) {
    if(room && rooms[room]) {
      unsubscribe(socket, { room: room.replace('/','') });
    }
  }

}

// Unsubscribe a client from a room
function unsubscribe(socket, data) {
  var room = data.room;

  // Remove the client from socket.io room
  // This is optional for the disconnect event, we do it anyway
  // because we want to broadcast the new room population
  socket.leave(room);

  // Broadcast to room the new user count
  if (io.sockets.manager.rooms['/' + room]) {
    var active_connections = io.sockets.manager.rooms['/' + room].length;
    io.sockets.in(room).emit('user:disconnect', active_connections);
  } else {

    // Wait a few seconds before closing the project to finish pending writes to pad
    closeTimer[room] = setTimeout(function() {
      // Iff no one left in room, remove Paperjs instance
      // from the array to free up memory
      var project = projects[room].project;
      // All projects share one View, calling remove() on one project destroys the View
      // for all projects. Set to false first.
      project.view = false;
      project.remove();
      projects[room] = undefined;
    }, 5000);
  }

}

function loadError(socket) {
  socket.emit('project:load:error');
}

// Ends a path
var end_external_path = function (room, points, artist) {

  var project = projects[room].project;
  project.activate();
  var path = projects[room].external_paths[artist];

  if (path) {

    // Close the path
    path.add(new paper.Point(points.end[1], points.end[2]));
    path.closed = true;
    path.smooth();
    project.view.draw();

    // Remove the old data
    projects[room].external_paths[artist] = false;

  }

  writeProjectToDB(room);
};

// Continues to draw a path in real time
progress_external_path = function (room, points, artist) {

  var project = projects[room].project;
  project.activate();
  var path = projects[room].external_paths[artist];

  // The path hasn't already been started
  // So start it
  if (!path) {

    projects[room].external_paths[artist] = new paper.Path();
    path = projects[room].external_paths[artist];

    // Starts the path
    var start_point = new paper.Point(points.start[1], points.start[2]);
    var color = new paper.Color(points.rgba.red, points.rgba.green, points.rgba.blue, points.rgba.opacity);
    path.fillColor = color;
    path.name = points.name;
    path.add(start_point);

  }

  // Draw all the points along the length of the path
  var paths = points.path;
  var length = paths.length;
  for (var i = 0; i < length; i++) {

    path.add(new paper.Point(paths[i].top[1], paths[i].top[2]));
    path.insert(0, new paper.Point(paths[i].bottom[1], paths[i].bottom[2]));

  }

  path.smooth();
  project.view.draw();

};

function writeProjectToDB(room) {
  var project = projects[room].project;
  var json = project.exportJSON();
  db.init(function (err) {
    if(err) {
      console.error(err);
    }
    db.set(room, {project: json});
  });
}

function clearCanvas(room) {
  var project = projects[room].project;

  if (project && project.activeLayer && project.activeLayer.hasChildren()) {
    // Remove all but the active layer
    if (project.layers.length > 1) {
      var activeLayerID = project.activeLayer._id;
      for (var i=0; i<project.layers.length; i++) {
        if (project.layers[i]._id != activeLayerID) {
          project.layers[i].remove();
          i--;
        }
      }
    }
    // Remove all of the children from the active layer
    if (project && project.activeLayer && project.activeLayer.hasChildren()) {
      project.activeLayer.removeChildren();
    }
    writeProjectToDB(room);
  }
}

// Remove an item from the canvas
function removeItem(room, artist, itemName) {
  var project = projects[room].project;
  if (project && project.activeLayer && project.activeLayer._namedChildren[itemName] && project.activeLayer._namedChildren[itemName][0]) {
    project.activeLayer._namedChildren[itemName][0].remove();
    io.sockets.in(room).emit('item:remove', artist, itemName);
    writeProjectToDB(room);
  }
}

// Move one or more existing items on the canvas
function moveItemsProgress(room, artist, itemNames, delta) {
  var project = projects[room].project;
  if (project && project.activeLayer) {
    for (x in itemNames) {
      var itemName = itemNames[x];
      var namedChildren = project.activeLayer._namedChildren;
      if (namedChildren && namedChildren[itemName] && namedChildren[itemName][0]) {
        project.activeLayer._namedChildren[itemName][0].position.x += delta[1];
        project.activeLayer._namedChildren[itemName][0].position.y += delta[2];
      }
    }
    if (itemNames) {
      io.sockets.in(room).emit('item:move', artist, itemNames, delta);
    }
  }
}

// Move one or more existing items on the canvas
// and write to DB
function moveItemsEnd(room, artist, itemNames, delta) {
  var project = projects[room].project;
  if (project && project.activeLayer) {
    for (x in itemNames) {
      var itemName = itemNames[x];
      var namedChildren = project.activeLayer._namedChildren;
      if (namedChildren && namedChildren[itemName] && namedChildren[itemName][0]) {
        project.activeLayer._namedChildren[itemName][0].position.x += delta[1];
        project.activeLayer._namedChildren[itemName][0].position.y += delta[2];
      }
    }
    if (itemNames) {
      io.sockets.in(room).emit('item:move', artist, itemNames, delta);
    }
    writeProjectToDB(room);
  }
}

// Add image to canvas
function addImage(room, artist, data, position, name) {
  var project = projects[room].project;
  if (project && project.activeLayer) {
    var image = JSON.parse(data);
    var raster = new paper.Raster(image);
    raster.position = new paper.Point(position[1], position[2]);
    raster.name = name;
    io.sockets.in(room).emit('image:add', artist, data, position, name);
    writeProjectToDB(room);
  }
}<|endoftext|>


<commit_before>/* TinyMCE pattern.
 *
 * Options:
 *    relatedItems(object): Related items pattern options. ({ attributes: ["UID", "Title", "Description", "getURL", "Type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
 *    upload(object): Upload pattern options. ({ attributes: look at upload pattern for getting the options list })
 *    text(object): Translation strings ({ insertBtn: "Insert", cancelBtn: "Cancel", insertHeading: "Insert link", title: "Title", internal: "Internal", external: "External", email: "Email", anchor: "Anchor", subject: "Subject" image: "Image", imageAlign: "Align", scale: "Size", alt: "Alternative Text", externalImage: "External Image URI"})
 *    scales(string): TODO: is this even used ('Listing (16x16):listing,Icon (32x32):icon,Tile (64x64):tile,Thumb (128x128):thumb,Mini (200x200):mini,Preview (400x400):preview,Large (768x768):large')
 *    targetList(array): TODO ([ {text: "Open in this window / frame", value: ""}, {text: "Open in new window", value: "_blank"}, {text: "Open in parent window / frame", value: "_parent"}, {text: "Open in top frame (replaces all frames)", value: "_top"}])
 * imageTypes(string): TODO ('Image')
 *    folderTypes(string): TODO ('Folder,Plone Site')
 *    linkableTypes(string): TODO ('Document,Event,File,Folder,Image,News Item,Topic')
 *    tiny(object): TODO ({ plugins: [ "advlist autolink lists charmap print preview anchor", "usearchreplace visualblocks code fullscreen autoresize", "insertdatetime media table contextmenu paste plonelink ploneimage" ], menubar: "edit table format tools view insert",
toolbar: "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | unlink plonelink ploneimage", autoresize_max_height: 1500 })
 *    prependToUrl(string): Text to prepend to generated internal urls. ('')
 *    appendToUrl(string): Text to append to generated internal urls. ('')
 *    prependToScalePart(string): Text to prepend to generated image scale url part. ('/imagescale/')
 *    appendToScalePart(string): Text to append to generated image scale url part. ('')
 *    linkAttribute(string): Ajax response data attribute to use for url. ('path')
 *
 * Documentation:
 *    # Default
 *
 *    {{ example-1 }}
 *
 *    # With dropzone
 *
 *    {{ example-2 }}
 *
 * Example: example-1
 *    <form>
 *      <textarea class="pat-tinymce"
 *          data-pat-tinymce='{"relatedItems": {
 *                                "vocabularyUrl": "/relateditems-test.json"
 *                                }}'></textarea>
 *    </form>
 *
 * Example: example-2
 *    <form>
 *      <textarea class="pat-tinymce"
 *          data-pat-tinymce='{"relatedItems": {"vocabularyUrl": "/relateditems-test.json" },
 *                            {"upload": {"baseUrl": "/", relativePath: "upload"} } }'></textarea>
 *    </form>
 *
 */

/* global alert:true */

define([
  'jquery',
  'underscore',
  'mockup-patterns-base',
  'mockup-patterns-relateditems',
  'mockup-patterns-modal',
  'tinymce',
  'mockup-patterns-autotoc',
  'text!mockup-patterns-tinymce-url/templates/result.xml',
  'text!mockup-patterns-tinymce-url/templates/selection.xml',
  'mockup-utils',
  'mockup-patterns-tinymce-url/js/links',
  'translate',
  'tinymce-modern-theme',
  'tinymce-advlist',
  'tinymce-anchor',
  'tinymce-autolink',
  'tinymce-autoresize',
  'tinymce-autosave',
  'tinymce-bbcode',
  'tinymce-charmap',
  'tinymce-code',
  'tinymce-colorpicker',
  'tinymce-contextmenu',
  'tinymce-directionality',
  'tinymce-emoticons',
  'tinymce-fullpage',
  'tinymce-fullscreen',
  'tinymce-hr',
  'tinymce-image',
  'tinymce-importcss',
  'tinymce-insertdatetime',
  'tinymce-layer',
  'tinymce-legacyoutput',
  'tinymce-link',
  'tinymce-lists',
  'tinymce-media',
  'tinymce-nonbreaking',
  'tinymce-noneditable',
  'tinymce-pagebreak',
  'tinymce-paste',
  'tinymce-preview',
  'tinymce-print',
  'tinymce-save',
  'tinymce-searchreplace',
  'tinymce-spellchecker',
  'tinymce-tabfocus',
  'tinymce-table',
  'tinymce-template',
  'tinymce-textcolor',
  'tinymce-textpattern',
  'tinymce-visualblocks',
  'tinymce-visualchars',
  'tinymce-wordcount'
], function($, _, Base, RelatedItems, Modal, tinymce, AutoTOC, ResultTemplate, SelectionTemplate, utils, LinkModal, _t) {
  'use strict';

  var TinyMCE = Base.extend({
    name: 'tinymce',
    trigger: '.pat-tinymce',
    defaults: {
      upload: {
        uploadMultiple: false,
        maxFiles: 1,
        showTitle: false
      },
      relatedItems: {
        // UID attribute is required here since we're working with related items
        attributes: ['UID', 'Title', 'Description', 'getURL', 'Type', 'path', 'ModificationDate', 'getIcon'],
        batchSize: 20,
        basePath: '/',
        vocabularyUrl: null,
        width: 500,
        maximumSelectionSize: 1,
        placeholder: _t('Search for item on site...')
      },
      text: {
        insertBtn: _t('Insert'), // so this can be configurable for different languages
        cancelBtn: _t('Cancel'),
        insertHeading: _t('Insert link'),
        title: _t('Title'),
        internal: _t('Internal'),
        external: _t('External'),
        email: _t('Email'),
        anchor: _t('Anchor'),
        subject: _t('Subject'),
        image: _t('Image'),
        imageAlign: _t('Align'),
        scale: _t('Size'),
        alt: _t('Alternative Text'),
        externalImage: _t('External Image URI')
      },
      // URL generation options
      loadingBaseUrl: '../../../bower_components/tinymce-builded/js/tinymce/',
      prependToUrl: '',
      appendToUrl: '',
      linkAttribute: 'path', // attribute to get link value from data
      prependToScalePart: '/imagescale/', // some value here is required to be able to parse scales back
      appendToScalePart: '',
      scales: _t('Listing (16x16):listing,Icon (32x32):icon,Tile (64x64):tile,' +
              'Thumb (128x128):thumb,Mini (200x200):mini,Preview (400x400):preview,' +
              'Large (768x768):large'),
      targetList: [
        {text: _t('Open in this window / frame'), value: ''},
        {text: _t('Open in new window'), value: '_blank'},
        {text: _t('Open in parent window / frame'), value: '_parent'},
        {text: _t('Open in top frame (replaces all frames)'), value: '_top'}
      ],
      imageTypes: 'Image',
      folderTypes: ['Folder', 'Plone Site'],
      linkableTypes: 'Document,Event,File,Folder,Image,News Item,Topic',
      tiny: {
        'content_css': '../../../bower_components/tinymce-builded/js/tinymce/skins/lightgray/content.min.css',
        theme: '-modern',
        plugins: [
          'advlist autolink lists charmap print preview anchor ' +
          'searchreplace visualblocks code fullscreen ' +
          'insertdatetime media table contextmenu paste plonelink ploneimage'
        ],
        menubar: 'edit table format tools view insert',
        toolbar: 'undo redo | styleselect | bold italic | ' +
                 'alignleft aligncenter alignright alignjustify | ' +
                 'bullist numlist outdent indent | ' +
                 'unlink plonelink ploneimage',
        //'autoresize_max_height': 900,
        'height': 400
      }
    },
    addLinkClicked: function() {
      var self = this;
      if (self.linkModal === null) {
        var $el = $('<div/>').insertAfter(self.$el);
        var linkTypes = ['internal', 'upload', 'external', 'email', 'anchor'];
        if(!self.options.upload){
          linkTypes.splice(1, 1);
        }
        self.linkModal = new LinkModal($el,
          $.extend(true, {}, self.options, {
            tinypattern: self,
            linkTypes: linkTypes
          })
        );
        self.linkModal.show();
      } else {
        self.linkModal.reinitialize();
        self.linkModal.show();
      }
    },
    addImageClicked: function() {
      var self = this;
      if (self.imageModal === null) {
        var linkTypes = ['image', 'uploadImage', 'externalImage'];
        if(!self.options.upload){
          linkTypes.splice(1, 1);
        }
        var options = $.extend(true, {}, self.options, {
          tinypattern: self,
          linkTypes: linkTypes,
          initialLinkType: 'image',
          text: {
            insertHeading: _t('Insert Image')
          },
          relatedItems: {
            baseCriteria: [{
              i: 'Type',
              o: 'plone.app.querystring.operation.list.contains',
              v: self.options.imageTypes.concat(self.options.folderTypes)
            }],
            selectableTypes: self.options.imageTypes,
            resultTemplate: ResultTemplate,
            selectionTemplate: SelectionTemplate
          }
        });
        var $el = $('<div/>').insertAfter(self.$el);
        self.imageModal = new LinkModal($el, options);
        self.imageModal.show();
      } else {
        self.imageModal.reinitialize();
        self.imageModal.show();
      }
    },
    generateUrl: function(data) {
      var self = this;
      var part = data[self.options.linkAttribute];
      return self.options.prependToUrl + part + self.options.appendToUrl;
    },
    generateImageUrl: function(data, scale_name) {
      var self = this;
      var url = self.generateUrl(data);
      if (scale_name !== ""){
        var part = scale_name;
        for(var i=0; i<self.options.scales.length; i++){
          if(self.options.scales[i].name == scale_name){
            part = self.options.scales[i].part;
          }
        }
        url = (url + self.options.prependToScalePart + part +
               self.options.appendToScalePart);
      }
      return url;
    },
    stripGeneratedUrl: function(url) {
      // to get original attribute back
      var self = this;
      url = url.split(self.options.prependToScalePart, 2)[0];
      if (self.options.prependToUrl) {
        var parts = url.split(self.options.prependToUrl, 2);
        if (parts.length === 2) {
          url = parts[1];
        }
      }
      if (self.options.appendToUrl) {
        url = url.split(self.options.appendToUrl)[0];
      }
      return url;
    },
    getScaleFromUrl: function(url) {
      var self = this;
      var split = url.split(self.options.prependToScalePart);
      if (split.length !== 2) {
        // not valid scale, screw it
        return null;
      }
      if (self.options.appendToScalePart) {
        url = split[1].split(self.options.appendToScalePart)[0];
      } else {
        url = split[1];
      }
      if (url.indexOf('/image_') !== -1) {
        url = url.split('/image_')[1];
      }
      return url;
    },
    init: function() {
      var self = this;
      self.linkModal = self.imageModal = self.uploadModal = null;
      // tiny needs an id in order to initialize. Creat it if not set.
      var id = utils.setId(self.$el);
      var tinyOptions = self.options.tiny;
      tinyOptions.selector = '#' + id;
      tinyOptions.addLinkClicked = function() {
        self.addLinkClicked.apply(self, []);
      };
      tinyOptions.addImageClicked = function() {
        self.addImageClicked.apply(self, []);
      };
      // XXX: disabled skin means it wont load css files which we already
      // include in widgets.min.css
      tinyOptions.skin = false;

      self.options.relatedItems.generateImageUrl = function(data, scale) {
        // this is so, in our result and selection template, we can
        // access getting actual urls from related items
        return self.generateImageUrl.apply(self, [data, scale]);
      };

      tinyOptions.init_instance_callback = function(editor) {
        if (self.tiny === undefined || self.tiny === null) {
          self.tiny = editor;
        }
      };
      if (tinyOptions.language !== 'en') {
        tinymce.baseURL = self.options.loadingBaseUrl;
      }

      if(typeof(self.options.scales) === 'string'){
        self.options.scales = _.map(self.options.scales.split(','), function(scale){
          var scale = scale.split(':');
          return {
            part: scale[1],
            name: scale[1],
            label: scale[0]
          }
        });
      }
      if(typeof(self.options.folderTypes) === 'string'){
        self.options.folderTypes = self.options.folderTypes.split(',');
      }
      if(typeof(self.options.imageTypes) === 'string'){
        self.options.imageTypes = self.options.imageTypes.split(',');
      }

      tinymce.init(tinyOptions);
      self.tiny = tinymce.get(id);

      /* tiny really should be doing this by default
       * but this fixes overlays not saving data */
      var $form = self.$el.parents('form');
      $form.on('submit', function() {
        self.tiny.save();
      });
    },
    destroy: function() {
      this.tiny.destroy();
    }
  });

  return TinyMCE;

});
<commit_msg>be able to append to original scale<commit_after>/* TinyMCE pattern.
 *
 * Options:
 *    relatedItems(object): Related items pattern options. ({ attributes: ["UID", "Title", "Description", "getURL", "Type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
 *    upload(object): Upload pattern options. ({ attributes: look at upload pattern for getting the options list })
 *    text(object): Translation strings ({ insertBtn: "Insert", cancelBtn: "Cancel", insertHeading: "Insert link", title: "Title", internal: "Internal", external: "External", email: "Email", anchor: "Anchor", subject: "Subject" image: "Image", imageAlign: "Align", scale: "Size", alt: "Alternative Text", externalImage: "External Image URI"})
 *    scales(string): TODO: is this even used ('Listing (16x16):listing,Icon (32x32):icon,Tile (64x64):tile,Thumb (128x128):thumb,Mini (200x200):mini,Preview (400x400):preview,Large (768x768):large')
 *    targetList(array): TODO ([ {text: "Open in this window / frame", value: ""}, {text: "Open in new window", value: "_blank"}, {text: "Open in parent window / frame", value: "_parent"}, {text: "Open in top frame (replaces all frames)", value: "_top"}])
 * imageTypes(string): TODO ('Image')
 *    folderTypes(string): TODO ('Folder,Plone Site')
 *    linkableTypes(string): TODO ('Document,Event,File,Folder,Image,News Item,Topic')
 *    tiny(object): TODO ({ plugins: [ "advlist autolink lists charmap print preview anchor", "usearchreplace visualblocks code fullscreen autoresize", "insertdatetime media table contextmenu paste plonelink ploneimage" ], menubar: "edit table format tools view insert",
toolbar: "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | unlink plonelink ploneimage", autoresize_max_height: 1500 })
 *    prependToUrl(string): Text to prepend to generated internal urls. ('')
 *    appendToUrl(string): Text to append to generated internal urls. ('')
 *    prependToScalePart(string): Text to prepend to generated image scale url part. ('/imagescale/')
 *    appendToScalePart(string): Text to append to generated image scale url part. ('')
 *    linkAttribute(string): Ajax response data attribute to use for url. ('path')
 *
 * Documentation:
 *    # Default
 *
 *    {{ example-1 }}
 *
 *    # With dropzone
 *
 *    {{ example-2 }}
 *
 * Example: example-1
 *    <form>
 *      <textarea class="pat-tinymce"
 *          data-pat-tinymce='{"relatedItems": {
 *                                "vocabularyUrl": "/relateditems-test.json"
 *                                }}'></textarea>
 *    </form>
 *
 * Example: example-2
 *    <form>
 *      <textarea class="pat-tinymce"
 *          data-pat-tinymce='{"relatedItems": {"vocabularyUrl": "/relateditems-test.json" },
 *                            {"upload": {"baseUrl": "/", relativePath: "upload"} } }'></textarea>
 *    </form>
 *
 */

/* global alert:true */

define([
  'jquery',
  'underscore',
  'mockup-patterns-base',
  'mockup-patterns-relateditems',
  'mockup-patterns-modal',
  'tinymce',
  'mockup-patterns-autotoc',
  'text!mockup-patterns-tinymce-url/templates/result.xml',
  'text!mockup-patterns-tinymce-url/templates/selection.xml',
  'mockup-utils',
  'mockup-patterns-tinymce-url/js/links',
  'translate',
  'tinymce-modern-theme',
  'tinymce-advlist',
  'tinymce-anchor',
  'tinymce-autolink',
  'tinymce-autoresize',
  'tinymce-autosave',
  'tinymce-bbcode',
  'tinymce-charmap',
  'tinymce-code',
  'tinymce-colorpicker',
  'tinymce-contextmenu',
  'tinymce-directionality',
  'tinymce-emoticons',
  'tinymce-fullpage',
  'tinymce-fullscreen',
  'tinymce-hr',
  'tinymce-image',
  'tinymce-importcss',
  'tinymce-insertdatetime',
  'tinymce-layer',
  'tinymce-legacyoutput',
  'tinymce-link',
  'tinymce-lists',
  'tinymce-media',
  'tinymce-nonbreaking',
  'tinymce-noneditable',
  'tinymce-pagebreak',
  'tinymce-paste',
  'tinymce-preview',
  'tinymce-print',
  'tinymce-save',
  'tinymce-searchreplace',
  'tinymce-spellchecker',
  'tinymce-tabfocus',
  'tinymce-table',
  'tinymce-template',
  'tinymce-textcolor',
  'tinymce-textpattern',
  'tinymce-visualblocks',
  'tinymce-visualchars',
  'tinymce-wordcount'
], function($, _, Base, RelatedItems, Modal, tinymce, AutoTOC, ResultTemplate, SelectionTemplate, utils, LinkModal, _t) {
  'use strict';

  var TinyMCE = Base.extend({
    name: 'tinymce',
    trigger: '.pat-tinymce',
    defaults: {
      upload: {
        uploadMultiple: false,
        maxFiles: 1,
        showTitle: false
      },
      relatedItems: {
        // UID attribute is required here since we're working with related items
        attributes: ['UID', 'Title', 'Description', 'getURL', 'Type', 'path', 'ModificationDate', 'getIcon'],
        batchSize: 20,
        basePath: '/',
        vocabularyUrl: null,
        width: 500,
        maximumSelectionSize: 1,
        placeholder: _t('Search for item on site...')
      },
      text: {
        insertBtn: _t('Insert'), // so this can be configurable for different languages
        cancelBtn: _t('Cancel'),
        insertHeading: _t('Insert link'),
        title: _t('Title'),
        internal: _t('Internal'),
        external: _t('External'),
        email: _t('Email'),
        anchor: _t('Anchor'),
        subject: _t('Subject'),
        image: _t('Image'),
        imageAlign: _t('Align'),
        scale: _t('Size'),
        alt: _t('Alternative Text'),
        externalImage: _t('External Image URI')
      },
      // URL generation options
      loadingBaseUrl: '../../../bower_components/tinymce-builded/js/tinymce/',
      prependToUrl: '',
      appendToUrl: '',
      linkAttribute: 'path', // attribute to get link value from data
      prependToScalePart: '/imagescale/', // some value here is required to be able to parse scales back
      appendToScalePart: '',
      appendToOriginalScalePart: '',
      scales: _t('Listing (16x16):listing,Icon (32x32):icon,Tile (64x64):tile,' +
              'Thumb (128x128):thumb,Mini (200x200):mini,Preview (400x400):preview,' +
              'Large (768x768):large'),
      targetList: [
        {text: _t('Open in this window / frame'), value: ''},
        {text: _t('Open in new window'), value: '_blank'},
        {text: _t('Open in parent window / frame'), value: '_parent'},
        {text: _t('Open in top frame (replaces all frames)'), value: '_top'}
      ],
      imageTypes: 'Image',
      folderTypes: ['Folder', 'Plone Site'],
      linkableTypes: 'Document,Event,File,Folder,Image,News Item,Topic',
      tiny: {
        'content_css': '../../../bower_components/tinymce-builded/js/tinymce/skins/lightgray/content.min.css',
        theme: '-modern',
        plugins: [
          'advlist autolink lists charmap print preview anchor ' +
          'searchreplace visualblocks code fullscreen ' +
          'insertdatetime media table contextmenu paste plonelink ploneimage'
        ],
        menubar: 'edit table format tools view insert',
        toolbar: 'undo redo | styleselect | bold italic | ' +
                 'alignleft aligncenter alignright alignjustify | ' +
                 'bullist numlist outdent indent | ' +
                 'unlink plonelink ploneimage',
        //'autoresize_max_height': 900,
        'height': 400
      }
    },
    addLinkClicked: function() {
      var self = this;
      if (self.linkModal === null) {
        var $el = $('<div/>').insertAfter(self.$el);
        var linkTypes = ['internal', 'upload', 'external', 'email', 'anchor'];
        if(!self.options.upload){
          linkTypes.splice(1, 1);
        }
        self.linkModal = new LinkModal($el,
          $.extend(true, {}, self.options, {
            tinypattern: self,
            linkTypes: linkTypes
          })
        );
        self.linkModal.show();
      } else {
        self.linkModal.reinitialize();
        self.linkModal.show();
      }
    },
    addImageClicked: function() {
      var self = this;
      if (self.imageModal === null) {
        var linkTypes = ['image', 'uploadImage', 'externalImage'];
        if(!self.options.upload){
          linkTypes.splice(1, 1);
        }
        var options = $.extend(true, {}, self.options, {
          tinypattern: self,
          linkTypes: linkTypes,
          initialLinkType: 'image',
          text: {
            insertHeading: _t('Insert Image')
          },
          relatedItems: {
            baseCriteria: [{
              i: 'Type',
              o: 'plone.app.querystring.operation.list.contains',
              v: self.options.imageTypes.concat(self.options.folderTypes)
            }],
            selectableTypes: self.options.imageTypes,
            resultTemplate: ResultTemplate,
            selectionTemplate: SelectionTemplate
          }
        });
        var $el = $('<div/>').insertAfter(self.$el);
        self.imageModal = new LinkModal($el, options);
        self.imageModal.show();
      } else {
        self.imageModal.reinitialize();
        self.imageModal.show();
      }
    },
    generateUrl: function(data) {
      var self = this;
      var part = data[self.options.linkAttribute];
      return self.options.prependToUrl + part + self.options.appendToUrl;
    },
    generateImageUrl: function(data, scale_name) {
      var self = this;
      var url = self.generateUrl(data);
      if (scale_name !== ""){
        var part = scale_name;
        for(var i=0; i<self.options.scales.length; i++){
          if(self.options.scales[i].name == scale_name){
            part = self.options.scales[i].part;
          }
        }
        url = (url + self.options.prependToScalePart + part +
               self.options.appendToScalePart);
      }else{
        url = url + self.options.appendToOriginalScalePart;
      }
      return url;
    },
    stripGeneratedUrl: function(url) {
      // to get original attribute back
      var self = this;
      url = url.split(self.options.prependToScalePart, 2)[0];
      if (self.options.prependToUrl) {
        var parts = url.split(self.options.prependToUrl, 2);
        if (parts.length === 2) {
          url = parts[1];
        }
      }
      if (self.options.appendToUrl) {
        url = url.split(self.options.appendToUrl)[0];
      }
      return url;
    },
    getScaleFromUrl: function(url) {
      var self = this;
      var split = url.split(self.options.prependToScalePart);
      if (split.length !== 2) {
        // not valid scale, screw it
        return null;
      }
      if (self.options.appendToScalePart) {
        url = split[1].split(self.options.appendToScalePart)[0];
      } else {
        url = split[1];
      }
      if (url.indexOf('/image_') !== -1) {
        url = url.split('/image_')[1];
      }
      return url;
    },
    init: function() {
      var self = this;
      self.linkModal = self.imageModal = self.uploadModal = null;
      // tiny needs an id in order to initialize. Creat it if not set.
      var id = utils.setId(self.$el);
      var tinyOptions = self.options.tiny;
      tinyOptions.selector = '#' + id;
      tinyOptions.addLinkClicked = function() {
        self.addLinkClicked.apply(self, []);
      };
      tinyOptions.addImageClicked = function() {
        self.addImageClicked.apply(self, []);
      };
      // XXX: disabled skin means it wont load css files which we already
      // include in widgets.min.css
      tinyOptions.skin = false;

      self.options.relatedItems.generateImageUrl = function(data, scale) {
        // this is so, in our result and selection template, we can
        // access getting actual urls from related items
        return self.generateImageUrl.apply(self, [data, scale]);
      };

      tinyOptions.init_instance_callback = function(editor) {
        if (self.tiny === undefined || self.tiny === null) {
          self.tiny = editor;
        }
      };
      if (tinyOptions.language !== 'en') {
        tinymce.baseURL = self.options.loadingBaseUrl;
      }

      if(typeof(self.options.scales) === 'string'){
        self.options.scales = _.map(self.options.scales.split(','), function(scale){
          var scale = scale.split(':');
          return {
            part: scale[1],
            name: scale[1],
            label: scale[0]
          }
        });
      }
      if(typeof(self.options.folderTypes) === 'string'){
        self.options.folderTypes = self.options.folderTypes.split(',');
      }
      if(typeof(self.options.imageTypes) === 'string'){
        self.options.imageTypes = self.options.imageTypes.split(',');
      }

      tinymce.init(tinyOptions);
      self.tiny = tinymce.get(id);

      /* tiny really should be doing this by default
       * but this fixes overlays not saving data */
      var $form = self.$el.parents('form');
      $form.on('submit', function() {
        self.tiny.save();
      });
    },
    destroy: function() {
      this.tiny.destroy();
    }
  });

  return TinyMCE;

});
<|endoftext|>


<commit_before>'use strict';

const app = require('express')();
const mongoose = require('mongoose');
const morgan = require('morgan');
const cors = require('cors');
const errorHandler = require('./lib/error-handler');
const Promise = require('./lib/promise');
const createError = require('http-errors');
mongoose.promise = Promise;

const server = require('http').Server(app);
const io = require('socket.io')(server);
const PORT = process.env.PORT || 3000;
const dbPort = process.env.MONGODB_URI || 'mongodb://localhost/deploy';
mongoose.connect(dbPort);

// const remoteRouter = require('./routes/remote-router');
const userRouter = require('./routes/user-router');
const authRouter = require('./routes/auth-router');

app.use(morgan('dev'));
app.use(cors());

app.get('/api/update', (req, res, next) => {
  io.emit('update');
  res.status(200).send('Remotes updated to database');
  next();
});

app.use('/api/remote/:button', (req, res, next) => {
  if (!req.params.button) {
    io.emit('error', 'error');
    return next(createError(400, 'Invalid Button'));
  }
  io.emit('post', [req.params.button]);
  res.status(200).send('sent ' + req.params.button + ' to remote.');
  next();
});

app.use('/api', authRouter);
app.use('/api', userRouter);
// app.use('/api', remoteRouter);

app.all('*', function(req, res, next) {
  next(createError(404, `Error: ${req.method} :: ${req.url} is not a route`));
});
app.use(errorHandler);

app.listen(PORT, () => console.log('server up on 3000'));
<commit_msg>changed app.listen to server.listen<commit_after>'use strict';

const app = require('express')();
const mongoose = require('mongoose');
const morgan = require('morgan');
const cors = require('cors');
const errorHandler = require('./lib/error-handler');
const Promise = require('./lib/promise');
const createError = require('http-errors');
mongoose.promise = Promise;

const server = require('http').Server(app);
const io = require('socket.io')(server);
const PORT = process.env.PORT || 3000;
const dbPort = process.env.MONGODB_URI || 'mongodb://localhost/deploy';
mongoose.connect(dbPort);

// const remoteRouter = require('./routes/remote-router');
const userRouter = require('./routes/user-router');
const authRouter = require('./routes/auth-router');

app.use(morgan('dev'));
app.use(cors());

app.get('/api/update', (req, res, next) => {
  io.emit('update');
  res.status(200).send('Remotes updated to database');
  next();
});

app.use('/api/remote/:button', (req, res, next) => {
  if (!req.params.button) {
    io.emit('error', 'error');
    return next(createError(400, 'Invalid Button'));
  }
  io.emit('post', [req.params.button]);
  res.status(200).send('sent ' + req.params.button + ' to remote.');
  next();
});

app.use('/api', authRouter);
app.use('/api', userRouter);
// app.use('/api', remoteRouter);

app.all('*', function(req, res, next) {
  next(createError(404, `Error: ${req.method} :: ${req.url} is not a route`));
});
app.use(errorHandler);

server.listen(PORT, () => console.log('server up on 3000'));
<|endoftext|>


<commit_before>/**
 * Terminal/WindowManager core script
 * jQuery plugin for opening windows from the terminal
 * Espen Andersen, Norwegian Broadcast Corporation
 * espen.andersen@nrk.no
 * (c) 2013 - NRK Investigative unit
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 */
( function( $ ) {

	var
		ROOT_SELECTOR = '.__window__manager__root__',

		that,
		root,
		persistent = { windows: { } },

		public_members = {
			'close_window': closeWindow,
			'flash_window': flashWindow,
			'move_window': moveWindow,
			'open_window': openWindow,
			'send_message': sendMessage,
			'set_focus': setFocus
		};

	// Produce a random, unique hexadecimal ID string
	function getUniqueId( prefix ) {
		return 	prefix +
					 String( '00000000' +
							 Math.floor( 0xFFFFFFFF * Math.random() )
							.toString( 16 ) )
					.slice( -8 );
	}

	// brings specified window to the front, normalizing but preserving
	// the z-order index for the other windows
	function bringToFront( hWnd ) {

		var
			i,

			hierarchy =
				root
					.find( '.window' )
					.toArray();

		persistent.windows[ hWnd ].css( 'z-index', 9999 );
		hierarchy.sort( function( a, b ) {
			return ( Number( a.style.zIndex ) - Number( b.style.zIndex ) );
		} );

		for( i = 0; i < hierarchy.length; i++ ) {
			$( hierarchy[ i ] ).css( 'z-index', 10 + i );
		}
	};

	// Close window, param must be either the window handle,
	// or an object with the handle stored in a hWnd property.
	function closeWindow( param ) {

		var
			hWnd = typeof param === 'object' ? param.hWnd : param;

		if( persistent.windows[ hWnd ] ) {

			var

				win =
					root
						.find( '#' + hWnd ),

				winInfo =
					win.data( 'wininfo' );

			sendMessage( { msg: 'wm_before_close' }, winInfo.hWnd );

			win.removeData( 'wininfo' );

			delete winInfo.hWnd;

			win
				.remove();

			delete persistent.windows[ hWnd ];

			var e = new $.Event( 'notify' );
			e.msg = 'close_window';
			e.winInfo = winInfo;
			that.trigger( e );
		}
	};

	// Flash window title bar, param must be either the window handle,
	// or an object with the handle stored in a hWnd property.
	function flashWindow( param ) {

		var
			hWnd = typeof param === 'object' ? param.hWnd : param,
			win = $( '#' + hWnd ),
			counter = 0,
			timer;

		if( persistent.windows[ hWnd ] ) {
			if( timer ) {
				clearInterval( timer );
				timer = 0;
				counter = 0;
			}
			timer =
				setInterval( function( e ) {
					if( ++counter >= 8 ) {
						clearInterval( timer );
						timer = 0;
						win.removeClass( 'flash' );
						counter = 0;
					}
					else {
						win.toggleClass( 'flash' );
					}
				}, 50 );
		}
	};

	// Create new window
	function createWindow( param ) {

		var

			styles = { display: 'none' },

			winCount = root.find( '.window' ).length,

			winInfo = {
				eventDispatcher: that,
				windowManager: root,
				hWnd: getUniqueId( 'win_' ),
				wndTitle: ( param && param.wndTitle ) || '',
				wndClass: ( param && param.wndClass ) || 'default',
				wndClose: ( param && param.wndClose ) || '',
				statusBar: param.statusBar === true,
				resizeHandle: param.resizeHandle === true
			};

			if( param ) {
				if( param.width ) styles.width = param.width;
				if( param.height ) styles.height = param.height;
			}
			else {
				param = { };
			}

			// Default positioning:
			if( styles.left === undefined ) {
				//styles.left = ( 1 + winCount ) * 30;
			}
			if( styles.top === undefined ) {
				//styles.top = ( 1 + winCount ) * 30;
			}

		var newWin = { };
		newWin[ winInfo.hWnd ] =

			root
				.prepend( 	'
' +
						   		'
' +
									'' + winInfo.wndTitle + '' +
									'X' +
								'
' +
								'
' +
								'
' +
							'
' )

				.find( '#' + winInfo.hWnd )
				.addClass( winInfo.wndClass )
				.data( 'wininfo', winInfo )
				.css( styles )
				.draggable( {
					handle: 'h2',
					start:
						function( e ) {
							bringToFront( $( e.target ).closest( '.window' ).attr( 'id' ) );
						}
				} )
				.find( '.titleBar .close' )
				.click( function( e ) {
					var
						hWnd =
							$( e.target )
								.closest( '.window' )
								.attr( 'id' );

					closeWindow( hWnd );
					e.preventDefault();
					e.stopPropagation();
				} )
				.end()
				.find( '.clientRect' )
				.on( 'mousewheel', function( scr, d ) {
					var w = $( this );
					if( d > 0 && w.scrollTop() === 0 ) {
						scr.preventDefault();
					}
					else if( d < 0 && ( w.scrollTop() >= ( w.get( 0 ).scrollHeight - w.innerHeight() - 1 ) ) ) {
						scr.preventDefault();
					}
				} )
				.end();

		if( winInfo.statusBar || winInfo.resizeHandle ) {
			root
				.find( '#' + winInfo.hWnd )
				.addClass( 'withStatusBar' )
				.append( '

Ready

' );
		}

		if( winInfo.resizeHandle ) {
			root
				.find( '#' + winInfo.hWnd )
				.find( '.statusBar' )
				.append( '

' );

			root.find( '#' + winInfo.hWnd ).resizable( { handles: "se" } );
		}

		persistent.windows = $.extend( { }, persistent.windows, newWin );

		param && param.wndProc && param.wndProc( winInfo );

		return winInfo;
	}

	// Default window proc displays en empty window
	function defaultWndProc( winInfo ) {
		showWindow( winInfo.hWnd );
	}

	// Initialize WindowManager
	function init( _options ) {

		root =
			$( that )
				.html( '

' )
				.find( ROOT_SELECTOR );
		root
			.on( 'click', '.window', function( e ) {
				$( e.target )
					.closest( '.window' )
					.each( function( index, win ) {
						var
							hWnd = $( win ).attr( 'id' );
						bringToFront( hWnd );
					} )
					.siblings( '.focus' )
					.removeClass( 'focus' )
					.end()
					.addClass( 'focus' );
			} )
			.on( 'drag', '.window', function( e ) {

				var
					hWnd = $( e.target ).attr( 'id' );

				if( persistent.windows[ hWnd ] ) {
					persistent.windows[ hWnd ].userPositioned = true;
				}

			} );
	};

	function isTopmost( hWnd ) {
		return $( '#' + hWnd ).css( 'z-index' ) == getTopmostIndex();
	};

	function getTopmostIndex() {
		var
			zOrder = [ ],
			windows = $( '.windowManager .window' );

		windows
			.each( function( i, e ) {
				zOrder[ i ] = $( e ).css( 'z-index' );
			} );

		var
			iTopMost =
				( zOrder
					.sort(
						function( a, b ) {
							return a - b;
						} )
					.reverse() ) [ 0 ]
		return iTopMost;
	};

	// Move a window
	function moveWindow( hWnd, pos ) {
		root
			.find( '#' + hWnd )
			.css( {
				top: pos.top,
				left: pos.left } );
	}

	// Open a window, create new if no handle to existing window is provided
	function openWindow( param ) {

		var winInfo;

		if( param === undefined ||
			param.hWnd === undefined ||
			persistent.windows[ param.hWnd ] === undefined ) {

			winInfo = createWindow( param );
		}
		else {
			winInfo = param;
		}

		showWindow( winInfo.hWnd );
	}

	// WindowManager data store
	function savePersistent() {
		root
			.data( 'persistent', persistent );
	}

	// Send message to one or all windows. If no handle is provided
	// (i.e. undefined), the message is broadcasted to all windows
	function sendMessage( param, hWnd ) {
		if( param && param.msg ) {
			var
				e = new $.Event( param.msg ),
				selector = hWnd !== undefined ? '#' + hWnd : '.window';
			e.msg = param.msg;
			e.param = param;
			root
				.find( selector )
				.trigger( e );
		}
	}

	// Bring window to front and activate it
	function setFocus( param ) {
		if( param && param.hWnd ) {
			bringToFront( param.hWnd );
		}
	}

	// Make window visible
	function showWindow( hWnd ) {

		var
			wnd = $( persistent.windows[ hWnd ] );

		bringToFront( hWnd );

		if( wnd.length === 1 ) {

			var
				x = parseInt( wnd.css( 'left' ) ),
				y = parseInt( wnd.css( 'top' ) );

			wnd
				.css( { display: 'block', left: 0, top: 0, opacity: 0.0 } )
				.animate( { left: x, top: y, opacity: 1.0 }, 500 );
		}
	}

	// Expose interface
	$.fn.windowManager = function( param ) {

		var retVal,
			index = 0,
			argv = arguments,
			argc = argv.length;

		this.each( function() {
			that = $( this );
			if( param === undefined || typeof param === 'object' ) {
				// Initialization
				init.apply( this, [ param ] );
				savePersistent();
			}
			else if( public_members[ param ] !== undefined ) {
				// Call to public member
				root = that.find( ROOT_SELECTOR );
				persistent = $( root ).data( 'persistent' );
				retVal = public_members[ param ].apply( this, Array.prototype.slice.call( argv, 1 ) );
				// Callback func should be the last argument
				if( argc > 0 && typeof argv[ argc - 1 ] === 'function' ) {
					// Pass return value for each element to callback function
					( argv[ argc - 1 ] )( index, retVal );
					savePersistent();
					retVal = null;
				}
				else {
					savePersistent();
					// No callback; break loop if return value was present
					if( retVal ) return false;
				}
			}
			else {
				$.error( 'Method not found in windowManager' );
			}
			index++;
		} );
		// Maintain chainability unless returning something
		return retVal || this;
	}

} )( jQuery );<commit_msg>added notification for window open<commit_after>/**
 * Terminal/WindowManager core script
 * jQuery plugin for opening windows from the terminal
 * Espen Andersen, Norwegian Broadcast Corporation
 * espen.andersen@nrk.no
 * (c) 2013 - NRK Investigative unit
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 */
( function( $ ) {

	var
		ROOT_SELECTOR = '.__window__manager__root__',

		that,
		root,
		persistent = { windows: { } },

		public_members = {
			'close_window': closeWindow,
			'flash_window': flashWindow,
			'move_window': moveWindow,
			'open_window': openWindow,
			'send_message': sendMessage,
			'set_focus': setFocus
		};

	// Produce a random, unique hexadecimal ID string
	function getUniqueId( prefix ) {
		return 	prefix +
					 String( '00000000' +
							 Math.floor( 0xFFFFFFFF * Math.random() )
							.toString( 16 ) )
					.slice( -8 );
	}

	// brings specified window to the front, normalizing but preserving
	// the z-order index for the other windows
	function bringToFront( hWnd ) {

		var
			i,

			hierarchy =
				root
					.find( '.window' )
					.toArray();

		persistent.windows[ hWnd ].css( 'z-index', 9999 );
		hierarchy.sort( function( a, b ) {
			return ( Number( a.style.zIndex ) - Number( b.style.zIndex ) );
		} );

		for( i = 0; i < hierarchy.length; i++ ) {
			$( hierarchy[ i ] ).css( 'z-index', 10 + i );
		}
	};

	// Close window, param must be either the window handle,
	// or an object with the handle stored in a hWnd property.
	function closeWindow( param ) {

		var
			hWnd = typeof param === 'object' ? param.hWnd : param;

		if( persistent.windows[ hWnd ] ) {

			var

				win =
					root
						.find( '#' + hWnd ),

				winInfo =
					win.data( 'wininfo' );

			sendMessage( { msg: 'wm_before_close' }, winInfo.hWnd );

			win.removeData( 'wininfo' );

			delete winInfo.hWnd;

			win
				.remove();

			delete persistent.windows[ hWnd ];

			var e = new $.Event( 'notify' );
			e.msg = 'close_window';
			e.winInfo = winInfo;
			that.trigger( e );
		}
	};

	// Flash window title bar, param must be either the window handle,
	// or an object with the handle stored in a hWnd property.
	function flashWindow( param ) {

		var
			hWnd = typeof param === 'object' ? param.hWnd : param,
			win = $( '#' + hWnd ),
			counter = 0,
			timer;

		if( persistent.windows[ hWnd ] ) {
			if( timer ) {
				clearInterval( timer );
				timer = 0;
				counter = 0;
			}
			timer =
				setInterval( function( e ) {
					if( ++counter >= 8 ) {
						clearInterval( timer );
						timer = 0;
						win.removeClass( 'flash' );
						counter = 0;
					}
					else {
						win.toggleClass( 'flash' );
					}
				}, 50 );
		}
	};

	// Create new window
	function createWindow( param ) {

		var

			styles = { display: 'none' },

			winCount = root.find( '.window' ).length,

			winInfo = {
				eventDispatcher: that,
				windowManager: root,
				hWnd: getUniqueId( 'win_' ),
				wndTitle: ( param && param.wndTitle ) || '',
				wndClass: ( param && param.wndClass ) || 'default',
				wndClose: ( param && param.wndClose ) || '',
				statusBar: param.statusBar === true,
				resizeHandle: param.resizeHandle === true
			};

			if( param ) {
				if( param.width ) styles.width = param.width;
				if( param.height ) styles.height = param.height;
			}
			else {
				param = { };
			}

			// Default positioning:
			if( styles.left === undefined ) {
				//styles.left = ( 1 + winCount ) * 30;
			}
			if( styles.top === undefined ) {
				//styles.top = ( 1 + winCount ) * 30;
			}

		var newWin = { };
		newWin[ winInfo.hWnd ] =

			root
				.prepend( 	'
' +
						   		'
' +
									'' + winInfo.wndTitle + '' +
									'X' +
								'
' +
								'
' +
								'
' +
							'
' )

				.find( '#' + winInfo.hWnd )
				.addClass( winInfo.wndClass )
				.data( 'wininfo', winInfo )
				.css( styles )
				.draggable( {
					handle: 'h2',
					start:
						function( e ) {
							bringToFront( $( e.target ).closest( '.window' ).attr( 'id' ) );
						}
				} )
				.find( '.titleBar .close' )
				.click( function( e ) {
					var
						hWnd =
							$( e.target )
								.closest( '.window' )
								.attr( 'id' );

					closeWindow( hWnd );
					e.preventDefault();
					e.stopPropagation();
				} )
				.end()
				.find( '.clientRect' )
				.on( 'mousewheel', function( scr, d ) {
					var w = $( this );
					if( d > 0 && w.scrollTop() === 0 ) {
						scr.preventDefault();
					}
					else if( d < 0 && ( w.scrollTop() >= ( w.get( 0 ).scrollHeight - w.innerHeight() - 1 ) ) ) {
						scr.preventDefault();
					}
				} )
				.end();

		if( winInfo.statusBar || winInfo.resizeHandle ) {
			root
				.find( '#' + winInfo.hWnd )
				.addClass( 'withStatusBar' )
				.append( '

Ready

' );
		}

		if( winInfo.resizeHandle ) {
			root
				.find( '#' + winInfo.hWnd )
				.find( '.statusBar' )
				.append( '

' );

			root.find( '#' + winInfo.hWnd ).resizable( { handles: "se" } );
		}

		persistent.windows = $.extend( { }, persistent.windows, newWin );

		param && param.wndProc && param.wndProc( winInfo );

		return winInfo;
	}

	// Default window proc displays en empty window
	function defaultWndProc( winInfo ) {
		showWindow( winInfo.hWnd );
	}

	// Initialize WindowManager
	function init( _options ) {

		root =
			$( that )
				.html( '

' )
				.find( ROOT_SELECTOR );
		root
			.on( 'click', '.window', function( e ) {
				$( e.target )
					.closest( '.window' )
					.each( function( index, win ) {
						var
							hWnd = $( win ).attr( 'id' );
						bringToFront( hWnd );
					} )
					.siblings( '.focus' )
					.removeClass( 'focus' )
					.end()
					.addClass( 'focus' );
			} )
			.on( 'drag', '.window', function( e ) {

				var
					hWnd = $( e.target ).attr( 'id' );

				if( persistent.windows[ hWnd ] ) {
					persistent.windows[ hWnd ].userPositioned = true;
				}

			} );
	};

	function isTopmost( hWnd ) {
		return $( '#' + hWnd ).css( 'z-index' ) == getTopmostIndex();
	};

	function getTopmostIndex() {
		var
			zOrder = [ ],
			windows = $( '.windowManager .window' );

		windows
			.each( function( i, e ) {
				zOrder[ i ] = $( e ).css( 'z-index' );
			} );

		var
			iTopMost =
				( zOrder
					.sort(
						function( a, b ) {
							return a - b;
						} )
					.reverse() ) [ 0 ]
		return iTopMost;
	};

	// Move a window
	function moveWindow( hWnd, pos ) {
		root
			.find( '#' + hWnd )
			.css( {
				top: pos.top,
				left: pos.left } );
	}

	// Open a window, create new if no handle to existing window is provided
	function openWindow( param ) {

		var winInfo;

		if( param === undefined ||
			param.hWnd === undefined ||
			persistent.windows[ param.hWnd ] === undefined ) {

			winInfo = createWindow( param );
		}
		else {
			winInfo = param;
		}

		showWindow( winInfo.hWnd );

		var e = new $.Event( 'notify' );
		e.msg = 'open_window';
		e.winInfo = winInfo;
		that.trigger( e );
	};

	// WindowManager data store
	function savePersistent() {
		root
			.data( 'persistent', persistent );
	};

	// Send message to one or all windows. If no handle is provided
	// (i.e. undefined), the message is broadcasted to all windows
	function sendMessage( param, hWnd ) {
		if( param && param.msg ) {
			var
				e = new $.Event( param.msg ),
				selector = hWnd !== undefined ? '#' + hWnd : '.window';
			e.msg = param.msg;
			e.param = param;
			root
				.find( selector )
				.trigger( e );
		}
	};

	// Bring window to front and activate it
	function setFocus( param ) {
		if( param && param.hWnd ) {
			bringToFront( param.hWnd );
		}
	};

	// Make window visible
	function showWindow( hWnd ) {

		var
			wnd = $( persistent.windows[ hWnd ] );

		bringToFront( hWnd );

		if( wnd.length === 1 ) {

			var
				x = parseInt( wnd.css( 'left' ) ),
				y = parseInt( wnd.css( 'top' ) );

			wnd
				.css( { display: 'block', left: 0, top: 0, opacity: 0.0 } )
				.animate( { left: x, top: y, opacity: 1.0 }, 500 );
		}
	};

	// Expose interface
	$.fn.windowManager = function( param ) {

		var retVal,
			index = 0,
			argv = arguments,
			argc = argv.length;

		this.each( function() {
			that = $( this );
			if( param === undefined || typeof param === 'object' ) {
				// Initialization
				init.apply( this, [ param ] );
				savePersistent();
			}
			else if( public_members[ param ] !== undefined ) {
				// Call to public member
				root = that.find( ROOT_SELECTOR );
				persistent = $( root ).data( 'persistent' );
				retVal = public_members[ param ].apply( this, Array.prototype.slice.call( argv, 1 ) );
				// Callback func should be the last argument
				if( argc > 0 && typeof argv[ argc - 1 ] === 'function' ) {
					// Pass return value for each element to callback function
					( argv[ argc - 1 ] )( index, retVal );
					savePersistent();
					retVal = null;
				}
				else {
					savePersistent();
					// No callback; break loop if return value was present
					if( retVal ) return false;
				}
			}
			else {
				$.error( 'Method not found in windowManager' );
			}
			index++;
		} );
		// Maintain chainability unless returning something
		return retVal || this;
	}

} )( jQuery );<|endoftext|>


<commit_before>
// import everything that is required
var config = require("config");
var express = require("express");
var q = require("q");
var request = require("request");
var crypto = require("crypto");
var qs = require("querystring");
var cookieParser = require("cookie-parser");
var nJwt = require("njwt");
var ad = require("activedirectory");
var AuthenticationContext = require("adal-node").AuthenticationContext;
var aadutils = require("./aadutils.js");

// get the configuration
var port = config.get("web.port");
var clientId = config.get("aad.clientId");
var clientSecret = config.get("aad.clientSecret");
var authority = config.get("aad.authority");
var redirectUri = config.get("aad.redirectUri");
var resource = config.get("aad.resource");
var jwtKey = config.get("jwt.key");

// create the web server
var app = express();
app.use(cookieParser());
app.use(express.static("public"));

// redirect to the main page
app.get("/", function(req, res) {
  res.redirect("/client.html");
});

// redirect through the AAD consent pattern
function consent(res, add) {
  crypto.randomBytes(48, function(err, buf) {
    if (err) {
      res.status(500).send("Server Error: a crypto token couldn't be created to secure the session.");
    } else {
      var token = buf.toString("base64").replace(/\//g, "_").replace(/\+/g, "-");
      res.cookie("authstate", token);
      var url = authority + "/oauth2/authorize?response_type=code&client_id=" + qs.escape(clientId) + "&redirect_uri=" + qs.escape(redirectUri) + "&state=" + qs.escape(token) + "&resource=" + qs.escape(resource) + add;
      res.redirect(url);
    }
  });
}

// a login with administrative consent
app.get("/consent", function(req, res) {
  consent(res, "&prompt=admin_consent");
});

// a login with user consent (if the admin has already consented there is no additional consent required)
app.get("/login/aad", function(req, res) {
  consent(res, "");
});

// Azure AD will first return a code that can then be converted into an access token with rights as defined for the app
function getAccessTokenFromCode(code) {
  var deferred = q.defer();

  var authenticationContext = new AuthenticationContext(authority);
  authenticationContext.acquireTokenWithAuthorizationCode(
    code,
    redirectUri,
    resource,
    clientId,
    clientSecret,
    function(err, response) {
      if (err) {
        deferred.reject(err.message);
      } else {
        deferred.resolve(response);
      }
    }
  );

  return deferred.promise;
}

// query the customer's Azure AD to find out what groups the user is a member of
function getGroupMembershipForUser(token, domain, userId) {
  var deferred = q.defer();

console.log("getGroupMembershipForUser");

  var options = {
    uri: "https://graph.microsoft.com/v1.0/me/memberOf?$select=displayName",
    json: true,
    headers: {
      "Authorization": "Bearer " + token
    }
  };

  request.get(options, function(error, response, body) {
    if (!error && response.statusCode == 200) {
  console.log("success!!!!");
        deferred.resolve(body.value);
    } else {
  console.log("failure!!!!");
        deferred.reject(JSON.stringify(body));
    }
  });

console.log("deferred");
  return deferred.promise;
}

function getJwtFromToken(token, userId) {
    var deferred = q.defer();

    // get the membership for the user
    var domain = userId.split("@")[1];
    getGroupMembershipForUser(token, domain, userId).then(function(groups) {

        // build a list of group names
        var membership = [];
        details.forEach(function(group) {
            if (group.displayName.startsWith("testauth_")) {
                membership.push(group.displayName.replace("testauth_", ""));
            }
        });

        // define rights
        var rights = [];
        if (membership.indexOf("admins") > -1) {
            rights.push("can admin");
            rights.push("can edit");
            rights.push("can view");
        } else if (membership.indexOf("users") > -1) {
            rights.push("can view");
        }

        // build the claims
        var claims = {
            iss: "http://testauth.plasne.com",
            sub: userId,
            scope: membership,
            rights: rights
        };

        // build the JWT
        var jwt = nJwt.create(claims, jwtKey);
        jwt.setExpiration(new Date().getTime() + (4 * 60 * 60 * 1000)); // 4 hours
        deferred.resolve(jwt.compact());

    }, function(msg) {
        deferred.reject(msg);
    });

    return deferred.promise;
}

// get an authorization token
app.get("/token", function(req, res) {

  // ensure this is all part of the same authorization chain
  if (req.cookies.authstate !== req.query.state) {
    res.status(400).send("Bad Request: this does not appear to be part of the same authorization chain.");
  } else {

    // get the access token
    getAccessTokenFromCode(req.query.code).then(function(tokenResponse) {

      // generate a JWT
      getJwtFromToken(tokenResponse.accessToken, tokenResponse.userId).then(function(jwt) {

        // return the JWT to the client
        res.cookie("accessToken", jwt, {
          maxAge: 4 * 60 * 60 * 1000 // 4 hours
        });
        res.redirect("/client.html");

      }, function(msg) {
        res.status(401).send("Unauthorized (jwt): " + msg);
      });

    }, function(msg) {
      res.status(401).send("Unauthorized (access token): " + msg);
    });

  }

});

// login to a traditional Active Directory
app.get("/login/ad", function(req, res) {

  // connect to AD
  var adConfig = config.get("ad");
  var client = new ad(adConfig);

  // authenticate the user
  var credentials = JSON.parse(req.cookies.credentials);
  client.authenticate(credentials.username, credentials.password, function(err, auth) {
    if (err) {
        res.status(401).send(JSON.stringify(err));
    }
    if (auth) {
      client.getGroupMembershipForUser(credentials.username, function(err, groups) {
        if (err) {
            res.status(500).send(JSON.stringify(err));
        }
        if (groups) {

            // build a list of group names
            var membership = [];
            groups.forEach(function(group) {
                if (group.cn.startsWith("testauth_")) {
                    membership.push(group.cn.replace("testauth_", ""));
                }
            });

            // define rights
            var rights = [];
            if (membership.indexOf("admins") > -1) {
                rights.push("can admin");
                rights.push("can edit");
                rights.push("can view");
            } else if (membership.indexOf("users") > -1) {
                rights.push("can view");
            }

            // build the claims
            var claims = {
                iss: "http://testauth.plasne.com",
                sub: credentials.username,
                scope: membership,
                rights: rights
            };

            // build the JWT
            var jwt = nJwt.create(claims, jwtKey);
            jwt.setExpiration(new Date().getTime() + (4 * 60 * 60 * 1000)); // 4 hours
            res.cookie("accessToken", jwt.compact(), {
                maxAge: 4 * 60 * 60 * 1000
            });

            // return to the client
            res.status(200).end();

        }
      });
    } else {
        res.status(401).send("Unknown authorization failure.");
    }
  });
});

function verifyToken(token) {
  var deferred = q.defer();

  // get the public keys
  var options = {
    uri: "https://login.microsoftonline.com/common/discovery/keys", // the key URL comes from: https://login.microsoftonline.com/<tenantId>/.well-known/openid-configuration
    json: true
  };
  request.get(options, function(error, response, body) {
    if (!error && response.statusCode == 200) {

      // try each public key
      body.keys.forEach(function(key) {
        var modulus = new Buffer(key.n, "base64");
        var exponent = new Buffer(key.e, "base64");
        var pem = aadutils.rsaPublicKeyPem(modulus, exponent);
        nJwt.verify(token, pem, "RS256", function(err, verified) {
            if (err) {
                deferred.reject("Unauthorized (verify token): " + err);
            } else {
                if (verified.body.aud == "http://testauth.plasne.com/") {
                    deferred.resolve(verified);
                } else {
                    deferred.reject("Unauthorized (aud): The token was generated for the wrong audience.");
                }
            }
        });
      });

    } else {
        deferred.reject("Unauthorized (get keys): " + error);
    }
  });

  return deferred.promise;
}

// the user has logged in to Azure AD and obtained a token already, in that case, validate the token and generate the JWT
app.get("/login/token", function(req, res) {

    // verify the existing token
    var token = req.get("Authorization").replace("Bearer ", "");
    verifyToken(token).then(function(verified) {

        // native apps cannot do admin consent, so we cannot reach back into the user's AAD, but we can generate a JWT without any special
        //   authorization and assume we do that in our application
        var claims = {
            iss: "http://testauth.plasne.com",
            sub: verified.body.upn
        };

        // build the JWT
        var jwt = nJwt.create(claims, jwtKey);
        jwt.setExpiration(new Date().getTime() + (4 * 60 * 60 * 1000)); // 4 hours
        res.status(200).send({ "accessToken": jwt.compact() });

    }, function(msg) {
        res.status(401).send(msg);
    });

});

// this is a service end-point that can verify the JWT coming from the client
app.get("/whoami", function(req, res) {
  var token;
  if (req.cookies.accessToken) token = req.cookies.accessToken;
  if (req.get("Authorization")) token = req.get("Authorization").replace("Bearer ", "");
  if (token) {
    nJwt.verify(token, jwtKey, function(err, verified) {
      if (err) {
        console.log(err);
      } else {
        var role = "none";
        if (verified.body.scope) {
          if (verified.body.scope.indexOf("users") > -1) role = "user";
          if (verified.body.scope.indexOf("admins") > -1) role = "admin";
        }
        res.send({
          "id": verified.body.sub,
          "role": role,
          "rights": (verified.body.rights) ? verified.body.rights : "none"
        });
      }
    });
  } else {
    res.status(401).send("Not Authorized: no access token was passed");
  }
});

app.listen(port);
console.log("listening on " + port);
<commit_msg>debug<commit_after>
// import everything that is required
var config = require("config");
var express = require("express");
var q = require("q");
var request = require("request");
var crypto = require("crypto");
var qs = require("querystring");
var cookieParser = require("cookie-parser");
var nJwt = require("njwt");
var ad = require("activedirectory");
var AuthenticationContext = require("adal-node").AuthenticationContext;
var aadutils = require("./aadutils.js");

// get the configuration
var port = config.get("web.port");
var clientId = config.get("aad.clientId");
var clientSecret = config.get("aad.clientSecret");
var authority = config.get("aad.authority");
var redirectUri = config.get("aad.redirectUri");
var resource = config.get("aad.resource");
var jwtKey = config.get("jwt.key");

// create the web server
var app = express();
app.use(cookieParser());
app.use(express.static("public"));

// redirect to the main page
app.get("/", function(req, res) {
  res.redirect("/client.html");
});

// redirect through the AAD consent pattern
function consent(res, add) {
  crypto.randomBytes(48, function(err, buf) {
    if (err) {
      res.status(500).send("Server Error: a crypto token couldn't be created to secure the session.");
    } else {
      var token = buf.toString("base64").replace(/\//g, "_").replace(/\+/g, "-");
      res.cookie("authstate", token);
      var url = authority + "/oauth2/authorize?response_type=code&client_id=" + qs.escape(clientId) + "&redirect_uri=" + qs.escape(redirectUri) + "&state=" + qs.escape(token) + "&resource=" + qs.escape(resource) + add;
      res.redirect(url);
    }
  });
}

// a login with administrative consent
app.get("/consent", function(req, res) {
  consent(res, "&prompt=admin_consent");
});

// a login with user consent (if the admin has already consented there is no additional consent required)
app.get("/login/aad", function(req, res) {
  consent(res, "");
});

// Azure AD will first return a code that can then be converted into an access token with rights as defined for the app
function getAccessTokenFromCode(code) {
  var deferred = q.defer();

  var authenticationContext = new AuthenticationContext(authority);
  authenticationContext.acquireTokenWithAuthorizationCode(
    code,
    redirectUri,
    resource,
    clientId,
    clientSecret,
    function(err, response) {
      if (err) {
        deferred.reject(err.message);
      } else {
        deferred.resolve(response);
      }
    }
  );

  return deferred.promise;
}

// query the customer's Azure AD to find out what groups the user is a member of
function getGroupMembershipForUser(token) {
  var deferred = q.defer();

  var options = {
    uri: "https://graph.microsoft.com/v1.0/me/memberOf?$select=displayName",
    json: true,
    headers: {
      "Authorization": "Bearer " + token
    }
  };

  request.get(options, function(error, response, body) {
    if (!error && response.statusCode == 200) {
      deferred.resolve(body.value);
    } else {
      deferred.reject(JSON.stringify(body));
    }
  });

  return deferred.promise;
}

function getJwtFromToken(token, userId) {
    var deferred = q.defer();

    // get the membership for the user
    getGroupMembershipForUser(token).then(function(groups) {

console.log("1");

        // build a list of group names
        var membership = [];
        details.forEach(function(group) {
            if (group.displayName.startsWith("testauth_")) {
                membership.push(group.displayName.replace("testauth_", ""));
            }
        });

        // define rights
        var rights = [];
        if (membership.indexOf("admins") > -1) {
            rights.push("can admin");
            rights.push("can edit");
            rights.push("can view");
        } else if (membership.indexOf("users") > -1) {
            rights.push("can view");
        }

        // build the claims
        var claims = {
            iss: "http://testauth.plasne.com",
            sub: userId,
            scope: membership,
            rights: rights
        };

console.log("2");

        // build the JWT
        var jwt = nJwt.create(claims, jwtKey);
        jwt.setExpiration(new Date().getTime() + (4 * 60 * 60 * 1000)); // 4 hours
        deferred.resolve(jwt.compact());

console.log("3");

    }, function(msg) {
console.log("4");
        deferred.reject(msg);
    }).done();

    return deferred.promise;
}

// get an authorization token
app.get("/token", function(req, res) {

  // ensure this is all part of the same authorization chain
  if (req.cookies.authstate !== req.query.state) {
    res.status(400).send("Bad Request: this does not appear to be part of the same authorization chain.");
  } else {

    // get the access token
    getAccessTokenFromCode(req.query.code).then(function(tokenResponse) {

      // generate a JWT
      getJwtFromToken(tokenResponse.accessToken, tokenResponse.userId).then(function(jwt) {

        // return the JWT to the client
        res.cookie("accessToken", jwt, {
          maxAge: 4 * 60 * 60 * 1000 // 4 hours
        });
        console.log("redirect");
        res.redirect("/client.html");

      }, function(msg) {
        res.status(401).send("Unauthorized (jwt): " + msg);
      }).done();

    }, function(msg) {
      res.status(401).send("Unauthorized (access token): " + msg);
    }).done();

  }

});

// login to a traditional Active Directory
app.get("/login/ad", function(req, res) {

  // connect to AD
  var adConfig = config.get("ad");
  var client = new ad(adConfig);

  // authenticate the user
  var credentials = JSON.parse(req.cookies.credentials);
  client.authenticate(credentials.username, credentials.password, function(err, auth) {
    if (err) {
        res.status(401).send(JSON.stringify(err));
    }
    if (auth) {
      client.getGroupMembershipForUser(credentials.username, function(err, groups) {
        if (err) {
            res.status(500).send(JSON.stringify(err));
        }
        if (groups) {

            // build a list of group names
            var membership = [];
            groups.forEach(function(group) {
                if (group.cn.startsWith("testauth_")) {
                    membership.push(group.cn.replace("testauth_", ""));
                }
            });

            // define rights
            var rights = [];
            if (membership.indexOf("admins") > -1) {
                rights.push("can admin");
                rights.push("can edit");
                rights.push("can view");
            } else if (membership.indexOf("users") > -1) {
                rights.push("can view");
            }

            // build the claims
            var claims = {
                iss: "http://testauth.plasne.com",
                sub: credentials.username,
                scope: membership,
                rights: rights
            };

            // build the JWT
            var jwt = nJwt.create(claims, jwtKey);
            jwt.setExpiration(new Date().getTime() + (4 * 60 * 60 * 1000)); // 4 hours
            res.cookie("accessToken", jwt.compact(), {
                maxAge: 4 * 60 * 60 * 1000
            });

            // return to the client
            res.status(200).end();

        }
      });
    } else {
        res.status(401).send("Unknown authorization failure.");
    }
  });
});

function verifyToken(token) {
  var deferred = q.defer();

  // get the public keys
  var options = {
    uri: "https://login.microsoftonline.com/common/discovery/keys", // the key URL comes from: https://login.microsoftonline.com/<tenantId>/.well-known/openid-configuration
    json: true
  };
  request.get(options, function(error, response, body) {
    if (!error && response.statusCode == 200) {

      // try each public key
      body.keys.forEach(function(key) {
        var modulus = new Buffer(key.n, "base64");
        var exponent = new Buffer(key.e, "base64");
        var pem = aadutils.rsaPublicKeyPem(modulus, exponent);
        nJwt.verify(token, pem, "RS256", function(err, verified) {
            if (err) {
                deferred.reject("Unauthorized (verify token): " + err);
            } else {
                if (verified.body.aud == "http://testauth.plasne.com/") {
                    deferred.resolve(verified);
                } else {
                    deferred.reject("Unauthorized (aud): The token was generated for the wrong audience.");
                }
            }
        });
      });

    } else {
        deferred.reject("Unauthorized (get keys): " + error);
    }
  });

  return deferred.promise;
}

// the user has logged in to Azure AD and obtained a token already, in that case, validate the token and generate the JWT
app.get("/login/token", function(req, res) {

    // verify the existing token
    var token = req.get("Authorization").replace("Bearer ", "");
    verifyToken(token).then(function(verified) {

        // native apps cannot do admin consent, so we cannot reach back into the user's AAD, but we can generate a JWT without any special
        //   authorization and assume we do that in our application
        var claims = {
            iss: "http://testauth.plasne.com",
            sub: verified.body.upn
        };

        // build the JWT
        var jwt = nJwt.create(claims, jwtKey);
        jwt.setExpiration(new Date().getTime() + (4 * 60 * 60 * 1000)); // 4 hours
        res.status(200).send({ "accessToken": jwt.compact() });

    }, function(msg) {
        res.status(401).send(msg);
    });

});

// this is a service end-point that can verify the JWT coming from the client
app.get("/whoami", function(req, res) {
  var token;
  if (req.cookies.accessToken) token = req.cookies.accessToken;
  if (req.get("Authorization")) token = req.get("Authorization").replace("Bearer ", "");
  if (token) {
    nJwt.verify(token, jwtKey, function(err, verified) {
      if (err) {
        console.log(err);
      } else {
        var role = "none";
        if (verified.body.scope) {
          if (verified.body.scope.indexOf("users") > -1) role = "user";
          if (verified.body.scope.indexOf("admins") > -1) role = "admin";
        }
        res.send({
          "id": verified.body.sub,
          "role": role,
          "rights": (verified.body.rights) ? verified.body.rights : "none"
        });
      }
    });
  } else {
    res.status(401).send("Not Authorized: no access token was passed");
  }
});

app.listen(port);
console.log("listening on " + port);
<|endoftext|>


<commit_before>import React from 'react'

import { shallow, mount, render } from 'enzyme'
import { assert, expect } from 'chai'
import AddNewMessage  from '../lib/components/AddNewMessage'

const sinon = require('sinon')

describe('Unit Test | AddNewMessage', () => {
  it('can mount with no properties', () => {
  const wrapper = shallow(<AddNewMessage />)
  })
  it('should have a button with 1 prop', function(){
    const wrapper = render(<AddNewMessage />)
    assert.equal(wrapper.find('.submitBtn').length, 1)
  })
  it('should have the button text rendered onto the page', function(){
    const wrapper = render(<AddNewMessage/>)
    expect(wrapper.text()).to.contain('Submit')
  })
})
// describe('Feature Test | AddNewMessage', () => {
//   it('submits a message', () => {
//     const onButtonClick = sinon.spy()
//     const wrapper = shallow(
//       <AddNewMessage onClick={onButtonClick} />)
//     const wrapper2 = shallow(
//       <MessageInput />)
//     wrapper.find('.submit-button').simulate('click')
//     expect(wrapper2.state('draftMessage')).to.eq('')
//   })
// })
<commit_msg>adds enzyme feature tests<commit_after>import React from 'react'

import { shallow, mount, render } from 'enzyme'
import { assert, expect } from 'chai'
import AddNewMessage  from '../lib/components/AddNewMessage'
import Application from '../lib/components/Application'

const sinon = require('sinon')

describe('Unit Test | AddNewMessage', () => {
  it('can mount with no properties', () => {
  const wrapper = shallow(<AddNewMessage />)
  })
  it('should have a button with 1 prop', function(){
    const wrapper = render(<AddNewMessage />)
    assert.equal(wrapper.find('.submitBtn').length, 1)
  })
  it('should have the button text rendered onto the page', function(){
    const wrapper = render(<AddNewMessage/>)
    expect(wrapper.text()).to.contain('Submit')
  })
})

describe('Feature Test | AddNewMessage', () => {
  it('submits a message', () => {
    const buttonClick = sinon.spy()
    const wrapper = shallow(
      <AddNewMessage onClick={buttonClick} />)
    const wrapper2 = shallow(
      <Application />)
    wrapper.find('.submitBtn').simulate('click')
    expect(wrapper2.state('draftMessage')).to.eq('')
  })
})
<|endoftext|>