<commit_before>var config = require('./config'),
	db = require('./db'),
	exec = require('child_process').exec,
	flow = require('flow'),
	formidable = require('formidable'),
	fs = require('fs'),
	path = require('path'),
	util = require('util');

var IMAGE_EXTS = ['.png', '.jpg', '.gif'];

function readable_filesize(size) {
       /* Metric. Deal with it. */
       if (size < 1000)
               return size + ' B';
       if (size < 1000000)
               return Math.round(size / 1000) + ' KB';
       size = Math.round(size / 100000).toString();
       return size.slice(0, -1) + '.' + size.slice(-1) + ' MB';
}

function get_thumb_specs(pinky) {
	if (pinky)
		return {dims: config.PINKY_DIMENSIONS,
				quality: config.PINKY_QUALITY, ext: '.jpg'}
	return {dims: config.THUMB_DIMENSIONS, quality: config.THUMB_QUALITY,
			ext: 'l.jpg'};
}

exports.get_image_view = function (image, imgnm, pinky) {
	if (!image.cache) {
		var d = image.dims;
		image.cache = {src: '' + image.time + IMAGE_EXTS[image.ext],
			size: readable_filesize(image.size),
			thumb_dims: d.slice(0, 4),
			pinky_dims: [d[0], d[1], d[4], d[5]]};
	}
	return {src: image.cache.src, size: image.cache.size, MD5: image.MD5,
		thumb: image.time + (pinky ? '.jpg' : 'l.jpg'), imgnm: imgnm,
		dims: pinky ? image.cache.pinky_dims : image.cache.thumb_dims};
};

exports.ImageUpload = function (clients, allocate_post, set_post_image,
			broadcast, status) {
	this.clients = clients;
	this.allocate_post = allocate_post;
	this.set_post_image = set_post_image;
	this.broadcast = broadcast;
	this.status = status;
};

var IU = exports.ImageUpload.prototype;

var validFields = ['client_id', 'alloc'];

IU.handle_request = function (req, resp) {
	this.resp = resp;
	if (!config.IMAGE_UPLOAD) {
		resp.writeHead(403, {'Content-Type': 'text/plain'});
		resp.end('No upload.');
		return;
	}
	var form = new formidable.IncomingForm();
	form.maxFieldsSize = 2048;
	form.onPart = function (part) {
		if (part.filename && part.name == 'image')
			form.handlePart(part);
		else if (!part.filename && validFields.indexOf(part.name) >= 0)
			form.handlePart(part);
		else
			this._error('Superfluous field.');
	};
	form.parse(req, this.parse_form.bind(this));
};

IU.parse_form = function (err, fields, files) {
	if (err) {
		console.log("Upload error: " + err);
		var code = 500;
		err = '' + (err.message || err);
		return this.failure(err);
	}

	var image = files.image;
	if (!image)
		return this.failure('No image.');
	this.image = image;
	var client = this.clients[fields.client_id];
	if (!client)
		return this.failure('Invalid client id.');
	this.client = client;

	if (client.uploading) {
		this.failure('Already uploading.');
		/* previous line negated client.uploading, so restore it */
		client.uploading = true;
		return;
	}
	client.uploading = true;
	if (client.post && client.post.image)
		return this.failure('Image already exists.');
	var ext = path.extname(image.filename).toLowerCase();
	if (ext == '.jpeg')
		ext = '.jpg';
	image.tagged_path = ext.replace('.', '') + ':' + image.path;
	image.ext = IMAGE_EXTS.indexOf(ext);
	if (image.ext < 0)
		return this.failure('Invalid image format.');
	if (fields.alloc) {
		try {
			this.alloc = JSON.parse(fields.alloc);
		}
		catch (e) {
			return this.failure('Bad alloc.');
		}
	}
	else if (!client.post)
		return this.failure('Missing alloc.');
	this.process();
}

IU.process = function () {
	this.status('Verifying...');
	var image = this.image;
	image.pinky = (this.client.post && this.client.post.op) ||
			(this.alloc && this.alloc.op);

	var specs = get_thumb_specs(image.pinky);
	var self = this;
	flow.exec(function () {
		self.MD5_image(this);
	}, function (MD5) {
		image.MD5 = MD5;
		db.check_duplicate_image(MD5, this);
	}, function (err, found) {
		if (err)
			return self.failure('Duplicate image check failed.');
		if (found) {
			self.image = found;
			self.image.filename = image.filename;
			self.image.pinky = image.pinky;
			return self.adapt_existing();
		}
		self.read_image_filesize(this);
	}, function (size) {
		image.size = size;
		self.read_image_dimensions(image.tagged_path, this);
	}, function (w, h) {
		if (!w || !h)
			return self.failure('Invalid image dimensions.');
		if (w > config.IMAGE_WIDTH_MAX)
			return self.failure('Image is too wide.');
		if (h > config.IMAGE_HEIGHT_MAX)
			return self.failure('Image is too tall.');
		image.dims = [w, h];
		image.thumb_path = image.path + '_thumb';
		self.status('Thumbnailing...');
		self.resize_image(image.tagged_path, image.thumb_path,
				specs.dims, specs.quality, this);
	}, function () {
		self.read_image_dimensions(image.thumb_path, this);
	}, function (w, h) {
		if (image.pinky)
			image.dims.push(0, 0, w, h);
		else
			image.dims.push(w, h, 0, 0);
		self.status('Publishing...');
		image.time = new Date().getTime();
		image.src = image.time + IMAGE_EXTS[image.ext];
		self.dest = path.join(config.IMAGE_DIR, image.src);
		self.handle_exec('mv -- ' + image.path + ' ' + self.dest,
				"Couldn't publish image.", this);
	}, function () {
		image.path = self.dest;
		image.thumb = image.time + specs.ext;
		self.nail = path.join(config.THUMB_DIR, image.thumb);
		self.handle_exec('mv -- ' + image.thumb_path + ' ' + self.nail,
				"Couldn't publish thumbnail.", this);
	}, function () {
		image.thumb_path = self.nail;
		db.insert_image(image, this);
	}, function (err, id) {
		if (err)
			return upload_failure("Couldn't add image to DB.");
		image.id = id;
		self.publish();
	});
};

IU.adapt_existing = function () {
	var image = this.image;
	var index = image.pinky ? 4 : 2;
	var specs = get_thumb_specs(image.pinky);
	image.src = image.time + IMAGE_EXTS[image.ext];
	image.thumb = image.time + specs.ext;
	if (image.dims[index]) {
		this.status('Publishing...');
		return this.publish();
	}
	image.thumb_path = path.join(config.THUMB_DIR, image.thumb);
	var self = this;
	flow.exec(function () {
		self.status('Thumbnailing...');
		/* Don't set image.src since we don't want to delete it
		 * on failure as it is shared */
		self.resize_image(path.join(config.IMAGE_DIR, image.src),
			image.thumb_path, specs.dims, specs.quality, this);
	}, function () {
		self.read_image_dimensions(image.thumb_path, this);
	}, function (w, h) {
		image.dims[index] = w;
		image.dims[index + 1] = h;
		self.status('Publishing...');
		db.update_thumbnail_dimensions(image.id, image.pinky, w, h,
				this);
	}, function (err) {
		if (err)
			return self.failure("Secondary thumbnail failure.");
		self.publish();
	});

};

IU.read_image_filesize = function (callback) {
	var self = this;
	fs.stat(this.image.path, function (err, stat) {
		if (err)
			return self.failure('Internal filesize error.');
		if (stat.size > config.IMAGE_FILESIZE_MAX)
			self.failure('File is too large.');
		else
			callback(stat.size);
	});
};

IU.read_image_dimensions = function (path, callback) {
	var self = this;
	exec('identify ' + path, function (error, stdout, stderr) {
		if (error) {
			console.log(stderr);
			return self.failure('Corrupt image.');
		}
		var m = stdout.match(/.* (\d+)x(\d+) /);
		if (!m)
			return self.failure('Corrupt image.');
		callback(parseInt(m[1]), parseInt(m[2]));
	});
};

IU.MD5_image = function (callback) {
	var self = this;
	exec('md5sum -b ' + this.image.path, function (error, stdout, stderr) {
		if (error) {
			console.log(stderr);
			return self.failure('Hashing error.');
		}
		callback(stdout.match(/^([\da-f]+)/)[1]);
	});
};

IU.resize_image = function (src, dest, dims, quality, callback) {
	this.handle_exec('convert ' + src + '[0] -gamma 0.454545 ' +
			'-filter lanczos -resize ' + dims + ' -gamma 2.2 ' +
			'-quality ' + quality + ' jpg:' + dest,
		'Conversion error.', callback);
};

IU.handle_exec = function (cmd, err_desc, callback) {
	var self = this;
	exec(cmd, function (error, stdout, stderr) {
		if (stdout)
			console.log(stdout);
		if (stderr)
			util.error(stderr);
		if (error == null && !stderr)
			callback();
		else
			self.failure(err_desc);
	});
};

IU.failure = function (err_desc) {
	this.iframe_call('upload_error', err_desc);
	var image = this.image;
	if (image) {
		if (image.path)
			fs.unlink(image.path);
		if (image.thumb_path)
			fs.unlink(image.thumb_path);
		if (image.id) {
			/* TODO: Remove DB row */
		}
	}
	if (this.client)
		this.client.uploading = false;
};

IU.publish = function () {
	var imgnm = this.image.filename.substr(0, 256);
	var image = {
		time: this.image.time, dims: this.image.dims,
		size: this.image.size, MD5: this.image.MD5,
		id: this.image.id, ext: this.image.ext
	};
	var self = this;
	if (this.client.post) {
		var post = this.client.post;
		/* Text beat us here, discard alloc (if any) */
		db.append_image(post.num, image.id, imgnm, function (err) {
			if (err)
				return self.failure("Publishing failure.");
			var view = exports.get_image_view(image, imgnm,
					self.image.pinky);
			self.iframe_call('upload_complete', view);
			self.set_post_image(post, image, imgnm);
			self.broadcast(view, self.client);
			self.client.uploading = false;
		});
		return;
	}
	flow.exec(function () {
		if (!self.allocate_post(self.alloc, image, imgnm,
				self.client, this))
			self.client.uploading = false;
	}, function (err, alloc) {
		if (err) {
			console.error(err);
			return self.failure('Bad post.');
		}
		self.client.uploading = false;
		self.iframe_call('postForm.on_allocation', alloc);
	});
};

IU.iframe_call = function (func, param) {
	var resp = this.resp;
	param = param ? JSON.stringify(param) : '';
	resp.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
	resp.end('<!doctype html>\n<title></title>\n<script>'
		+ 'parent.' + func + '(' + param + ');</script>');
};
<commit_msg>White backgrounds on transparent thumbnails<commit_after>var config = require('./config'),
	db = require('./db'),
	exec = require('child_process').exec,
	flow = require('flow'),
	formidable = require('formidable'),
	fs = require('fs'),
	path = require('path'),
	util = require('util');

var IMAGE_EXTS = ['.png', '.jpg', '.gif'];

function readable_filesize(size) {
       /* Metric. Deal with it. */
       if (size < 1000)
               return size + ' B';
       if (size < 1000000)
               return Math.round(size / 1000) + ' KB';
       size = Math.round(size / 100000).toString();
       return size.slice(0, -1) + '.' + size.slice(-1) + ' MB';
}

function get_thumb_specs(pinky) {
	if (pinky)
		return {dims: config.PINKY_DIMENSIONS,
				quality: config.PINKY_QUALITY, ext: '.jpg'}
	return {dims: config.THUMB_DIMENSIONS, quality: config.THUMB_QUALITY,
			ext: 'l.jpg'};
}

exports.get_image_view = function (image, imgnm, pinky) {
	if (!image.cache) {
		var d = image.dims;
		image.cache = {src: '' + image.time + IMAGE_EXTS[image.ext],
			size: readable_filesize(image.size),
			thumb_dims: d.slice(0, 4),
			pinky_dims: [d[0], d[1], d[4], d[5]]};
	}
	return {src: image.cache.src, size: image.cache.size, MD5: image.MD5,
		thumb: image.time + (pinky ? '.jpg' : 'l.jpg'), imgnm: imgnm,
		dims: pinky ? image.cache.pinky_dims : image.cache.thumb_dims};
};

exports.ImageUpload = function (clients, allocate_post, set_post_image,
			broadcast, status) {
	this.clients = clients;
	this.allocate_post = allocate_post;
	this.set_post_image = set_post_image;
	this.broadcast = broadcast;
	this.status = status;
};

var IU = exports.ImageUpload.prototype;

var validFields = ['client_id', 'alloc'];

IU.handle_request = function (req, resp) {
	this.resp = resp;
	if (!config.IMAGE_UPLOAD) {
		resp.writeHead(403, {'Content-Type': 'text/plain'});
		resp.end('No upload.');
		return;
	}
	var form = new formidable.IncomingForm();
	form.maxFieldsSize = 2048;
	form.onPart = function (part) {
		if (part.filename && part.name == 'image')
			form.handlePart(part);
		else if (!part.filename && validFields.indexOf(part.name) >= 0)
			form.handlePart(part);
		else
			this._error('Superfluous field.');
	};
	form.parse(req, this.parse_form.bind(this));
};

IU.parse_form = function (err, fields, files) {
	if (err) {
		console.log("Upload error: " + err);
		var code = 500;
		err = '' + (err.message || err);
		return this.failure(err);
	}

	var image = files.image;
	if (!image)
		return this.failure('No image.');
	this.image = image;
	var client = this.clients[fields.client_id];
	if (!client)
		return this.failure('Invalid client id.');
	this.client = client;

	if (client.uploading) {
		this.failure('Already uploading.');
		/* previous line negated client.uploading, so restore it */
		client.uploading = true;
		return;
	}
	client.uploading = true;
	if (client.post && client.post.image)
		return this.failure('Image already exists.');
	var ext = path.extname(image.filename).toLowerCase();
	if (ext == '.jpeg')
		ext = '.jpg';
	image.tagged_path = ext.replace('.', '') + ':' + image.path;
	image.ext = IMAGE_EXTS.indexOf(ext);
	if (image.ext < 0)
		return this.failure('Invalid image format.');
	if (fields.alloc) {
		try {
			this.alloc = JSON.parse(fields.alloc);
		}
		catch (e) {
			return this.failure('Bad alloc.');
		}
	}
	else if (!client.post)
		return this.failure('Missing alloc.');
	this.process();
}

IU.process = function () {
	this.status('Verifying...');
	var image = this.image;
	image.pinky = (this.client.post && this.client.post.op) ||
			(this.alloc && this.alloc.op);

	var specs = get_thumb_specs(image.pinky);
	var self = this;
	flow.exec(function () {
		self.MD5_image(this);
	}, function (MD5) {
		image.MD5 = MD5;
		db.check_duplicate_image(MD5, this);
	}, function (err, found) {
		if (err)
			return self.failure('Duplicate image check failed.');
		if (found) {
			self.image = found;
			self.image.filename = image.filename;
			self.image.pinky = image.pinky;
			return self.adapt_existing();
		}
		self.read_image_filesize(this);
	}, function (size) {
		image.size = size;
		self.read_image_dimensions(image.tagged_path, this);
	}, function (w, h) {
		if (!w || !h)
			return self.failure('Invalid image dimensions.');
		if (w > config.IMAGE_WIDTH_MAX)
			return self.failure('Image is too wide.');
		if (h > config.IMAGE_HEIGHT_MAX)
			return self.failure('Image is too tall.');
		image.dims = [w, h];
		image.thumb_path = image.path + '_thumb';
		self.status('Thumbnailing...');
		self.resize_image(image.tagged_path, image.thumb_path,
				specs.dims, specs.quality, this);
	}, function () {
		self.read_image_dimensions(image.thumb_path, this);
	}, function (w, h) {
		if (image.pinky)
			image.dims.push(0, 0, w, h);
		else
			image.dims.push(w, h, 0, 0);
		self.status('Publishing...');
		image.time = new Date().getTime();
		image.src = image.time + IMAGE_EXTS[image.ext];
		self.dest = path.join(config.IMAGE_DIR, image.src);
		self.handle_exec('mv -- ' + image.path + ' ' + self.dest,
				"Couldn't publish image.", this);
	}, function () {
		image.path = self.dest;
		image.thumb = image.time + specs.ext;
		self.nail = path.join(config.THUMB_DIR, image.thumb);
		self.handle_exec('mv -- ' + image.thumb_path + ' ' + self.nail,
				"Couldn't publish thumbnail.", this);
	}, function () {
		image.thumb_path = self.nail;
		db.insert_image(image, this);
	}, function (err, id) {
		if (err)
			return upload_failure("Couldn't add image to DB.");
		image.id = id;
		self.publish();
	});
};

IU.adapt_existing = function () {
	var image = this.image;
	var index = image.pinky ? 4 : 2;
	var specs = get_thumb_specs(image.pinky);
	image.src = image.time + IMAGE_EXTS[image.ext];
	image.thumb = image.time + specs.ext;
	if (image.dims[index]) {
		this.status('Publishing...');
		return this.publish();
	}
	image.thumb_path = path.join(config.THUMB_DIR, image.thumb);
	var self = this;
	flow.exec(function () {
		self.status('Thumbnailing...');
		/* Don't set image.src since we don't want to delete it
		 * on failure as it is shared */
		self.resize_image(path.join(config.IMAGE_DIR, image.src),
			image.thumb_path, specs.dims, specs.quality, this);
	}, function () {
		self.read_image_dimensions(image.thumb_path, this);
	}, function (w, h) {
		image.dims[index] = w;
		image.dims[index + 1] = h;
		self.status('Publishing...');
		db.update_thumbnail_dimensions(image.id, image.pinky, w, h,
				this);
	}, function (err) {
		if (err)
			return self.failure("Secondary thumbnail failure.");
		self.publish();
	});

};

IU.read_image_filesize = function (callback) {
	var self = this;
	fs.stat(this.image.path, function (err, stat) {
		if (err)
			return self.failure('Internal filesize error.');
		if (stat.size > config.IMAGE_FILESIZE_MAX)
			self.failure('File is too large.');
		else
			callback(stat.size);
	});
};

IU.read_image_dimensions = function (path, callback) {
	var self = this;
	exec('identify ' + path, function (error, stdout, stderr) {
		if (error) {
			console.log(stderr);
			return self.failure('Corrupt image.');
		}
		var m = stdout.match(/.* (\d+)x(\d+) /);
		if (!m)
			return self.failure('Corrupt image.');
		callback(parseInt(m[1]), parseInt(m[2]));
	});
};

IU.MD5_image = function (callback) {
	var self = this;
	exec('md5sum -b ' + this.image.path, function (error, stdout, stderr) {
		if (error) {
			console.log(stderr);
			return self.failure('Hashing error.');
		}
		callback(stdout.match(/^([\da-f]+)/)[1]);
	});
};

IU.resize_image = function (src, dest, dims, quality, callback) {
	this.handle_exec('convert ' + src + '[0] -gamma 0.454545 ' +
			'-filter lanczos -resize ' + dims + ' -gamma 2.2 ' +
			'-background white -mosaic +matte ' +
			'-quality ' + quality + ' jpg:' + dest,
		'Conversion error.', callback);
};

IU.handle_exec = function (cmd, err_desc, callback) {
	var self = this;
	exec(cmd, function (error, stdout, stderr) {
		if (stdout)
			console.log(stdout);
		if (stderr)
			util.error(stderr);
		if (error == null && !stderr)
			callback();
		else
			self.failure(err_desc);
	});
};

IU.failure = function (err_desc) {
	this.iframe_call('upload_error', err_desc);
	var image = this.image;
	if (image) {
		if (image.path)
			fs.unlink(image.path);
		if (image.thumb_path)
			fs.unlink(image.thumb_path);
		if (image.id) {
			/* TODO: Remove DB row */
		}
	}
	if (this.client)
		this.client.uploading = false;
};

IU.publish = function () {
	var imgnm = this.image.filename.substr(0, 256);
	var image = {
		time: this.image.time, dims: this.image.dims,
		size: this.image.size, MD5: this.image.MD5,
		id: this.image.id, ext: this.image.ext
	};
	var self = this;
	if (this.client.post) {
		var post = this.client.post;
		/* Text beat us here, discard alloc (if any) */
		db.append_image(post.num, image.id, imgnm, function (err) {
			if (err)
				return self.failure("Publishing failure.");
			var view = exports.get_image_view(image, imgnm,
					self.image.pinky);
			self.iframe_call('upload_complete', view);
			self.set_post_image(post, image, imgnm);
			self.broadcast(view, self.client);
			self.client.uploading = false;
		});
		return;
	}
	flow.exec(function () {
		if (!self.allocate_post(self.alloc, image, imgnm,
				self.client, this))
			self.client.uploading = false;
	}, function (err, alloc) {
		if (err) {
			console.error(err);
			return self.failure('Bad post.');
		}
		self.client.uploading = false;
		self.iframe_call('postForm.on_allocation', alloc);
	});
};

IU.iframe_call = function (func, param) {
	var resp = this.resp;
	param = param ? JSON.stringify(param) : '';
	resp.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
	resp.end('<!doctype html>\n<title></title>\n<script>'
		+ 'parent.' + func + '(' + param + ');</script>');
};
<|endoftext|>


<commit_before>function create()
{
    if (arguments.length < 1)
        printUsage("create");

    var shouldSymbolicallyLink = false,
        index = 0,
        count = arguments.length,

        template = "Application",
        destination = "";

    for (; index < count; ++index)
    {
        var argument = arguments[index];

        switch (arguments[index])
        {
            case "-l":          shouldSymbolicallyLink = true;
                                break;

            case "--template":  template = arguments[++index];
                                break;

            default:            destination = argument;
        }
    }

    var sourceTemplate = new File(OBJJ_HOME + "/lib/steam/Templates/" + template),
        destinationProject = new File(destination);

    if (!destinationProject.exists())
    {
        exec(["cp", "-vR", sourceTemplate.getCanonicalPath(), destinationProject.getCanonicalPath()], true);

        createFrameworksInFile(destinationProject, shouldSymbolicallyLink);
    }
    else
        System.out.println("Directory already exists");
}
<commit_msg>Added better templating support.<commit_after>function create()
{
    if (arguments.length < 1)
        printUsage("create");

    var shouldSymbolicallyLink = false,
        index = 0,
        count = arguments.length,

        template = "Application",
        destination = "";

    for (; index < count; ++index)
    {
        var argument = arguments[index];

        switch (arguments[index])
        {
            case "-l":          shouldSymbolicallyLink = true;
                                break;

            case "--template":  template = arguments[++index];
                                break;

            default:            destination = argument;
        }
    }

    var sourceTemplate = new File(OBJJ_HOME + "/lib/steam/Templates/" + template),
        destinationProject = new File(destination);

    if (!destinationProject.exists())
    {
        exec(["cp", "-vR", sourceTemplate.getCanonicalPath(), destinationProject.getCanonicalPath()], true);

        var files = getFiles(destinationProject, ['j', "plist", "html"]),
            index = 0,
            count = files.length;

        for (; index < count; ++index)
        {
            var file = files[index],
                contents = readFile(file);

            contents = contents.replace(/__Product__/g, destinationProject.getName());

            writeContentsToFile(contents, file);
        }

        createFrameworksInFile(destinationProject, shouldSymbolicallyLink);
    }
    else
        System.out.println("Directory already exists");
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react'
import { Navbar, Nav, NavItem, Modal, Button } from 'react-bootstrap'
import { actions, Control, Errors, Form } from 'react-redux-form'
import { LinkContainer } from 'react-router-bootstrap'

const Navigation = React.createClass({
  getInitialState () {
    return {show: false}
  },

  open () {
    this.setState({show: true})
  },

  close () {
    this.setState({show: false})
  },

  handleLogout () {
    this.props.logout()
  },

  onSubmit (values) {
    const { email, password } = values
    this.props.login(email, password)
      .then(action => {
        if (action.type === 'LOGIN_SUCCESS') {
          this.close()
        } else {
          this.props.dispatch(actions.setErrors('forms.loginCredentials', 'The email address or password that you\'ve entered is incorrect'))
        }
      })
  },

  render () {
    const user = this.props.account.user
    const loggedInButtons = (

        <NavItem eventKey={1} href='/user/{user.id}'>{user ? user.firstName : null}</NavItem>
        <NavItem eventKey={2} onClick={this.handleLogout}>Log out</NavItem>

    )
    const loggedOutButtons = (

        <LinkContainer to='/join'><NavItem eventKey={1}>Sign Up</NavItem></LinkContainer>
        <NavItem eventKey={2} onClick={this.open}>Log in</NavItem>

    )
    const loginModal = (
      <Modal show={this.state.show} onHide={this.close}>
        <Form model='forms.loginCredentials' onSubmit={this.onSubmit}>
          <Modal.Header closeButton>
            <Modal.Title>Log in</Modal.Title>
          </Modal.Header>
          <Modal.Body>

                Email:
                <Control.text className='loginEmailField' model='.email' />

                Password:
                <Control model='.password' type='password' />

            <Errors model='forms.loginCredentials' />
          </Modal.Body>
          <Modal.Footer>
            <Button type='submit'>Send</Button>
          </Modal.Footer>
        </Form>
      </Modal>
    )
    return (

        <Navbar inverse collapseOnSelect>
          <Navbar.Header>
            <Navbar.Brand>
              GoFlat
            </Navbar.Brand>
            <Navbar.Toggle />
          </Navbar.Header>
          <Navbar.Collapse>
            {user ? loggedInButtons : loggedOutButtons}
          </Navbar.Collapse>
        </Navbar>
        {loginModal}

    )
  }
})

Navigation.propTypes = {
  dispatch: PropTypes.func.isRequired,
  login: PropTypes.func.isRequired,
  logout: PropTypes.func.isRequired
}

export default Navigation
<commit_msg>Logout redirects to landing page - closes #89<commit_after>import React, { PropTypes } from 'react'
import { Navbar, Nav, NavItem, Modal, Button } from 'react-bootstrap'
import { actions, Control, Errors, Form } from 'react-redux-form'
import { LinkContainer } from 'react-router-bootstrap'
import { browserHistory } from 'react-router'

const Navigation = React.createClass({
  getInitialState () {
    return {show: false}
  },

  open () {
    this.setState({show: true})
  },

  close () {
    this.setState({show: false})
  },

  handleLogout () {
    this.props.logout()
    browserHistory.push('/')
  },

  onSubmit (values) {
    const { email, password } = values
    this.props.login(email, password)
      .then(action => {
        if (action.type === 'LOGIN_SUCCESS') {
          this.close()
        } else {
          this.props.dispatch(actions.setErrors('forms.loginCredentials', 'The email address or password that you\'ve entered is incorrect'))
        }
      })
  },

  render () {
    const user = this.props.account.user
    const loggedInButtons = (

        <NavItem eventKey={1} href='/user/{user.id}'>{user ? user.firstName : null}</NavItem>
        <NavItem eventKey={2} onClick={this.handleLogout}>Log out</NavItem>

    )
    const loggedOutButtons = (

        <LinkContainer to='/join'><NavItem eventKey={1}>Sign Up</NavItem></LinkContainer>
        <NavItem eventKey={2} onClick={this.open}>Log in</NavItem>

    )
    const loginModal = (
      <Modal show={this.state.show} onHide={this.close}>
        <Form model='forms.loginCredentials' onSubmit={this.onSubmit}>
          <Modal.Header closeButton>
            <Modal.Title>Log in</Modal.Title>
          </Modal.Header>
          <Modal.Body>

                Email:
                <Control.text className='loginEmailField' model='.email' />

                Password:
                <Control model='.password' type='password' />

            <Errors model='forms.loginCredentials' />
          </Modal.Body>
          <Modal.Footer>
            <Button type='submit'>Send</Button>
          </Modal.Footer>
        </Form>
      </Modal>
    )
    return (

        <Navbar inverse collapseOnSelect>
          <Navbar.Header>
            <Navbar.Brand>
              GoFlat
            </Navbar.Brand>
            <Navbar.Toggle />
          </Navbar.Header>
          <Navbar.Collapse>
            {user ? loggedInButtons : loggedOutButtons}
          </Navbar.Collapse>
        </Navbar>
        {loginModal}

    )
  }
})

Navigation.propTypes = {
  dispatch: PropTypes.func.isRequired,
  login: PropTypes.func.isRequired,
  logout: PropTypes.func.isRequired
}

export default Navigation
<|endoftext|>


<commit_before>"use strict";

module.exports = function(imports) {

    // TODO: update this for groups

    let ObjectId = imports.modules.mongoose.Types.ObjectId;
    let Promise = imports.modules.Promise;

    let util = imports.util;

    let Chat = imports.models.Chat;
    let User = imports.models.User;

    let io = imports.socketio;

    // { [userId]: [ { sockets: [Socket] } ] }
    let online_clients = {};

    let sio = {};

    io.on("connection", Promise.coroutine(function*(socket) {
        let sess = socket.request.session.userId && (yield User.findOne({
            _id: socket.request.session.userId
        }));
        if (sess) {
            try {

                if (!(sess._id in online_clients)) { // later

                    let chats = yield Chat.find({
                        team: sess.team,
                        $or: [{
                            userMembers: new ObjectId(sess._id)
                        }, {
                            subdivisionMembers: {
                                "$in": sess.groups,
                            }
                        }]
                    }, {
                        _id: 1
                    });

                    let chatIds = chats.map(chat => chat._id.toString());

                    for (let user_id in online_clients) {
                        if (online_clients[user_id].chats.hasAnythingFrom(chatIds)) {
                            for (let sock of online_clients[user_id].sockets) {
                                io.to(sock).emit("joined", {
                                    _id: sess._id
                                });
                            }
                        }
                    }
                    online_clients[sess._id] = {
                        chats: chatIds,
                        sockets: []
                    };
                }
                online_clients[sess._id].sockets.push(socket.id);

            } catch (err) {
                console.error(err);
            }
        }

        socket.on("disconnect", function() {
            if (!sess || !(sess._id in online_clients)) {
                // TODO: sometimes online_clients[sess._id] doesnt exist
                // (maybe because it takes time for the mongo query to execute
                // and add user chats to the online_clients object at the sess._id index)
                return;
            }

            let index = online_clients[sess._id].sockets.indexOf(socket.id);
            if (index != -1) {
                online_clients[sess._id].sockets.splice(index, 1); // remove the socket from the list of sockets for the user

                if (online_clients[sess._id].sockets.length == 0) { // if no clients remain for the user

                    let chatIds = online_clients[sess._id].chats;
                    delete online_clients[sess._id]; // remove from online clients

                    for (let user_id in online_clients) { // notify other clients that they went offline
                        if (online_clients[user_id].chats.hasAnythingFrom(chatIds)) { // if they have any chats in common
                            for (let sock of online_clients[user_id].sockets) {
                                io.to(sock).emit("left", {
                                    _id: sess._id
                                });
                            }
                        }
                    }

                }
            }
        });

        socket.on("sendMessage", util.handler(function*(data) {

            let now = new Date();
            let content = util.normalizeDisplayedText(data.content);
//            let content = data.content;
            let chatId = data.chatId;

            yield Chat.update({
                $and: [
                    { _id: chatId },
                    util.hiddenGroups.audienceQuery(sess),
                ],
            }, {
                $push: {
                    messages: {
                        $each: [{
                            author: sess._id,
                            content: content,
                            timestamp: now,
                        }],
                        $position: 0
                    }
                },
                updated_at: now,
            });

            let message = {
                author: sess,
                content: content,
                timestamp: now,
            };

            let chat = yield Chat.findOne({
                _id: chatId,
            });
            let users = yield util.hiddenGroups.getUsersIn(chat.audience);
            let userIds = users
                .map(user => user._id)
                .filter(userId => userId.toString() in online_clients);
            for (let userId of userIds) {
                for (let sock of online_clients[userId.toString()].sockets) {
                    if (sock !== socket.id) {
                        io.to(sock).emit("message", {
                            chatId: chatId,
                            message: message,
                        });
                    }
                }
            }
            socket.emit("message-sent", {
                chatId: chatId,
                content: content,
            });

        }));

        // TODO: if a user has multiple clients and sends a message, display sent message on all clients

        socket.on("get clients", function() {
            socket.emit("get clients", Object.keys(online_clients));
        });

        // TODO: send new chats over socket.io

        socket.on("start typing", function(data) {
            for (let user_id of Object.keys(online_clients)) {
                if (~online_clients[user_id].chats.indexOf(data.chat_id) && user_id != sess._id) {
                    for (let sock of online_clients[user_id].sockets) {
                        io.to(sock).emit("start typing", data);
                    }
                }
            }
        });

        socket.on("stop typing", function(data) {
            for (let user_id of Object.keys(online_clients)) {
                if (online_clients[user_id].chats.indexOf(data.chat_id) != -1 && user_id != sess._id) {
                    for (let sock of online_clients[user_id].sockets) {
                        io.to(sock).emit("stop typing", data);
                    }
                }
            }
        });

    }));

    return sio;
};
<commit_msg>Fix typing indicator<commit_after>"use strict";

module.exports = function(imports) {

    // TODO: update this for groups

    let ObjectId = imports.modules.mongoose.Types.ObjectId;
    let Promise = imports.modules.Promise;

    let util = imports.util;

    let Chat = imports.models.Chat;
    let User = imports.models.User;

    let io = imports.socketio;

    // { [userId]: [ { sockets: [Socket] } ] }
    let online_clients = {};

    let sio = {};

    io.on("connection", Promise.coroutine(function*(socket) {
        let sess = socket.request.session.userId && (yield User.findOne({
            _id: socket.request.session.userId
        }));
        if (sess) {
            try {

                if (!(sess._id in online_clients)) { // later

                    let chats = yield Chat.find(util.hiddenGroups.audienceQuery(sess));

                    let chatIds = chats.map(chat => chat._id.toString());

                    for (let user_id in online_clients) {
                        if (online_clients[user_id].chats.hasAnythingFrom(chatIds)) {
                            for (let sock of online_clients[user_id].sockets) {
                                io.to(sock).emit("joined", {
                                    _id: sess._id
                                });
                            }
                        }
                    }
                    online_clients[sess._id] = {
                        chats: chatIds,
                        sockets: []
                    };
                }
                online_clients[sess._id].sockets.push(socket.id);

            } catch (err) {
                console.error(err);
            }
        }

        socket.on("disconnect", function() {
            if (!sess || !(sess._id in online_clients)) {
                // TODO: sometimes online_clients[sess._id] doesnt exist
                // (maybe because it takes time for the mongo query to execute
                // and add user chats to the online_clients object at the sess._id index)
                return;
            }

            let index = online_clients[sess._id].sockets.indexOf(socket.id);
            if (index != -1) {
                online_clients[sess._id].sockets.splice(index, 1); // remove the socket from the list of sockets for the user

                if (online_clients[sess._id].sockets.length == 0) { // if no clients remain for the user

                    let chatIds = online_clients[sess._id].chats;
                    delete online_clients[sess._id]; // remove from online clients

                    for (let user_id in online_clients) { // notify other clients that they went offline
                        if (online_clients[user_id].chats.hasAnythingFrom(chatIds)) { // if they have any chats in common
                            for (let sock of online_clients[user_id].sockets) {
                                io.to(sock).emit("left", {
                                    _id: sess._id
                                });
                            }
                        }
                    }

                }
            }
        });

        socket.on("sendMessage", util.handler(function*(data) {

            let now = new Date();
            let content = util.normalizeDisplayedText(data.content);
//            let content = data.content;
            let chatId = data.chatId;

            yield Chat.update({
                $and: [
                    { _id: chatId },
                    util.hiddenGroups.audienceQuery(sess),
                ],
            }, {
                $push: {
                    messages: {
                        $each: [{
                            author: sess._id,
                            content: content,
                            timestamp: now,
                        }],
                        $position: 0
                    }
                },
                updated_at: now,
            });

            let message = {
                author: sess,
                content: content,
                timestamp: now,
            };

            let chat = yield Chat.findOne({
                _id: chatId,
            });
            let users = yield util.hiddenGroups.getUsersIn(chat.audience);
            let userIds = users
                .map(user => user._id)
                .filter(userId => userId.toString() in online_clients);
            for (let userId of userIds) {
                for (let sock of online_clients[userId.toString()].sockets) {
                    if (sock !== socket.id) {
                        io.to(sock).emit("message", {
                            chatId: chatId,
                            message: message,
                        });
                    }
                }
            }
            socket.emit("message-sent", {
                chatId: chatId,
                content: content,
            });

        }));

        // TODO: if a user has multiple clients and sends a message, display sent message on all clients

        socket.on("get clients", function() {
            socket.emit("get clients", Object.keys(online_clients));
        });

        // TODO: send new chats over socket.io

        socket.on("start typing", function(data) {
            for (let user_id of Object.keys(online_clients)) {
                if (~online_clients[user_id].chats.indexOf(data.chatId) && user_id != sess._id) {
                    for (let sock of online_clients[user_id].sockets) {
                        io.to(sock).emit("start typing", data);
                    }
                }
            }
        });

        socket.on("stop typing", function(data) {
            for (let user_id of Object.keys(online_clients)) {
                if (online_clients[user_id].chats.indexOf(data.chatId) != -1 && user_id != sess._id) {
                    for (let sock of online_clients[user_id].sockets) {
                        io.to(sock).emit("stop typing", data);
                    }
                }
            }
        });

    }));

    return sio;
};
<|endoftext|>


<commit_before>module.exports = function (grunt) {
  var cmd = require('../lib/cmd');
  var git = require('../lib/git');
  var task = require('../lib/grunt');
  var traceur = require('../lib/traceur');
  var version = require('../lib/version')(grunt);

  return {
    deploy: {
      command: cmd(
        'rm -rf .tmp',
        'mkdir -p .tmp',
        'cd .tmp',
        'git clone git@github.com:skatejs/skatejs.github.io .',
        'ls -a1 | grep -v "^\.git$" | grep -v "^\.$" | grep -v "^\.\.$" | xargs rm -rf',
        'cp -rf ../docs/build/* ./',
        'git add .',
        'git commit -am "Update documentation."',
        'git push',
        'cd ..',
        'rm -rf .tmp'
      )
    },
    dist: {
      command: cmd(
        'rm -rf dist',
        traceur('src/skate.js', 'dist/skate.js')
      )
    },
    docs: {
      command: traceur('docs/src/scripts/index.js', 'docs/build/scripts/index.js')
    },
    installBower: {
      command: './node_modules/.bin/bower install'
    },
    installTraceur: {
      command: cmd(
        'cd ./node_modules/traceur',
        'npm install'
      )
    },
    release: {
      command: cmd(
        task('replace:version --tag ' + version.next()),
        task('test'),
        task('dist'),
        git('commit -am "' + version() + ' -> ' + version.next() + '"'),
        git('tag -a ' + version.next() + ' -m ' + version.next()),
        git('push'),
        git('push --tags')
      )
    },
    test: {
      command: cmd(
        'rm -rf .tmp',
        traceur('test/unit.js', '.tmp/run-unit-tests.js')
      )
    }
  };
};
<commit_msg>Add npm publish command to release task.<commit_after>module.exports = function (grunt) {
  'use strict';

  var cmd = require('../lib/cmd');
  var git = require('../lib/git');
  var task = require('../lib/grunt');
  var traceur = require('../lib/traceur');
  var version = require('../lib/version')(grunt);

  return {
    deploy: {
      command: cmd(
        'rm -rf .tmp',
        'mkdir -p .tmp',
        'cd .tmp',
        'git clone git@github.com:skatejs/skatejs.github.io .',
        'ls -a1 | grep -v "^\.git$" | grep -v "^\.$" | grep -v "^\.\.$" | xargs rm -rf',
        'cp -rf ../docs/build/* ./',
        'git add .',
        'git commit -am "Update documentation."',
        'git push',
        'cd ..',
        'rm -rf .tmp'
      )
    },
    dist: {
      command: cmd(
        'rm -rf dist',
        traceur('src/skate.js', 'dist/skate.js')
      )
    },
    docs: {
      command: traceur('docs/src/scripts/index.js', 'docs/build/scripts/index.js')
    },
    installBower: {
      command: './node_modules/.bin/bower install'
    },
    installTraceur: {
      command: cmd(
        'cd ./node_modules/traceur',
        'npm install'
      )
    },
    release: {
      command: cmd(
        task('replace:version --tag ' + version.next()),
        task('test'),
        task('dist'),
        git('commit -am "' + version() + ' -> ' + version.next() + '"'),
        git('tag -a ' + version.next() + ' -m ' + version.next()),
        git('push'),
        git('push --tags')
        'npm publish'
      )
    },
    test: {
      command: cmd(
        'rm -rf .tmp',
        traceur('test/unit.js', '.tmp/run-unit-tests.js')
      )
    }
  };
};
<|endoftext|>


<commit_before>var childProcess = require("child_process");

var gssh = __dirname + "/../../scripts/gssh";

function findServers(pattern, options, callback) {
    if (!callback && typeof options == "function") {
        return findServers(pattern, {}, options);
    }

    childProcess.exec(gssh + " --print-names " + pattern, function (err, stdout) {
        if (err) return callback(err);

        var serverNames = stdout.split("\n")
            .filter(function(name) { return !!name; })
            .map(function (name) { return name.replace(/ubuntu@/, ""); });

        return callback(null,  serverNames);
    });

}

function runCommand(pattern, command, options, callback) {
    if (!callback && typeof options == "function") {
        return runCommand(pattern, command, {}, options);
    }

    var parallel = ""
    if (options.parallel) {
        parallel = " -P"
    }

    var gsshCommand = gssh + " " + parallel + " " + pattern + " '" + command + "'";
    childProcess.exec(gsshCommand, function (err, stdout) {
        return callback(err, stdout);
    });
}

module.exports = {
    find: findServers,
    run: runCommand
}<commit_msg>Const should be capital<commit_after>var childProcess = require("child_process");

var GSSH = __dirname + "/../../scripts/gssh";

function findServers(pattern, options, callback) {
    if (!callback && typeof options == "function") {
        return findServers(pattern, {}, options);
    }

    childProcess.exec(GSSH + " --print-names " + pattern, function (err, stdout) {
        if (err) return callback(err);

        var serverNames = stdout.split("\n")
            .filter(function(name) { return !!name; })
            .map(function (name) { return name.replace(/ubuntu@/, ""); });

        return callback(null,  serverNames);
    });

}

function runCommand(pattern, command, options, callback) {
    if (!callback && typeof options == "function") {
        return runCommand(pattern, command, {}, options);
    }

    var parallel = ""
    if (options.parallel) {
        parallel = " -P"
    }

    var gsshCommand = GSSH + " " + parallel + " " + pattern + " '" + command + "'";
    childProcess.exec(gsshCommand, function (err, stdout) {
        return callback(err, stdout);
    });
}

module.exports = {
    find: findServers,
    run: runCommand
}<|endoftext|>


<commit_before>'use strict';

var inherits = require('inherits');

var RuleProvider = require('diagram-js/lib/features/rules/RuleProvider');

/**
 * A custom rule provider that decides what elements can be
 * dropped where based on a `vendor:allowDrop` BPMN extension.
 *
 * See {@link BpmnRules} for the default implementation
 * of BPMN 2.0 modeling rules provided by bpmn-js.
 *
 * @param {EventBus} eventBus
 */
function CustomRules(eventBus) {
    RuleProvider.call(this, eventBus);
}

inherits(CustomRules, RuleProvider);

CustomRules.$inject = [ 'eventBus' ];

module.exports = CustomRules;

CustomRules.prototype.init = function() {

    this.addRule('shapes.move',2000, function(context) {

        var target = context.target;

        if(!!target){   // !!=cast in boolean
            var shape = context.shapes[0]; // Remove [0] and check for a better solution
            var shapeBo = shape.businessObject,
                targetBo = target.businessObject;
            if(shape.type=="bpmn:UserTask"){
                var allowDrop = targetBo.get('isAppEnsemble');
                return !!allowDrop;
            }
            //else if()

        }

    });
};<commit_msg>Marked new TODOs<commit_after>'use strict';

var inherits = require('inherits');

var RuleProvider = require('diagram-js/lib/features/rules/RuleProvider');

/**
 * A custom rule provider that decides what elements can be
 * dropped where based on a `vendor:allowDrop` BPMN extension.
 *
 * See {@link BpmnRules} for the default implementation
 * of BPMN 2.0 modeling rules provided by bpmn-js.
 *
 * @param {EventBus} eventBus
 */
function CustomRules(eventBus) {
    RuleProvider.call(this, eventBus);
}

inherits(CustomRules, RuleProvider);

CustomRules.$inject = [ 'eventBus' ];

module.exports = CustomRules;

CustomRules.prototype.init = function() {

    this.addRule('shapes.move',2000, function(context) {

        var target = context.target;

        if(!!target){   // !!=cast in boolean
            var shape = context.shapes[0]; // Remove [0] and check for a better solution
            var shapeBo = shape.businessObject,
                targetBo = target.businessObject;
            if(shape.type=="bpmn:UserTask"){
                var allowDrop = targetBo.get('isAppEnsemble');
                return !!allowDrop;
            }
            //else if() TODO:Add functionality, that no other tasks (everthing else is ok) can be dragged into an appEnsemble (evtl ber shapebo->descriptor and then search for parent bpmn:Task

        }

    });
};<|endoftext|>


<commit_before>Travis.Controllers.Builds.Show = Ember.Object.extend({
  buildBinding: 'parent.build',
  repositoryBinding: 'parent.repository',

  init: function() {
    this._super();
    Ember.run.later(this.updateTimes.bind(this), Travis.UPDATE_TIMES_INTERVAL);
    var self = this;

    this.view = Ember.View.create({
      controller: this,
      repositoryBinding: 'controller.repository',
      contentBinding: 'controller.build',
      jobsBinding: 'controller.jobs',
      templateName: 'app/templates/builds/show'
    });
  },

  destroy: function() {
    if(this.view) {
      this.view.$().remove();
      this.view.destroy();
    }
  },

  updateTimes: function() {
    var build  = this.get('build');
    if(build) build.updateTimes();

    var matrix = this.getPath('build.matrix');
    if(matrix) $.each(matrix.toArray(), function(ix, job) { job.updateTimes(); }.bind(this));

    Ember.run.later(this.updateTimes.bind(this), Travis.UPDATE_TIMES_INTERVAL);
  },

  _buildObserver: function() {
    if(this.getPath('build.isReady') && this.getPath('build.matrix.length') === 0) {
      this.get('build').refresh();
    }
    if(this.getPath('build.isReady') && this.getPath('build.matrix.length') == 1 && this.getPath('build.matrix').objectAt(0).get('log') === null) {
      // TODO why does firstObject not work here?
      this.getPath('build.matrix').objectAt(0).refresh();
    }
  }.observes('build.status')
});
<commit_msg>fix for builds with only one matrix entry not updating the log in realtime :fist:<commit_after>Travis.Controllers.Builds.Show = Ember.Object.extend({
  buildBinding: 'parent.build',
  repositoryBinding: 'parent.repository',

  init: function() {
    this._super();
    Ember.run.later(this.updateTimes.bind(this), Travis.UPDATE_TIMES_INTERVAL);
    var self = this;

    this.view = Ember.View.create({
      controller: this,
      repositoryBinding: 'controller.repository',
      contentBinding: 'controller.build',
      jobsBinding: 'controller.jobs',
      templateName: 'app/templates/builds/show'
    });
  },

  buildDidChange: function() {
    var build = this.get('build');

    if (build.get('isLoaded')) {
      this.subscribeToFirstJob();
    } else {
      build.addObserver('isLoaded', this, 'subscribeToFirstJob');
    }
  }.observes('build'),

  subscribeToFirstJob: function() {
    if (this.getPath('build.matrix.length') > 1) {
      return true;
    }

    var jobs = this.getPath('build.matrix'),
        job  = jobs.objectAt(0);

    if(job.get('isReady') && (job.get('state') != 'finished')) {
      job.subscribe();
    }
  },

  destroy: function() {
    if(this.view) {
      this.view.$().remove();
      this.view.destroy();
    }
  },

  updateTimes: function() {
    var build  = this.get('build');
    if(build) build.updateTimes();

    var matrix = this.getPath('build.matrix');
    if(matrix) $.each(matrix.toArray(), function(ix, job) { job.updateTimes(); }.bind(this));

    Ember.run.later(this.updateTimes.bind(this), Travis.UPDATE_TIMES_INTERVAL);
  },

  _buildObserver: function() {
    if(this.getPath('build.isReady') && this.getPath('build.matrix.length') === 0) {
      this.get('build').refresh();
    }
    if(this.getPath('build.isReady') && this.getPath('build.matrix.length') == 1 && this.getPath('build.matrix').objectAt(0).get('log') === null) {
      // TODO why does firstObject not work here?
      this.getPath('build.matrix').objectAt(0).refresh();
    }
  }.observes('build.status')
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule DraftEditor.react
 * @typechecks
 * @flow
 * @preventMunge
 */

'use strict';

const DefaultDraftBlockRenderMap = require('DefaultDraftBlockRenderMap');
const DefaultDraftInlineStyle = require('DefaultDraftInlineStyle');
const DraftEditorCompositionHandler = require('DraftEditorCompositionHandler');
const DraftEditorContents = require('DraftEditorContents.react');
const DraftEditorDragHandler = require('DraftEditorDragHandler');
const DraftEditorEditHandler = require('DraftEditorEditHandler');
const DraftEditorPlaceholder = require('DraftEditorPlaceholder.react');
const EditorState = require('EditorState');
const React = require('React');
const ReactDOM = require('ReactDOM');
const Scroll = require('Scroll');
const Style = require('Style');
const UserAgent = require('UserAgent');

const cx = require('cx');
const emptyFunction = require('emptyFunction');
const generateRandomKey = require('generateRandomKey');
const getDefaultKeyBinding = require('getDefaultKeyBinding');
const nullthrows = require('nullthrows');
const getScrollPosition = require('getScrollPosition');

import type {BlockMap} from 'BlockMap';
import type {DraftEditorModes} from 'DraftEditorModes';
import type {DraftEditorProps, DraftEditorDefaultProps} from 'DraftEditorProps';
import type {DraftScrollPosition} from 'DraftScrollPosition';

const isIE = UserAgent.isBrowser('IE');

// IE does not support the `input` event on contentEditable, so we can't
// observe spellcheck behavior.
const allowSpellCheck = !isIE;

// Define a set of handler objects to correspond to each possible `mode`
// of editor behavior.
const handlerMap = {
  'edit': DraftEditorEditHandler,
  'composite': DraftEditorCompositionHandler,
  'drag': DraftEditorDragHandler,
  'cut': null,
  'render': null,
};

type State = {
  containerKey: number,
};

/**
 * `DraftEditor` is the root editor component. It composes a `contentEditable`
 * div, and provides a wide variety of useful function props for managing the
 * state of the editor. See `DraftEditorProps` for details.
 */
class DraftEditor extends React.Component {
  props: DraftEditorProps;
  state: State;

  static defaultProps: DraftEditorDefaultProps = {
    blockRenderMap: DefaultDraftBlockRenderMap,
    blockRendererFn: emptyFunction.thatReturnsNull,
    blockStyleFn: emptyFunction.thatReturns(''),
    keyBindingFn: getDefaultKeyBinding,
    readOnly: false,
    spellCheck: false,
    stripPastedStyles: false,
  };

  _blockSelectEvents: boolean;
  _clipboard: ?BlockMap;
  _handler: ?Object;
  _dragCount: number;
  _internalDrag: boolean;
  _editorKey: string;
  _placeholderAccessibilityID: string;
  _latestEditorState: EditorState;
  _pendingStateFromBeforeInput: void | EditorState;

  /**
   * Define proxies that can route events to the current handler.
   */
  _onBeforeInput: Function;
  _onBlur: Function;
  _onCharacterData: Function;
  _onCompositionEnd: Function;
  _onCompositionStart: Function;
  _onCopy: Function;
  _onCut: Function;
  _onDragEnd: Function;
  _onDragOver: Function;
  _onDragStart: Function;
  _onDrop: Function;
  _onInput: Function;
  _onFocus: Function;
  _onKeyDown: Function;
  _onKeyPress: Function;
  _onKeyUp: Function;
  _onMouseDown: Function;
  _onMouseUp: Function;
  _onPaste: Function;
  _onSelect: Function;

  focus: () => void;
  blur: () => void;
  setMode: (mode: DraftEditorModes) => void;
  exitCurrentMode: () => void;
  restoreEditorDOM: (scrollPosition?: DraftScrollPosition) => void;
  setClipboard: (clipboard: ?BlockMap) => void;
  getClipboard: () => ?BlockMap;
  getEditorKey: () => string;
  update: (editorState: EditorState) => void;
  onDragEnter: () => void;
  onDragLeave: () => void;

  constructor(props: DraftEditorProps) {
    super(props);

    this._blockSelectEvents = false;
    this._clipboard = null;
    this._handler = null;
    this._dragCount = 0;
    this._editorKey = generateRandomKey();
    this._placeholderAccessibilityID = 'placeholder-' + this._editorKey;
    this._latestEditorState = props.editorState;

    this._onBeforeInput = this._buildHandler('onBeforeInput');
    this._onBlur = this._buildHandler('onBlur');
    this._onCharacterData = this._buildHandler('onCharacterData');
    this._onCompositionEnd = this._buildHandler('onCompositionEnd');
    this._onCompositionStart = this._buildHandler('onCompositionStart');
    this._onCopy = this._buildHandler('onCopy');
    this._onCut = this._buildHandler('onCut');
    this._onDragEnd = this._buildHandler('onDragEnd');
    this._onDragOver = this._buildHandler('onDragOver');
    this._onDragStart = this._buildHandler('onDragStart');
    this._onDrop = this._buildHandler('onDrop');
    this._onInput = this._buildHandler('onInput');
    this._onFocus = this._buildHandler('onFocus');
    this._onKeyDown = this._buildHandler('onKeyDown');
    this._onKeyPress = this._buildHandler('onKeyPress');
    this._onKeyUp = this._buildHandler('onKeyUp');
    this._onMouseDown = this._buildHandler('onMouseDown');
    this._onMouseUp = this._buildHandler('onMouseUp');
    this._onPaste = this._buildHandler('onPaste');
    this._onSelect = this._buildHandler('onSelect');

    // Manual binding for public and internal methods.
    this.focus = this._focus.bind(this);
    this.blur = this._blur.bind(this);
    this.setMode = this._setMode.bind(this);
    this.exitCurrentMode = this._exitCurrentMode.bind(this);
    this.restoreEditorDOM = this._restoreEditorDOM.bind(this);
    this.setClipboard = this._setClipboard.bind(this);
    this.getClipboard = this._getClipboard.bind(this);
    this.getEditorKey = () => this._editorKey;
    this.update = this._update.bind(this);
    this.onDragEnter = this._onDragEnter.bind(this);
    this.onDragLeave = this._onDragLeave.bind(this);

    // See `_restoreEditorDOM()`.
    this.state = {containerKey: 0};
  }

  /**
   * Build a method that will pass the event to the specified handler method.
   * This allows us to look up the correct handler function for the current
   * editor mode, if any has been specified.
   */
  _buildHandler(eventName: string): Function {
    return (e) => {
      if (!this.props.readOnly) {
        const method = this._handler && this._handler[eventName];
        method && method(this, e);
      }
    };
  }

  _showPlaceholder(): boolean {
    return (
      !!this.props.placeholder &&
      !this.props.editorState.isInCompositionMode() &&
      !this.props.editorState.getCurrentContent().hasText()
    );
  }

  _renderPlaceholder(): ?React.Element<any> {
    if (this._showPlaceholder()) {
      return (
        <DraftEditorPlaceholder
          text={nullthrows(this.props.placeholder)}
          editorState={this.props.editorState}
          textAlignment={this.props.textAlignment}
          accessibilityID={this._placeholderAccessibilityID}
        />
      );
    }
    return null;
  }

  render(): React.Element<any> {
    const {readOnly, textAlignment} = this.props;
    const rootClass = cx({
      'DraftEditor/root': true,
      'DraftEditor/alignLeft': textAlignment === 'left',
      'DraftEditor/alignRight': textAlignment === 'right',
      'DraftEditor/alignCenter': textAlignment === 'center',
    });

    const contentStyle = {
      outline: 'none',
      whiteSpace: 'pre-wrap',
      wordWrap: 'break-word',
    };

    return (
      <div className={rootClass}>
        {this._renderPlaceholder()}
        <div
          className={cx('DraftEditor/editorContainer')}
          key={'editor' + this.state.containerKey}
          ref="editorContainer">
          <div
            aria-activedescendant={
              readOnly ? null : this.props.ariaActiveDescendantID
            }
            aria-autocomplete={readOnly ? null : this.props.ariaAutoComplete}
            aria-describedby={
              this._showPlaceholder() ? this._placeholderAccessibilityID : null
            }
            aria-expanded={readOnly ? null : this.props.ariaExpanded}
            aria-haspopup={readOnly ? null : this.props.ariaHasPopup}
            aria-label={this.props.ariaLabel}
            aria-owns={readOnly ? null : this.props.ariaOwneeID}
            className={cx('public/DraftEditor/content')}
            contentEditable={!readOnly}
            data-testid={this.props.webDriverTestID}
            onBeforeInput={this._onBeforeInput}
            onBlur={this._onBlur}
            onCompositionEnd={this._onCompositionEnd}
            onCompositionStart={this._onCompositionStart}
            onCopy={this._onCopy}
            onCut={this._onCut}
            onDragEnd={this._onDragEnd}
            onDragEnter={this.onDragEnter}
            onDragLeave={this.onDragLeave}
            onDragOver={this._onDragOver}
            onDragStart={this._onDragStart}
            onDrop={this._onDrop}
            onFocus={this._onFocus}
            onInput={this._onInput}
            onKeyDown={this._onKeyDown}
            onKeyPress={this._onKeyPress}
            onKeyUp={this._onKeyUp}
            onMouseUp={this._onMouseUp}
            onPaste={this._onPaste}
            onSelect={this._onSelect}
            ref="editor"
            role={readOnly ? null : (this.props.role || 'textbox')}
            spellCheck={allowSpellCheck && this.props.spellCheck}
            style={contentStyle}
            suppressContentEditableWarning
            tabIndex={this.props.tabIndex}>
            <DraftEditorContents
              blockRenderMap={this.props.blockRenderMap}
              blockRendererFn={this.props.blockRendererFn}
              blockStyleFn={this.props.blockStyleFn}
              customStyleMap={
                {...DefaultDraftInlineStyle, ...this.props.customStyleMap}
              }
              customStyleFn={this.props.customStyleFn}
              editorKey={this._editorKey}
              editorState={this.props.editorState}
            />
          </div>
        </div>
      </div>
    );
  }

  componentDidMount(): void {
    this.setMode('edit');

    /**
     * IE has a hardcoded "feature" that attempts to convert link text into
     * anchors in contentEditable DOM. This breaks the editor's expectations of
     * the DOM, and control is lost. Disable it to make IE behave.
     * See: http://blogs.msdn.com/b/ieinternals/archive/2010/09/15/
     * ie9-beta-minor-change-list.aspx
     */
    if (isIE) {
      document.execCommand('AutoUrlDetect', false, false);
    }
  }

  /**
   * Prevent selection events from affecting the current editor state. This
   * is mostly intended to defend against IE, which fires off `selectionchange`
   * events regardless of whether the selection is set via the browser or
   * programmatically. We only care about selection events that occur because
   * of browser interaction, not re-renders and forced selections.
   */
  componentWillUpdate(nextProps: DraftEditorProps): void {
    this._blockSelectEvents = true;
    this._latestEditorState = nextProps.editorState;
  }

  componentDidUpdate(): void {
    this._blockSelectEvents = false;
  }

  /**
   * Used via `this.focus()`.
   *
   * Force focus back onto the editor node.
   *
   * Forcing focus causes the browser to scroll to the top of the editor, which
   * may be undesirable when the editor is taller than the viewport. To solve
   * this, either use a specified scroll position (in cases like `cut` behavior
   * where it should be restored to a known position) or store the current
   * scroll state and put it back in place after focus has been forced.
   */
  _focus(scrollPosition?: DraftScrollPosition): void {
    const {editorState} = this.props;
    const alreadyHasFocus = editorState.getSelection().getHasFocus();
    const editorNode = ReactDOM.findDOMNode(this.refs.editor);

    const scrollParent = Style.getScrollParent(editorNode);
    const {x, y} = scrollPosition || getScrollPosition(scrollParent);

    editorNode.focus();
    if (scrollParent === window) {
      window.scrollTo(x, y);
    } else {
      Scroll.setTop(scrollParent, y);
    }

    // On Chrome and Safari, calling focus on contenteditable focuses the
    // cursor at the first character. This is something you don't expect when
    // you're clicking on an input element but not directly on a character.
    // Put the cursor back where it was before the blur.
    if (!alreadyHasFocus) {
      this.update(
        EditorState.forceSelection(
          editorState,
          editorState.getSelection()
        )
      );
    }
  }

  _blur(): void {
    ReactDOM.findDOMNode(this.refs.editor).blur();
  }

  /**
   * Used via `this.setMode(...)`.
   *
   * Set the behavior mode for the editor component. This switches the current
   * handler module to ensure that DOM events are managed appropriately for
   * the active mode.
   */
  _setMode(mode: DraftEditorModes): void {
    this._handler = handlerMap[mode];
  }

  _exitCurrentMode(): void {
    this.setMode('edit');
  }

  /**
   * Used via `this.restoreEditorDOM()`.
   *
   * Force a complete re-render of the editor based on the current EditorState.
   * This is useful when we know we are going to lose control of the DOM
   * state (cut command, IME) and we want to make sure that reconciliation
   * occurs on a version of the DOM that is synchronized with our EditorState.
   */
  _restoreEditorDOM(scrollPosition?: DraftScrollPosition): void {
    this.setState({containerKey: this.state.containerKey + 1}, () => {
      this._focus(scrollPosition);
    });
  }

  /**
   * Used via `this.setClipboard(...)`.
   *
   * Set the clipboard state for a cut/copy event.
   */
  _setClipboard(clipboard: ?BlockMap): void {
    this._clipboard = clipboard;
  }

  /**
   * Used via `this.getClipboard()`.
   *
   * Retrieve the clipboard state for a cut/copy event.
   */
  _getClipboard(): ?BlockMap {
    return this._clipboard;
  }

  /**
   * Used via `this.update(...)`.
   *
   * Propagate a new `EditorState` object to higher-level components. This is
   * the method by which event handlers inform the `DraftEditor` component of
   * state changes. A component that composes a `DraftEditor` **must** provide
   * an `onChange` prop to receive state updates passed along from this
   * function.
   */
  _update(editorState: EditorState): void {
    this._latestEditorState = editorState;
    this.props.onChange(editorState);
  }

  /**
   * Used in conjunction with `_onDragLeave()`, by counting the number of times
   * a dragged element enters and leaves the editor (or any of its children),
   * to determine when the dragged element absolutely leaves the editor.
   */
  _onDragEnter(): void {
    this._dragCount++;
  }

  /**
   * See `_onDragEnter()`.
   */
  _onDragLeave(): void {
    this._dragCount--;
    if (this._dragCount === 0) {
      this.exitCurrentMode();
    }
  }
}

module.exports = DraftEditor;
<commit_msg>Fix editor losing focus after first word on Android. (#907)<commit_after>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule DraftEditor.react
 * @typechecks
 * @flow
 * @preventMunge
 */

'use strict';

const DefaultDraftBlockRenderMap = require('DefaultDraftBlockRenderMap');
const DefaultDraftInlineStyle = require('DefaultDraftInlineStyle');
const DraftEditorCompositionHandler = require('DraftEditorCompositionHandler');
const DraftEditorContents = require('DraftEditorContents.react');
const DraftEditorDragHandler = require('DraftEditorDragHandler');
const DraftEditorEditHandler = require('DraftEditorEditHandler');
const DraftEditorPlaceholder = require('DraftEditorPlaceholder.react');
const EditorState = require('EditorState');
const React = require('React');
const ReactDOM = require('ReactDOM');
const Scroll = require('Scroll');
const Style = require('Style');
const UserAgent = require('UserAgent');

const cx = require('cx');
const emptyFunction = require('emptyFunction');
const generateRandomKey = require('generateRandomKey');
const getDefaultKeyBinding = require('getDefaultKeyBinding');
const nullthrows = require('nullthrows');
const getScrollPosition = require('getScrollPosition');

import type {BlockMap} from 'BlockMap';
import type {DraftEditorModes} from 'DraftEditorModes';
import type {DraftEditorProps, DraftEditorDefaultProps} from 'DraftEditorProps';
import type {DraftScrollPosition} from 'DraftScrollPosition';

const isIE = UserAgent.isBrowser('IE');

// IE does not support the `input` event on contentEditable, so we can't
// observe spellcheck behavior.
const allowSpellCheck = !isIE;

// Define a set of handler objects to correspond to each possible `mode`
// of editor behavior.
const handlerMap = {
  'edit': DraftEditorEditHandler,
  'composite': DraftEditorCompositionHandler,
  'drag': DraftEditorDragHandler,
  'cut': null,
  'render': null,
};

type State = {
  contentsKey: number,
};

/**
 * `DraftEditor` is the root editor component. It composes a `contentEditable`
 * div, and provides a wide variety of useful function props for managing the
 * state of the editor. See `DraftEditorProps` for details.
 */
class DraftEditor extends React.Component {
  props: DraftEditorProps;
  state: State;

  static defaultProps: DraftEditorDefaultProps = {
    blockRenderMap: DefaultDraftBlockRenderMap,
    blockRendererFn: emptyFunction.thatReturnsNull,
    blockStyleFn: emptyFunction.thatReturns(''),
    keyBindingFn: getDefaultKeyBinding,
    readOnly: false,
    spellCheck: false,
    stripPastedStyles: false,
  };

  _blockSelectEvents: boolean;
  _clipboard: ?BlockMap;
  _handler: ?Object;
  _dragCount: number;
  _internalDrag: boolean;
  _editorKey: string;
  _placeholderAccessibilityID: string;
  _latestEditorState: EditorState;
  _pendingStateFromBeforeInput: void | EditorState;

  /**
   * Define proxies that can route events to the current handler.
   */
  _onBeforeInput: Function;
  _onBlur: Function;
  _onCharacterData: Function;
  _onCompositionEnd: Function;
  _onCompositionStart: Function;
  _onCopy: Function;
  _onCut: Function;
  _onDragEnd: Function;
  _onDragOver: Function;
  _onDragStart: Function;
  _onDrop: Function;
  _onInput: Function;
  _onFocus: Function;
  _onKeyDown: Function;
  _onKeyPress: Function;
  _onKeyUp: Function;
  _onMouseDown: Function;
  _onMouseUp: Function;
  _onPaste: Function;
  _onSelect: Function;

  focus: () => void;
  blur: () => void;
  setMode: (mode: DraftEditorModes) => void;
  exitCurrentMode: () => void;
  restoreEditorDOM: (scrollPosition?: DraftScrollPosition) => void;
  setClipboard: (clipboard: ?BlockMap) => void;
  getClipboard: () => ?BlockMap;
  getEditorKey: () => string;
  update: (editorState: EditorState) => void;
  onDragEnter: () => void;
  onDragLeave: () => void;

  constructor(props: DraftEditorProps) {
    super(props);

    this._blockSelectEvents = false;
    this._clipboard = null;
    this._handler = null;
    this._dragCount = 0;
    this._editorKey = generateRandomKey();
    this._placeholderAccessibilityID = 'placeholder-' + this._editorKey;
    this._latestEditorState = props.editorState;

    this._onBeforeInput = this._buildHandler('onBeforeInput');
    this._onBlur = this._buildHandler('onBlur');
    this._onCharacterData = this._buildHandler('onCharacterData');
    this._onCompositionEnd = this._buildHandler('onCompositionEnd');
    this._onCompositionStart = this._buildHandler('onCompositionStart');
    this._onCopy = this._buildHandler('onCopy');
    this._onCut = this._buildHandler('onCut');
    this._onDragEnd = this._buildHandler('onDragEnd');
    this._onDragOver = this._buildHandler('onDragOver');
    this._onDragStart = this._buildHandler('onDragStart');
    this._onDrop = this._buildHandler('onDrop');
    this._onInput = this._buildHandler('onInput');
    this._onFocus = this._buildHandler('onFocus');
    this._onKeyDown = this._buildHandler('onKeyDown');
    this._onKeyPress = this._buildHandler('onKeyPress');
    this._onKeyUp = this._buildHandler('onKeyUp');
    this._onMouseDown = this._buildHandler('onMouseDown');
    this._onMouseUp = this._buildHandler('onMouseUp');
    this._onPaste = this._buildHandler('onPaste');
    this._onSelect = this._buildHandler('onSelect');

    // Manual binding for public and internal methods.
    this.focus = this._focus.bind(this);
    this.blur = this._blur.bind(this);
    this.setMode = this._setMode.bind(this);
    this.exitCurrentMode = this._exitCurrentMode.bind(this);
    this.restoreEditorDOM = this._restoreEditorDOM.bind(this);
    this.setClipboard = this._setClipboard.bind(this);
    this.getClipboard = this._getClipboard.bind(this);
    this.getEditorKey = () => this._editorKey;
    this.update = this._update.bind(this);
    this.onDragEnter = this._onDragEnter.bind(this);
    this.onDragLeave = this._onDragLeave.bind(this);

    // See `_restoreEditorDOM()`.
    this.state = {contentsKey: 0};
  }

  /**
   * Build a method that will pass the event to the specified handler method.
   * This allows us to look up the correct handler function for the current
   * editor mode, if any has been specified.
   */
  _buildHandler(eventName: string): Function {
    return (e) => {
      if (!this.props.readOnly) {
        const method = this._handler && this._handler[eventName];
        method && method(this, e);
      }
    };
  }

  _showPlaceholder(): boolean {
    return (
      !!this.props.placeholder &&
      !this.props.editorState.isInCompositionMode() &&
      !this.props.editorState.getCurrentContent().hasText()
    );
  }

  _renderPlaceholder(): ?React.Element<any> {
    if (this._showPlaceholder()) {
      return (
        <DraftEditorPlaceholder
          text={nullthrows(this.props.placeholder)}
          editorState={this.props.editorState}
          textAlignment={this.props.textAlignment}
          accessibilityID={this._placeholderAccessibilityID}
        />
      );
    }
    return null;
  }

  render(): React.Element<any> {
    const {readOnly, textAlignment} = this.props;
    const rootClass = cx({
      'DraftEditor/root': true,
      'DraftEditor/alignLeft': textAlignment === 'left',
      'DraftEditor/alignRight': textAlignment === 'right',
      'DraftEditor/alignCenter': textAlignment === 'center',
    });

    const contentStyle = {
      outline: 'none',
      whiteSpace: 'pre-wrap',
      wordWrap: 'break-word',
    };

    return (
      <div className={rootClass}>
        {this._renderPlaceholder()}
        <div
          className={cx('DraftEditor/editorContainer')}
          ref="editorContainer">
          <div
            aria-activedescendant={
              readOnly ? null : this.props.ariaActiveDescendantID
            }
            aria-autocomplete={readOnly ? null : this.props.ariaAutoComplete}
            aria-describedby={
              this._showPlaceholder() ? this._placeholderAccessibilityID : null
            }
            aria-expanded={readOnly ? null : this.props.ariaExpanded}
            aria-haspopup={readOnly ? null : this.props.ariaHasPopup}
            aria-label={this.props.ariaLabel}
            aria-owns={readOnly ? null : this.props.ariaOwneeID}
            className={cx('public/DraftEditor/content')}
            contentEditable={!readOnly}
            data-testid={this.props.webDriverTestID}
            onBeforeInput={this._onBeforeInput}
            onBlur={this._onBlur}
            onCompositionEnd={this._onCompositionEnd}
            onCompositionStart={this._onCompositionStart}
            onCopy={this._onCopy}
            onCut={this._onCut}
            onDragEnd={this._onDragEnd}
            onDragEnter={this.onDragEnter}
            onDragLeave={this.onDragLeave}
            onDragOver={this._onDragOver}
            onDragStart={this._onDragStart}
            onDrop={this._onDrop}
            onFocus={this._onFocus}
            onInput={this._onInput}
            onKeyDown={this._onKeyDown}
            onKeyPress={this._onKeyPress}
            onKeyUp={this._onKeyUp}
            onMouseUp={this._onMouseUp}
            onPaste={this._onPaste}
            onSelect={this._onSelect}
            ref="editor"
            role={readOnly ? null : (this.props.role || 'textbox')}
            spellCheck={allowSpellCheck && this.props.spellCheck}
            style={contentStyle}
            suppressContentEditableWarning
            tabIndex={this.props.tabIndex}>
            <DraftEditorContents
              blockRenderMap={this.props.blockRenderMap}
              blockRendererFn={this.props.blockRendererFn}
              blockStyleFn={this.props.blockStyleFn}
              customStyleMap={
                {...DefaultDraftInlineStyle, ...this.props.customStyleMap}
              }
              customStyleFn={this.props.customStyleFn}
              editorKey={this._editorKey}
              editorState={this.props.editorState}
              key={'contents' + this.state.contentsKey}
            />
          </div>
        </div>
      </div>
    );
  }

  componentDidMount(): void {
    this.setMode('edit');

    /**
     * IE has a hardcoded "feature" that attempts to convert link text into
     * anchors in contentEditable DOM. This breaks the editor's expectations of
     * the DOM, and control is lost. Disable it to make IE behave.
     * See: http://blogs.msdn.com/b/ieinternals/archive/2010/09/15/
     * ie9-beta-minor-change-list.aspx
     */
    if (isIE) {
      document.execCommand('AutoUrlDetect', false, false);
    }
  }

  /**
   * Prevent selection events from affecting the current editor state. This
   * is mostly intended to defend against IE, which fires off `selectionchange`
   * events regardless of whether the selection is set via the browser or
   * programmatically. We only care about selection events that occur because
   * of browser interaction, not re-renders and forced selections.
   */
  componentWillUpdate(nextProps: DraftEditorProps): void {
    this._blockSelectEvents = true;
    this._latestEditorState = nextProps.editorState;
  }

  componentDidUpdate(): void {
    this._blockSelectEvents = false;
  }

  /**
   * Used via `this.focus()`.
   *
   * Force focus back onto the editor node.
   *
   * Forcing focus causes the browser to scroll to the top of the editor, which
   * may be undesirable when the editor is taller than the viewport. To solve
   * this, either use a specified scroll position (in cases like `cut` behavior
   * where it should be restored to a known position) or store the current
   * scroll state and put it back in place after focus has been forced.
   */
  _focus(scrollPosition?: DraftScrollPosition): void {
    const {editorState} = this.props;
    const alreadyHasFocus = editorState.getSelection().getHasFocus();
    const editorNode = ReactDOM.findDOMNode(this.refs.editor);

    const scrollParent = Style.getScrollParent(editorNode);
    const {x, y} = scrollPosition || getScrollPosition(scrollParent);

    editorNode.focus();
    if (scrollParent === window) {
      window.scrollTo(x, y);
    } else {
      Scroll.setTop(scrollParent, y);
    }

    // On Chrome and Safari, calling focus on contenteditable focuses the
    // cursor at the first character. This is something you don't expect when
    // you're clicking on an input element but not directly on a character.
    // Put the cursor back where it was before the blur.
    if (!alreadyHasFocus) {
      this.update(
        EditorState.forceSelection(
          editorState,
          editorState.getSelection()
        )
      );
    }
  }

  _blur(): void {
    ReactDOM.findDOMNode(this.refs.editor).blur();
  }

  /**
   * Used via `this.setMode(...)`.
   *
   * Set the behavior mode for the editor component. This switches the current
   * handler module to ensure that DOM events are managed appropriately for
   * the active mode.
   */
  _setMode(mode: DraftEditorModes): void {
    this._handler = handlerMap[mode];
  }

  _exitCurrentMode(): void {
    this.setMode('edit');
  }

  /**
   * Used via `this.restoreEditorDOM()`.
   *
   * Force a complete re-render of the DraftEditorContents based on the current
   * EditorState. This is useful when we know we are going to lose control of
   * the DOM state (cut command, IME) and we want to make sure that
   * reconciliation occurs on a version of the DOM that is synchronized with
   * our EditorState.
   */
  _restoreEditorDOM(scrollPosition?: DraftScrollPosition): void {
    this.setState({contentsKey: this.state.contentsKey + 1}, () => {
      this._focus(scrollPosition);
    });
  }

  /**
   * Used via `this.setClipboard(...)`.
   *
   * Set the clipboard state for a cut/copy event.
   */
  _setClipboard(clipboard: ?BlockMap): void {
    this._clipboard = clipboard;
  }

  /**
   * Used via `this.getClipboard()`.
   *
   * Retrieve the clipboard state for a cut/copy event.
   */
  _getClipboard(): ?BlockMap {
    return this._clipboard;
  }

  /**
   * Used via `this.update(...)`.
   *
   * Propagate a new `EditorState` object to higher-level components. This is
   * the method by which event handlers inform the `DraftEditor` component of
   * state changes. A component that composes a `DraftEditor` **must** provide
   * an `onChange` prop to receive state updates passed along from this
   * function.
   */
  _update(editorState: EditorState): void {
    this._latestEditorState = editorState;
    this.props.onChange(editorState);
  }

  /**
   * Used in conjunction with `_onDragLeave()`, by counting the number of times
   * a dragged element enters and leaves the editor (or any of its children),
   * to determine when the dragged element absolutely leaves the editor.
   */
  _onDragEnter(): void {
    this._dragCount++;
  }

  /**
   * See `_onDragEnter()`.
   */
  _onDragLeave(): void {
    this._dragCount--;
    if (this._dragCount === 0) {
      this.exitCurrentMode();
    }
  }
}

module.exports = DraftEditor;
<|endoftext|>


<commit_before>goog.provide('ol.renderer.Layer');

goog.require('goog.Disposable');
goog.require('goog.asserts');
goog.require('ol.ImageState');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.layer.Layer');
goog.require('ol.layer.LayerState');
goog.require('ol.source.Source');
goog.require('ol.source.State');
goog.require('ol.source.Tile');

/**
 * @constructor
 * @extends {goog.Disposable}
 * @param {ol.renderer.Map} mapRenderer Map renderer.
 * @param {ol.layer.Layer} layer Layer.
 * @suppress {checkStructDictInheritance}
 * @struct
 */
ol.renderer.Layer = function(mapRenderer, layer) {

  goog.base(this);

  /**
   * @private
   * @type {ol.renderer.Map}
   */
  this.mapRenderer_ = mapRenderer;

  /**
   * @private
   * @type {ol.layer.Layer}
   */
  this.layer_ = layer;

};
goog.inherits(ol.renderer.Layer, goog.Disposable);

/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {olx.FrameState} frameState Frame state.
 * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
 *     callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T
 */
ol.renderer.Layer.prototype.forEachFeatureAtPixel = goog.nullFunction;

/**
 * @protected
 * @return {ol.layer.Layer} Layer.
 */
ol.renderer.Layer.prototype.getLayer = function() {
  return this.layer_;
};

/**
 * @protected
 * @return {ol.Map} Map.
 */
ol.renderer.Layer.prototype.getMap = function() {
  return this.mapRenderer_.getMap();
};

/**
 * @protected
 * @return {ol.renderer.Map} Map renderer.
 */
ol.renderer.Layer.prototype.getMapRenderer = function() {
  return this.mapRenderer_;
};

/**
 * Handle changes in image state.
 * @param {goog.events.Event} event Image change event.
 * @protected
 */
ol.renderer.Layer.prototype.handleImageChange = function(event) {
  var image = /** @type {ol.Image} */ (event.target);
  if (image.getState() === ol.ImageState.LOADED) {
    this.renderIfReadyAndVisible();
  }
};

/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.layer.LayerState} layerState Layer state.
 */
ol.renderer.Layer.prototype.prepareFrame = goog.abstractMethod;

/**
 * @protected
 */
ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
  var layer = this.getLayer();
  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
    this.getMap().render();
  }
};

/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Tile} tileSource Tile source.
 * @protected
 */
ol.renderer.Layer.prototype.scheduleExpireCache =
    function(frameState, tileSource) {
  if (tileSource.canExpireCache()) {
    frameState.postRenderFunctions.push(
        goog.partial(
            /**
             * @param {ol.source.Tile} tileSource Tile source.
             * @param {ol.Map} map Map.
             * @param {olx.FrameState} frameState Frame state.
             */
            function(tileSource, map, frameState) {
              var tileSourceKey = goog.getUid(tileSource).toString();
              tileSource.expireCache(frameState.usedTiles[tileSourceKey]);
            }, tileSource));
  }
};

/**
 * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
 *     set (target).
 * @param {Array.<ol.Attribution>} attributions Attributions (source).
 * @protected
 */
ol.renderer.Layer.prototype.updateAttributions =
    function(attributionsSet, attributions) {
  if (goog.isDefAndNotNull(attributions)) {
    var attribution, i, ii;
    for (i = 0, ii = attributions.length; i < ii; ++i) {
      attribution = attributions[i];
      attributionsSet[goog.getUid(attribution).toString()] = attribution;
    }
  }
};

/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Source} source Source.
 * @protected
 */
ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
  var logo = source.getLogo();
  if (goog.isDef(logo)) {
    if (goog.isString(logo)) {
      frameState.logos[logo] = '';
    } else if (goog.isObject(logo)) {
      goog.asserts.assertString(logo.href);
      goog.asserts.assertString(logo.src);
      frameState.logos[logo.src] = logo.href;
    }
  }
};

/**
 * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles.
 * @param {ol.source.Tile} tileSource Tile source.
 * @param {number} z Z.
 * @param {ol.TileRange} tileRange Tile range.
 * @protected
 */
ol.renderer.Layer.prototype.updateUsedTiles =
    function(usedTiles, tileSource, z, tileRange) {
  // FIXME should we use tilesToDrawByZ instead?
  var tileSourceKey = goog.getUid(tileSource).toString();
  var zKey = z.toString();
  if (tileSourceKey in usedTiles) {
    if (zKey in usedTiles[tileSourceKey]) {
      usedTiles[tileSourceKey][zKey].extend(tileRange);
    } else {
      usedTiles[tileSourceKey][zKey] = tileRange;
    }
  } else {
    usedTiles[tileSourceKey] = {};
    usedTiles[tileSourceKey][zKey] = tileRange;
  }
};

/**
 * @param {function(ol.Tile): boolean} isLoadedFunction Function to
 *     determine if the tile is loaded.
 * @param {ol.source.Tile} tileSource Tile source.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @protected
 * @return {function(number, number, number): ol.Tile} Returns a tile if it is
 *     loaded.
 */
ol.renderer.Layer.prototype.createGetTileIfLoadedFunction =
    function(isLoadedFunction, tileSource, pixelRatio, projection) {
  return (
      /**
       * @param {number} z Z.
       * @param {number} x X.
       * @param {number} y Y.
       * @return {ol.Tile} Tile.
       */
      function(z, x, y) {
        var tile = tileSource.getTile(z, x, y, pixelRatio, projection);
        return isLoadedFunction(tile) ? tile : null;
      });
};

/**
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {ol.Size} size Size.
 * @protected
 * @return {ol.Coordinate} Snapped center.
 */
ol.renderer.Layer.prototype.snapCenterToPixel =
    function(center, resolution, size) {
  return [
    resolution * (Math.round(center[0] / resolution) + (size[0] % 2) / 2),
    resolution * (Math.round(center[1] / resolution) + (size[1] % 2) / 2)
  ];
};

/**
 * Manage tile pyramid.
 * This function performs a number of functions related to the tiles at the
 * current zoom and lower zoom levels:
 * - registers idle tiles in frameState.wantedTiles so that they are not
 *   discarded by the tile queue
 * - enqueues missing tiles
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Tile} tileSource Tile source.
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @param {ol.Extent} extent Extent.
 * @param {number} currentZ Current Z.
 * @param {number|undefined} preload Load low resolution tiles up to 'preload'
 *     levels.
 * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback.
 * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`.
 * @protected
 * @template T
 */
ol.renderer.Layer.prototype.manageTilePyramid = function(
    frameState, tileSource, tileGrid, pixelRatio, projection, extent,
    currentZ, preload, opt_tileCallback, opt_this) {
  var tileSourceKey = goog.getUid(tileSource).toString();
  if (!(tileSourceKey in frameState.wantedTiles)) {
    frameState.wantedTiles[tileSourceKey] = {};
  }
  var wantedTiles = frameState.wantedTiles[tileSourceKey];
  var tileQueue = frameState.tileQueue;
  var minZoom = tileGrid.getMinZoom();
  var tile, tileRange, tileResolution, x, y, z;
  if (!goog.isDef(preload)) {
    preload = 0;
  }
  for (z = currentZ; z >= minZoom; --z) {
    tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
    tileResolution = tileGrid.getResolution(z);
    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
        if (currentZ - z <= preload) {
          tile = tileSource.getTile(z, x, y, pixelRatio, projection);
          if (tile.getState() == ol.TileState.IDLE) {
            wantedTiles[tile.tileCoord.toString()] = true;
            if (!tileQueue.isKeyQueued(tile.getKey())) {
              tileQueue.enqueue([tile, tileSourceKey,
                tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
            }
          }
          if (goog.isDef(opt_tileCallback)) {
            opt_tileCallback.call(opt_this, tile);
          }
        } else {
          tileSource.useTile(z, x, y);
        }
      }
    }
  }
};
<commit_msg>Reuse tileRange in ol.renderer.Layer#manageTilePyramid<commit_after>goog.provide('ol.renderer.Layer');

goog.require('goog.Disposable');
goog.require('goog.asserts');
goog.require('ol.ImageState');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.layer.Layer');
goog.require('ol.layer.LayerState');
goog.require('ol.source.Source');
goog.require('ol.source.State');
goog.require('ol.source.Tile');

/**
 * @constructor
 * @extends {goog.Disposable}
 * @param {ol.renderer.Map} mapRenderer Map renderer.
 * @param {ol.layer.Layer} layer Layer.
 * @suppress {checkStructDictInheritance}
 * @struct
 */
ol.renderer.Layer = function(mapRenderer, layer) {

  goog.base(this);

  /**
   * @private
   * @type {ol.renderer.Map}
   */
  this.mapRenderer_ = mapRenderer;

  /**
   * @private
   * @type {ol.layer.Layer}
   */
  this.layer_ = layer;

};
goog.inherits(ol.renderer.Layer, goog.Disposable);

/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {olx.FrameState} frameState Frame state.
 * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature
 *     callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T
 */
ol.renderer.Layer.prototype.forEachFeatureAtPixel = goog.nullFunction;

/**
 * @protected
 * @return {ol.layer.Layer} Layer.
 */
ol.renderer.Layer.prototype.getLayer = function() {
  return this.layer_;
};

/**
 * @protected
 * @return {ol.Map} Map.
 */
ol.renderer.Layer.prototype.getMap = function() {
  return this.mapRenderer_.getMap();
};

/**
 * @protected
 * @return {ol.renderer.Map} Map renderer.
 */
ol.renderer.Layer.prototype.getMapRenderer = function() {
  return this.mapRenderer_;
};

/**
 * Handle changes in image state.
 * @param {goog.events.Event} event Image change event.
 * @protected
 */
ol.renderer.Layer.prototype.handleImageChange = function(event) {
  var image = /** @type {ol.Image} */ (event.target);
  if (image.getState() === ol.ImageState.LOADED) {
    this.renderIfReadyAndVisible();
  }
};

/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.layer.LayerState} layerState Layer state.
 */
ol.renderer.Layer.prototype.prepareFrame = goog.abstractMethod;

/**
 * @protected
 */
ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
  var layer = this.getLayer();
  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
    this.getMap().render();
  }
};

/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Tile} tileSource Tile source.
 * @protected
 */
ol.renderer.Layer.prototype.scheduleExpireCache =
    function(frameState, tileSource) {
  if (tileSource.canExpireCache()) {
    frameState.postRenderFunctions.push(
        goog.partial(
            /**
             * @param {ol.source.Tile} tileSource Tile source.
             * @param {ol.Map} map Map.
             * @param {olx.FrameState} frameState Frame state.
             */
            function(tileSource, map, frameState) {
              var tileSourceKey = goog.getUid(tileSource).toString();
              tileSource.expireCache(frameState.usedTiles[tileSourceKey]);
            }, tileSource));
  }
};

/**
 * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
 *     set (target).
 * @param {Array.<ol.Attribution>} attributions Attributions (source).
 * @protected
 */
ol.renderer.Layer.prototype.updateAttributions =
    function(attributionsSet, attributions) {
  if (goog.isDefAndNotNull(attributions)) {
    var attribution, i, ii;
    for (i = 0, ii = attributions.length; i < ii; ++i) {
      attribution = attributions[i];
      attributionsSet[goog.getUid(attribution).toString()] = attribution;
    }
  }
};

/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Source} source Source.
 * @protected
 */
ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
  var logo = source.getLogo();
  if (goog.isDef(logo)) {
    if (goog.isString(logo)) {
      frameState.logos[logo] = '';
    } else if (goog.isObject(logo)) {
      goog.asserts.assertString(logo.href);
      goog.asserts.assertString(logo.src);
      frameState.logos[logo.src] = logo.href;
    }
  }
};

/**
 * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles.
 * @param {ol.source.Tile} tileSource Tile source.
 * @param {number} z Z.
 * @param {ol.TileRange} tileRange Tile range.
 * @protected
 */
ol.renderer.Layer.prototype.updateUsedTiles =
    function(usedTiles, tileSource, z, tileRange) {
  // FIXME should we use tilesToDrawByZ instead?
  var tileSourceKey = goog.getUid(tileSource).toString();
  var zKey = z.toString();
  if (tileSourceKey in usedTiles) {
    if (zKey in usedTiles[tileSourceKey]) {
      usedTiles[tileSourceKey][zKey].extend(tileRange);
    } else {
      usedTiles[tileSourceKey][zKey] = tileRange;
    }
  } else {
    usedTiles[tileSourceKey] = {};
    usedTiles[tileSourceKey][zKey] = tileRange;
  }
};

/**
 * @param {function(ol.Tile): boolean} isLoadedFunction Function to
 *     determine if the tile is loaded.
 * @param {ol.source.Tile} tileSource Tile source.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @protected
 * @return {function(number, number, number): ol.Tile} Returns a tile if it is
 *     loaded.
 */
ol.renderer.Layer.prototype.createGetTileIfLoadedFunction =
    function(isLoadedFunction, tileSource, pixelRatio, projection) {
  return (
      /**
       * @param {number} z Z.
       * @param {number} x X.
       * @param {number} y Y.
       * @return {ol.Tile} Tile.
       */
      function(z, x, y) {
        var tile = tileSource.getTile(z, x, y, pixelRatio, projection);
        return isLoadedFunction(tile) ? tile : null;
      });
};

/**
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {ol.Size} size Size.
 * @protected
 * @return {ol.Coordinate} Snapped center.
 */
ol.renderer.Layer.prototype.snapCenterToPixel =
    function(center, resolution, size) {
  return [
    resolution * (Math.round(center[0] / resolution) + (size[0] % 2) / 2),
    resolution * (Math.round(center[1] / resolution) + (size[1] % 2) / 2)
  ];
};

/**
 * Manage tile pyramid.
 * This function performs a number of functions related to the tiles at the
 * current zoom and lower zoom levels:
 * - registers idle tiles in frameState.wantedTiles so that they are not
 *   discarded by the tile queue
 * - enqueues missing tiles
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Tile} tileSource Tile source.
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @param {ol.Extent} extent Extent.
 * @param {number} currentZ Current Z.
 * @param {number|undefined} preload Load low resolution tiles up to 'preload'
 *     levels.
 * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback.
 * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`.
 * @protected
 * @template T
 */
ol.renderer.Layer.prototype.manageTilePyramid = function(
    frameState, tileSource, tileGrid, pixelRatio, projection, extent,
    currentZ, preload, opt_tileCallback, opt_this) {
  var tileSourceKey = goog.getUid(tileSource).toString();
  if (!(tileSourceKey in frameState.wantedTiles)) {
    frameState.wantedTiles[tileSourceKey] = {};
  }
  var wantedTiles = frameState.wantedTiles[tileSourceKey];
  var tileQueue = frameState.tileQueue;
  var minZoom = tileGrid.getMinZoom();
  var tile, tileRange, tileResolution, x, y, z;
  if (!goog.isDef(preload)) {
    preload = 0;
  }
  for (z = currentZ; z >= minZoom; --z) {
    tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
    tileResolution = tileGrid.getResolution(z);
    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
        if (currentZ - z <= preload) {
          tile = tileSource.getTile(z, x, y, pixelRatio, projection);
          if (tile.getState() == ol.TileState.IDLE) {
            wantedTiles[tile.tileCoord.toString()] = true;
            if (!tileQueue.isKeyQueued(tile.getKey())) {
              tileQueue.enqueue([tile, tileSourceKey,
                tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
            }
          }
          if (goog.isDef(opt_tileCallback)) {
            opt_tileCallback.call(opt_this, tile);
          }
        } else {
          tileSource.useTile(z, x, y);
        }
      }
    }
  }
};
<|endoftext|>