<commit_before>import React, { Component } from 'react';
import CardItem from './card_item';
import {fetchPolicy} from '../actions';
import { connect } from 'react-redux';

class Card extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isSearchResult: props.isSearchResult,
      policyNumber: props.title
    }
  }
  handleSelect() {
    if (!this.state.isSearchResult) return;

    this.props.fetchPolicy(this.props.title);
  }

  render() {
    if (!this.props || this.props === undefined || this.props === {}) {
      return (
Loading...
);
    }

    const valueList = this.props.items.filter(
      (item) => !item.name.includes("ID")
    )
    .map((item) => {
      return (
        <CardItem key={item.name} name={item.name} value={item.value}/>
      );
    });

    return (

{this.props.title}

            { valueList }

    );
  }
}

export default connect(null,  {fetchPolicy} )(Card);
<commit_msg>simple syntax change<commit_after>import React, { Component } from 'react';
import CardItem from './card_item';
import {fetchPolicy} from '../actions';
import { connect } from 'react-redux';

class Card extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isSearchResult: props.isSearchResult,
      policyNumber: props.title
    }
  }
  handleSelect() {
    if (!this.state.isSearchResult) return;

    this.props.fetchPolicy(this.props.title);
  }

  render() {
    if (!this.props || this.props === undefined || this.props === {}) {
      return (
Loading...
);
    }
    const valueList = this.props.items.filter(
      (item) => { return !item.name.includes("ID")}
    )
    .map((item) => {
      return (
        <CardItem key={item.name} name={item.name} value={item.value}/>
      );
    });

    return (

{this.props.title}

            { valueList }

    );
  }
}

export default connect(null,  {fetchPolicy} )(Card);
<|endoftext|>


<commit_before>'use strict';

var Honeypot = require('project-honeypot');
var simpleRecaptcha = require('simple-recaptcha-new');
var pluginData = require('./plugin.json');
var winston = module.parent.require('winston');
var nconf = module.parent.require('nconf');
var async = module.parent.require('async');
var Meta = module.parent.require('./meta');
var user = module.parent.require('./user');
var topics = module.parent.require('./topics');
var db = module.parent.require('./database');

var akismet;
var honeypot;
var recaptchaArgs;
var pluginSettings;
var Plugin = module.exports;

pluginData.nbbId = pluginData.id.replace(/nodebb-plugin-/, '');

Plugin.load = function (params, callback) {

	var render = function (req, res, next) {
		res.render('admin/plugins/' + pluginData.nbbId, pluginData || {});
	};

	Meta.settings.get(pluginData.nbbId, function (err, settings) {
		if (err) {
			return callback(err);
		}
		if (!settings) {
			winston.warn('[plugins/' + pluginData.nbbId + '] Settings not set or could not be retrived!');
			return callback();
		}

		if (settings.akismetEnabled === 'on') {
			if (settings.akismetApiKey) {
				akismet = require('akismet').client({
					blog: nconf.get('url'),
					apiKey: settings.akismetApiKey
				});
				akismet.verifyKey(function (err, verified) {
					if (!verified) {
						winston.error('[plugins/' + pluginData.nbbId + '] Unable to verify Akismet API key.');
						akismet = null;
					}
				});
			} else {
				winston.error('[plugins/' + pluginData.nbbId + '] Akismet API Key not set!');
			}
		}

		if (settings.honeypotEnabled === 'on') {
			if (settings.honeypotApiKey) {
				honeypot = Honeypot(settings.honeypotApiKey);
			} else {
				winston.error('[plugins/' + pluginData.nbbId + '] Honeypot API Key not set!');
			}
		}

		if (settings.recaptchaEnabled === 'on') {
			if (settings.recaptchaPublicKey && settings.recaptchaPrivateKey) {

				recaptchaArgs = {
					publicKey: settings.recaptchaPublicKey,
					targetId: pluginData.nbbId + '-recaptcha-target',
					options: {
						// theme: settings.recaptchaTheme || 'clean',
						//todo: switch to custom theme, issue#9
						theme: 'clean',

						hl: (Meta.config.defaultLang || 'en').toLowerCase(),
						tabindex: settings.recaptchaTabindex || 0
					}
				};
			}
		}

		if (!settings.akismetMinReputationHam) {
			settings.akismetMinReputationHam = 10;
		}

		winston.info('[plugins/' + pluginData.nbbId + '] Settings loaded');
		pluginSettings = settings;

		params.router.get('/admin/plugins/' + pluginData.nbbId, params.middleware.admin.buildHeader, render);
		params.router.get('/api/admin/plugins/' + pluginData.nbbId, render);

		callback();
	});
};

Plugin.addCaptcha = function (data, callback) {
	if (recaptchaArgs) {
		var captcha = {
			label: 'Captcha',
			html: '' +
			'

' +
			'',
			styleName: pluginData.nbbId
		};
		if (data.templateData.regFormEntry && Array.isArray(data.templateData.regFormEntry)) {
			data.templateData.regFormEntry.push(captcha);
		} else {
			data.templateData.captcha = captcha;
		}
	}
	callback(null, data);
};

Plugin.onPostEdit = function(data, callback) {
	async.waterfall([
		function (next) {
			topics.getTopicField(data.post.tid, 'cid', next);
		},
		function (cid, next) {
			Plugin.checkReply({
				content: data.post.content,
				uid: data.post.uid,
				cid: cid,
				req: data.req,
			}, {type: 'post'}, function (err) {
				next(err, data);
			});
		},
	], callback);
};

Plugin.onTopicEdit = function(data, callback) {
	Plugin.checkReply({
		title: data.topic.title || '',
		uid: data.topic.uid,
		cid: data.topic.cid,
		req: data.req
	}, {type: 'topic'}, function(err) {
		callback(err, data);
	});
};

Plugin.onTopicPost = function(data, callback) {
	Plugin.checkReply(data, {type: 'topic'}, callback);
};

Plugin.onTopicReply = function(data, callback) {
	Plugin.checkReply(data, {type: 'post'}, callback);
};

Plugin.checkReply = function (data, options, callback) {
	if (typeof options === 'function') {
		callback = options;
		options = null;
	}
	options = options || {};

	// http://akismet.com/development/api/#comment-check
	if (!akismet || !data || !data.req) {
		return callback(null, data);
	}
	var userData;
	var akismetData;
	async.waterfall([
		function (next) {
			async.parallel({
				isAdmin: function(next) {
					user.isAdministrator(data.uid, next);
				},
				isModerator: function (next) {
					user.isModerator(data.uid, data.cid, next);
				},
				userData: function(next) {
					user.getUserFields(data.uid, ['username', 'reputation', 'email'], next);
				}
			}, next);
		},
		function (results, next) {
			userData = results.userData;
			if (results.isAdmin || results.isModerator) {
				return callback(null, data);
			}
			akismetData = {
				referrer: data.req.headers['referer'],
				user_ip: data.req.ip,
				user_agent: data.req.headers['user-agent'],
				permalink: nconf.get('url').replace(/\/$/, '') + data.req.path,
				comment_content: (data.title ? data.title + '\n\n' : '') + (data.content || ''),
				comment_author: userData.username,
				comment_author_email: userData.email,
				// https://github.com/akhoury/nodebb-plugin-spam-be-gone/issues/54
				comment_type: options.type === 'topic' ? 'forum-post' : 'comment'
			};
			akismet.checkSpam(akismetData, next);
		},
		function (spam, next) {
			if (!spam) {
				return callback(null, data);
			}

			if (parseInt(userData.reputation, 10) >= parseInt(pluginSettings.akismetMinReputationHam, 10)) {
				akismet.submitHam(akismetData, function (err) {
					if (err) {
						winston.error(err);
					}
				});
			}

			winston.verbose('[plugins/' + pluginData.nbbId + '] Post "' + akismetData.comment_content + '" by uid: ' + data.uid + ' username: ' + userData.username + '@' + data.req.ip + ' was flagged as spam and rejected.');
			next(new Error('Post content was flagged as spam by Akismet.com'));
		}
	], callback);
};

Plugin.checkRegister = function (data, callback) {
	async.parallel([
		function (next) {
			Plugin._honeypotCheck(data.req, data.res, data.userData, next);
		},
		function (next) {
			Plugin._recaptchaCheck(data.req, data.res, data.userData, next);
		}
	], function (err) {
		callback(err, data);
	});
};

Plugin.onPostFlagged = function (data) {
	var flagObj = data.flag;

	// Don't do anything if flag is not for a post and not for "spam" reason
	if (flagObj.type !== 'post' || flagObj.description !== 'Spam') {
		return;
	}

	if (akismet && pluginSettings.akismetFlagReporting && parseInt(flagObj.reporter.reputation, 10) >= parseInt(pluginSettings.akismetFlagReporting, 10)) {
		async.parallel({
			userData: function (next) {
				user.getUserFields(flagObj.target.uid, ['username', 'email'], next);
			},
			permalink: function (next) {
				topics.getTopicField(flagObj.target.tid, 'slug', next);
			},
			ip: function (next) {
				db.getSortedSetRevRange('uid:' + flagObj.target.uid + ':ip', 0, 1, next);
			}
		}, function (err, data) {
			// todo: we don't have access to the req here :/
			var submitted = {
				user_ip: data.ip ? data.ip[0] : '',
				permalink: nconf.get('url').replace(/\/$/, '') + '/topic/' + data.permalink,
				comment_author: data.userData.username,
				comment_author_email: data.userData.email,
				comment_content: flagObj.target.content,
				comment_type: 'forum-post'
			};

			akismet.submitSpam(submitted, function (err) {
				if (err) {
					winston.error('Error reporting to Akismet', err, submitted);
				}

				winston.info('Spam reported to Akismet.', submitted);
			});
		});
	}
};

Plugin._honeypotCheck = function (req, res, userData, next) {
	if (honeypot && req && req.ip) {
		honeypot.query(req.ip, function (err, results) {
			if (err) {
				winston.error(err);
				next(null, userData);
			} else {
				if (results && results.found && results.type) {
					if (results.type.spammer || results.type.suspicious) {
						var message = userData.username + ' | ' + userData.email + ' was detected as ' + (results.type.spammer ? 'spammer' : 'suspicious');

						winston.warn('[plugins/' + pluginData.nbbId + '] ' + message + ' and was denied registration.');
						next(new Error(message), userData);
					} else {
						next(null, userData);
					}
				} else {
					winston.verbose('[plugins/' + pluginData.nbbId + '] username:' + userData.username + ' ip:' + req.ip + ' was not found in Honeypot database');
					next(null, userData);
				}
			}
		});
	} else {
		next(null, userData);
	}
};

Plugin._recaptchaCheck = function (req, res, userData, next) {
	if (recaptchaArgs && req && req.ip && req.body) {

		simpleRecaptcha(
			pluginSettings.recaptchaPrivateKey,
			req.ip,
			req.body['g-recaptcha-response'],
			function (err) {
				if (err) {
					var message = err.Error || 'Captcha not verified, are you a robot?';
					winston.verbose('[plugins/' + pluginData.nbbId + '] ' + message);
					next(new Error(message), userData);
				} else {
					next(null, userData);
				}
			}
		);
	} else {
		next(null, userData);
	}
};

Plugin.admin = {
	menu: function (custom_header, callback) {
		custom_header.plugins.push({
			"route": '/plugins/' + pluginData.nbbId,
			"icon": pluginData.faIcon,
			"name": pluginData.name
		});

		callback(null, custom_header);
	}
};
<commit_msg>update requires<commit_after>'use strict';

var Honeypot = require('project-honeypot');
var simpleRecaptcha = require('simple-recaptcha-new');
var pluginData = require('./plugin.json');
var winston = require.main.require('winston');
var nconf = require.main.require('nconf');
var async = require.main.require('async');
var Meta = require.main.require('./src/meta');
var user = require.main.require('./src/user');
var topics = require.main.require('./src/topics');
var db = require.main.require('./src/database');

var akismet;
var honeypot;
var recaptchaArgs;
var pluginSettings;
var Plugin = module.exports;

pluginData.nbbId = pluginData.id.replace(/nodebb-plugin-/, '');

Plugin.load = function (params, callback) {

	var render = function (req, res, next) {
		res.render('admin/plugins/' + pluginData.nbbId, pluginData || {});
	};

	Meta.settings.get(pluginData.nbbId, function (err, settings) {
		if (err) {
			return callback(err);
		}
		if (!settings) {
			winston.warn('[plugins/' + pluginData.nbbId + '] Settings not set or could not be retrived!');
			return callback();
		}

		if (settings.akismetEnabled === 'on') {
			if (settings.akismetApiKey) {
				akismet = require('akismet').client({
					blog: nconf.get('url'),
					apiKey: settings.akismetApiKey
				});
				akismet.verifyKey(function (err, verified) {
					if (!verified) {
						winston.error('[plugins/' + pluginData.nbbId + '] Unable to verify Akismet API key.');
						akismet = null;
					}
				});
			} else {
				winston.error('[plugins/' + pluginData.nbbId + '] Akismet API Key not set!');
			}
		}

		if (settings.honeypotEnabled === 'on') {
			if (settings.honeypotApiKey) {
				honeypot = Honeypot(settings.honeypotApiKey);
			} else {
				winston.error('[plugins/' + pluginData.nbbId + '] Honeypot API Key not set!');
			}
		}

		if (settings.recaptchaEnabled === 'on') {
			if (settings.recaptchaPublicKey && settings.recaptchaPrivateKey) {

				recaptchaArgs = {
					publicKey: settings.recaptchaPublicKey,
					targetId: pluginData.nbbId + '-recaptcha-target',
					options: {
						// theme: settings.recaptchaTheme || 'clean',
						//todo: switch to custom theme, issue#9
						theme: 'clean',

						hl: (Meta.config.defaultLang || 'en').toLowerCase(),
						tabindex: settings.recaptchaTabindex || 0
					}
				};
			}
		}

		if (!settings.akismetMinReputationHam) {
			settings.akismetMinReputationHam = 10;
		}

		winston.info('[plugins/' + pluginData.nbbId + '] Settings loaded');
		pluginSettings = settings;

		params.router.get('/admin/plugins/' + pluginData.nbbId, params.middleware.admin.buildHeader, render);
		params.router.get('/api/admin/plugins/' + pluginData.nbbId, render);

		callback();
	});
};

Plugin.addCaptcha = function (data, callback) {
	if (recaptchaArgs) {
		var captcha = {
			label: 'Captcha',
			html: '' +
			'

' +
			'',
			styleName: pluginData.nbbId
		};
		if (data.templateData.regFormEntry && Array.isArray(data.templateData.regFormEntry)) {
			data.templateData.regFormEntry.push(captcha);
		} else {
			data.templateData.captcha = captcha;
		}
	}
	callback(null, data);
};

Plugin.onPostEdit = function(data, callback) {
	async.waterfall([
		function (next) {
			topics.getTopicField(data.post.tid, 'cid', next);
		},
		function (cid, next) {
			Plugin.checkReply({
				content: data.post.content,
				uid: data.post.uid,
				cid: cid,
				req: data.req,
			}, {type: 'post'}, function (err) {
				next(err, data);
			});
		},
	], callback);
};

Plugin.onTopicEdit = function(data, callback) {
	Plugin.checkReply({
		title: data.topic.title || '',
		uid: data.topic.uid,
		cid: data.topic.cid,
		req: data.req
	}, {type: 'topic'}, function(err) {
		callback(err, data);
	});
};

Plugin.onTopicPost = function(data, callback) {
	Plugin.checkReply(data, {type: 'topic'}, callback);
};

Plugin.onTopicReply = function(data, callback) {
	Plugin.checkReply(data, {type: 'post'}, callback);
};

Plugin.checkReply = function (data, options, callback) {
	if (typeof options === 'function') {
		callback = options;
		options = null;
	}
	options = options || {};

	// http://akismet.com/development/api/#comment-check
	if (!akismet || !data || !data.req) {
		return callback(null, data);
	}
	var userData;
	var akismetData;
	async.waterfall([
		function (next) {
			async.parallel({
				isAdmin: function(next) {
					user.isAdministrator(data.uid, next);
				},
				isModerator: function (next) {
					user.isModerator(data.uid, data.cid, next);
				},
				userData: function(next) {
					user.getUserFields(data.uid, ['username', 'reputation', 'email'], next);
				}
			}, next);
		},
		function (results, next) {
			userData = results.userData;
			if (results.isAdmin || results.isModerator) {
				return callback(null, data);
			}
			akismetData = {
				referrer: data.req.headers['referer'],
				user_ip: data.req.ip,
				user_agent: data.req.headers['user-agent'],
				permalink: nconf.get('url').replace(/\/$/, '') + data.req.path,
				comment_content: (data.title ? data.title + '\n\n' : '') + (data.content || ''),
				comment_author: userData.username,
				comment_author_email: userData.email,
				// https://github.com/akhoury/nodebb-plugin-spam-be-gone/issues/54
				comment_type: options.type === 'topic' ? 'forum-post' : 'comment'
			};
			akismet.checkSpam(akismetData, next);
		},
		function (spam, next) {
			if (!spam) {
				return callback(null, data);
			}

			if (parseInt(userData.reputation, 10) >= parseInt(pluginSettings.akismetMinReputationHam, 10)) {
				akismet.submitHam(akismetData, function (err) {
					if (err) {
						winston.error(err);
					}
				});
			}

			winston.verbose('[plugins/' + pluginData.nbbId + '] Post "' + akismetData.comment_content + '" by uid: ' + data.uid + ' username: ' + userData.username + '@' + data.req.ip + ' was flagged as spam and rejected.');
			next(new Error('Post content was flagged as spam by Akismet.com'));
		}
	], callback);
};

Plugin.checkRegister = function (data, callback) {
	async.parallel([
		function (next) {
			Plugin._honeypotCheck(data.req, data.res, data.userData, next);
		},
		function (next) {
			Plugin._recaptchaCheck(data.req, data.res, data.userData, next);
		}
	], function (err) {
		callback(err, data);
	});
};

Plugin.onPostFlagged = function (data) {
	var flagObj = data.flag;

	// Don't do anything if flag is not for a post and not for "spam" reason
	if (flagObj.type !== 'post' || flagObj.description !== 'Spam') {
		return;
	}

	if (akismet && pluginSettings.akismetFlagReporting && parseInt(flagObj.reporter.reputation, 10) >= parseInt(pluginSettings.akismetFlagReporting, 10)) {
		async.parallel({
			userData: function (next) {
				user.getUserFields(flagObj.target.uid, ['username', 'email'], next);
			},
			permalink: function (next) {
				topics.getTopicField(flagObj.target.tid, 'slug', next);
			},
			ip: function (next) {
				db.getSortedSetRevRange('uid:' + flagObj.target.uid + ':ip', 0, 1, next);
			}
		}, function (err, data) {
			// todo: we don't have access to the req here :/
			var submitted = {
				user_ip: data.ip ? data.ip[0] : '',
				permalink: nconf.get('url').replace(/\/$/, '') + '/topic/' + data.permalink,
				comment_author: data.userData.username,
				comment_author_email: data.userData.email,
				comment_content: flagObj.target.content,
				comment_type: 'forum-post'
			};

			akismet.submitSpam(submitted, function (err) {
				if (err) {
					winston.error('Error reporting to Akismet', err, submitted);
				}

				winston.info('Spam reported to Akismet.', submitted);
			});
		});
	}
};

Plugin._honeypotCheck = function (req, res, userData, next) {
	if (honeypot && req && req.ip) {
		honeypot.query(req.ip, function (err, results) {
			if (err) {
				winston.error(err);
				next(null, userData);
			} else {
				if (results && results.found && results.type) {
					if (results.type.spammer || results.type.suspicious) {
						var message = userData.username + ' | ' + userData.email + ' was detected as ' + (results.type.spammer ? 'spammer' : 'suspicious');

						winston.warn('[plugins/' + pluginData.nbbId + '] ' + message + ' and was denied registration.');
						next(new Error(message), userData);
					} else {
						next(null, userData);
					}
				} else {
					winston.verbose('[plugins/' + pluginData.nbbId + '] username:' + userData.username + ' ip:' + req.ip + ' was not found in Honeypot database');
					next(null, userData);
				}
			}
		});
	} else {
		next(null, userData);
	}
};

Plugin._recaptchaCheck = function (req, res, userData, next) {
	if (recaptchaArgs && req && req.ip && req.body) {

		simpleRecaptcha(
			pluginSettings.recaptchaPrivateKey,
			req.ip,
			req.body['g-recaptcha-response'],
			function (err) {
				if (err) {
					var message = err.Error || 'Captcha not verified, are you a robot?';
					winston.verbose('[plugins/' + pluginData.nbbId + '] ' + message);
					next(new Error(message), userData);
				} else {
					next(null, userData);
				}
			}
		);
	} else {
		next(null, userData);
	}
};

Plugin.admin = {
	menu: function (custom_header, callback) {
		custom_header.plugins.push({
			"route": '/plugins/' + pluginData.nbbId,
			"icon": pluginData.faIcon,
			"name": pluginData.name
		});

		callback(null, custom_header);
	}
};
<|endoftext|>


<commit_before>
import Immutable from 'immutable'
import Base64 from '../serializers/base-64'
import Debug from 'debug'
import React from 'react'
import ReactDOM from 'react-dom'
import TYPES from '../constants/types'
import Leaf from './leaf'
import Void from './void'
import scrollTo from '../utils/scroll-to'
import warning from '../utils/warning'
import IS_DEV from '../utils/is-dev'

/**
 * Debug.
 *
 * @type {Function}
 */

const debug = Debug('slate:node')

/**
 * Node.
 *
 * @type {Component}
 */

class Node extends React.Component {

  /**
   * Property types.
   *
   * @type {Object}
   */

  static propTypes = {
    editor: React.PropTypes.object.isRequired,
    node: React.PropTypes.object.isRequired,
    parent: React.PropTypes.object.isRequired,
    schema: React.PropTypes.object.isRequired,
    state: React.PropTypes.object.isRequired
  }

  /**
   * Constructor.
   *
   * @param {Object} props
   */

  constructor(props) {
    super(props)
    const { node, schema } = props
    this.state = {}
    this.state.Component = node.kind == 'text' ? null : node.getComponent(schema)
  }

  /**
   * Debug.
   *
   * @param {String} message
   * @param {Mixed} ...args
   */

  debug = (message, ...args) => {
    const { node } = this.props
    const { key, kind, type } = node
    let id = kind == 'text' ? `${key} (${kind})` : `${key} (${type})`
    debug(message, `${id}`, ...args)
  }

  /**
   * On receiving new props, update the `Component` renderer.
   *
   * @param {Object} props
   */

  componentWillReceiveProps = (props) => {
    if (props.node.kind == 'text') return
    if (props.node == this.props.node) return
    const Component = props.node.getComponent(props.schema)
    this.setState({ Component })
  }

  /**
   * Should the node update?
   *
   * @param {Object} nextProps
   * @param {Object} state
   * @return {Boolean}
   */

  shouldComponentUpdate = (nextProps) => {
    const { Component } = this.state

    // If the node is rendered with a `Component` that has enabled suppression
    // of update checking, always return true so that it can deal with update
    // checking itself.
    if (Component && Component.suppressShouldComponentUpdate) {
      return true
    }

    // If the node has changed, update.
    if (nextProps.node != this.props.node) {
      if (!IS_DEV || !Immutable.is(nextProps.node, this.props.node)) {
        return true
      } else {
        warning('Encountered different references for identical node values in "shouldComponentUpdate". Check that you are preserving references for the following node:\n', nextProps.node)
      }
    }

    const nextHasEdgeIn = nextProps.state.selection.hasEdgeIn(nextProps.node)

    // If the selection is focused and is inside the node, we need to update so
    // that the selection will be set by one of the <Leaf> components.
    if (
      nextProps.state.isFocused &&
      nextHasEdgeIn
    ) {
      return true
    }

    const hasEdgeIn = this.props.state.selection.hasEdgeIn(nextProps.node)
    // If the selection is blurred but was previously focused (or vice versa) inside the node,
    // we need to update to ensure the selection gets updated by re-rendering.
    if (
      this.props.state.isFocused != nextProps.state.isFocused &&
      (
          hasEdgeIn || nextHasEdgeIn
      )
    ) {
      return true
    }

    // For block and inline nodes, which can have custom renderers, we need to
    // include another check for whether the previous selection had an edge in
    // the node, to allow for intuitive selection-based rendering.
    if (
      this.props.node.kind != 'text' &&
      hasEdgeIn != nextHasEdgeIn
    ) {
      return true
    }

    // For text nodes, which can have custom decorations, we need to check to
    // see if the block has changed, which has caused the decorations to change.
    if (nextProps.node.kind == 'text' && nextProps.schema.hasDecorators) {
      const { node, schema, state } = nextProps

      const { document } = state
      const decorators = document.getDescendantDecorators(node.key, schema)
      const ranges = node.getRanges(decorators)

      const prevNode = this.props.node
      const prevSchema = this.props.schema
      const prevDocument = this.props.state.document
      const prevDecorators = prevDocument.getDescendantDecorators(prevNode.key, prevSchema)
      const prevRanges = prevNode.getRanges(prevDecorators)

      if (!ranges.equals(prevRanges)) {
        return true
      }
    }

    // Otherwise, don't update.
    return false
  }

  /**
   * On mount, update the scroll position.
   */

  componentDidMount = () => {
    this.updateScroll()
  }

  /**
   * After update, update the scroll position if the node's content changed.
   *
   * @param {Object} prevProps
   * @param {Object} prevState
   */

  componentDidUpdate = (prevProps, prevState) => {
    if (this.props.node != prevProps.node) this.updateScroll()
  }

  /**
   * Update the scroll position after a change as occured if this is a leaf
   * block and it has the selection's ending edge. This ensures that scrolling
   * matches native `contenteditable` behavior even for cases where the edit is
   * not applied natively, like when enter is pressed.
   */

  updateScroll = () => {
    const { node, state } = this.props
    const { selection } = state

    // If this isn't a block, or it's a wrapping block, abort.
    if (node.kind != 'block') return
    if (node.nodes.first().kind == 'block') return

    // If the selection is blurred, or this block doesn't contain it, abort.
    if (selection.isBlurred) return
    if (!selection.hasEndIn(node)) return

    const el = ReactDOM.findDOMNode(this)
    scrollTo(el)

    this.debug('updateScroll', el)
  }

  /**
   * On drag start, add a serialized representation of the node to the data.
   *
   * @param {Event} e
   */

  onDragStart = (e) => {
    const { node } = this.props
    const encoded = Base64.serializeNode(node)
    const data = e.nativeEvent.dataTransfer
    data.setData(TYPES.NODE, encoded)

    this.debug('onDragStart', e)
  }

  /**
   * Render.
   *
   * @return {Element} element
   */

  render = () => {
    this.debug('render')

    const { node } = this.props
    return node.kind == 'text'
      ? this.renderText()
      : this.renderElement()
  }

  /**
   * Render a `child` node.
   *
   * @param {Node} child
   * @return {Element} element
   */

  renderNode = (child) => {
    return (
      <Node
        key={child.key}
        node={child}
        parent={this.props.node}
        editor={this.props.editor}
        schema={this.props.schema}
        state={this.props.state}
      />
    )
  }

  /**
   * Render an element `node`.
   *
   * @return {Element} element
   */

  renderElement = () => {
    const { editor, node, parent, state } = this.props
    const { Component } = this.state
    const children = node.nodes
      .map(child => this.renderNode(child))
      .toArray()

    // Attributes that the developer must to mix into the element in their
    // custom node renderer component.
    const attributes = {
      'data-key': node.key,
      'onDragStart': this.onDragStart
    }

    // If it's a block node with inline children, add the proper `dir` attribute
    // for text direction.
    if (node.kind == 'block' && node.nodes.first().kind != 'block') {
      const direction = node.getTextDirection()
      if (direction == 'rtl') attributes.dir = 'rtl'
    }

    const element = (
      <Component
        attributes={attributes}
        key={node.key}
        editor={editor}
        parent={parent}
        node={node}
        state={state}
      >
        {children}
      </Component>
    )

    return node.isVoid
      ? <Void {...this.props}>{element}</Void>
      : element
  }

  /**
   * Render a text node.
   *
   * @return {Element} element
   */

  renderText = () => {
    const { node, schema, state } = this.props
    const { document } = state
    const decorators = document.getDescendantDecorators(node.key, schema)
    const ranges = node.getRanges(decorators)
    let offset = 0

    const leaves = ranges.map((range, i) => {
      const leaf = this.renderLeaf(ranges, range, i, offset)
      offset += range.text.length
      return leaf
    })

    return (

        {leaves}

    )
  }

  /**
   * Render a single leaf node given a `range` and `offset`.
   *
   * @param {List} ranges
   * @param {Range} range
   * @param {Number} index
   * @param {Number} offset
   * @return {Element} leaf
   */

  renderLeaf = (ranges, range, index, offset) => {
    const { node, parent, schema, state } = this.props
    const text = range.text
    const marks = range.marks

    return (
      <Leaf
        key={`${node.key}-${index}`}
        index={index}
        marks={marks}
        node={node}
        parent={parent}
        ranges={ranges}
        schema={schema}
        state={state}
        text={text}
      />
    )
  }

}

/**
 * Export.
 *
 * @type {Component}
 */

export default Node
<commit_msg>Improve rendering performance when schema has no decorators<commit_after>
import Immutable from 'immutable'
import Base64 from '../serializers/base-64'
import Debug from 'debug'
import React from 'react'
import ReactDOM from 'react-dom'
import TYPES from '../constants/types'
import Leaf from './leaf'
import Void from './void'
import scrollTo from '../utils/scroll-to'
import warning from '../utils/warning'
import IS_DEV from '../utils/is-dev'

/**
 * Debug.
 *
 * @type {Function}
 */

const debug = Debug('slate:node')

/**
 * Node.
 *
 * @type {Component}
 */

class Node extends React.Component {

  /**
   * Property types.
   *
   * @type {Object}
   */

  static propTypes = {
    editor: React.PropTypes.object.isRequired,
    node: React.PropTypes.object.isRequired,
    parent: React.PropTypes.object.isRequired,
    schema: React.PropTypes.object.isRequired,
    state: React.PropTypes.object.isRequired
  }

  /**
   * Constructor.
   *
   * @param {Object} props
   */

  constructor(props) {
    super(props)
    const { node, schema } = props
    this.state = {}
    this.state.Component = node.kind == 'text' ? null : node.getComponent(schema)
  }

  /**
   * Debug.
   *
   * @param {String} message
   * @param {Mixed} ...args
   */

  debug = (message, ...args) => {
    const { node } = this.props
    const { key, kind, type } = node
    let id = kind == 'text' ? `${key} (${kind})` : `${key} (${type})`
    debug(message, `${id}`, ...args)
  }

  /**
   * On receiving new props, update the `Component` renderer.
   *
   * @param {Object} props
   */

  componentWillReceiveProps = (props) => {
    if (props.node.kind == 'text') return
    if (props.node == this.props.node) return
    const Component = props.node.getComponent(props.schema)
    this.setState({ Component })
  }

  /**
   * Should the node update?
   *
   * @param {Object} nextProps
   * @param {Object} state
   * @return {Boolean}
   */

  shouldComponentUpdate = (nextProps) => {
    const { Component } = this.state

    // If the node is rendered with a `Component` that has enabled suppression
    // of update checking, always return true so that it can deal with update
    // checking itself.
    if (Component && Component.suppressShouldComponentUpdate) {
      return true
    }

    // If the node has changed, update.
    if (nextProps.node != this.props.node) {
      if (!IS_DEV || !Immutable.is(nextProps.node, this.props.node)) {
        return true
      } else {
        warning('Encountered different references for identical node values in "shouldComponentUpdate". Check that you are preserving references for the following node:\n', nextProps.node)
      }
    }

    const nextHasEdgeIn = nextProps.state.selection.hasEdgeIn(nextProps.node)

    // If the selection is focused and is inside the node, we need to update so
    // that the selection will be set by one of the <Leaf> components.
    if (
      nextProps.state.isFocused &&
      nextHasEdgeIn
    ) {
      return true
    }

    const hasEdgeIn = this.props.state.selection.hasEdgeIn(nextProps.node)
    // If the selection is blurred but was previously focused (or vice versa) inside the node,
    // we need to update to ensure the selection gets updated by re-rendering.
    if (
      this.props.state.isFocused != nextProps.state.isFocused &&
      (
          hasEdgeIn || nextHasEdgeIn
      )
    ) {
      return true
    }

    // For block and inline nodes, which can have custom renderers, we need to
    // include another check for whether the previous selection had an edge in
    // the node, to allow for intuitive selection-based rendering.
    if (
      this.props.node.kind != 'text' &&
      hasEdgeIn != nextHasEdgeIn
    ) {
      return true
    }

    // For text nodes, which can have custom decorations, we need to check to
    // see if the block has changed, which has caused the decorations to change.
    if (nextProps.node.kind == 'text' && nextProps.schema.hasDecorators) {
      const { node, schema, state } = nextProps

      const { document } = state
      const decorators = document.getDescendantDecorators(node.key, schema)
      const ranges = node.getRanges(decorators)

      const prevNode = this.props.node
      const prevSchema = this.props.schema
      const prevDocument = this.props.state.document
      const prevDecorators = prevDocument.getDescendantDecorators(prevNode.key, prevSchema)
      const prevRanges = prevNode.getRanges(prevDecorators)

      if (!ranges.equals(prevRanges)) {
        return true
      }
    }

    // Otherwise, don't update.
    return false
  }

  /**
   * On mount, update the scroll position.
   */

  componentDidMount = () => {
    this.updateScroll()
  }

  /**
   * After update, update the scroll position if the node's content changed.
   *
   * @param {Object} prevProps
   * @param {Object} prevState
   */

  componentDidUpdate = (prevProps, prevState) => {
    if (this.props.node != prevProps.node) this.updateScroll()
  }

  /**
   * Update the scroll position after a change as occured if this is a leaf
   * block and it has the selection's ending edge. This ensures that scrolling
   * matches native `contenteditable` behavior even for cases where the edit is
   * not applied natively, like when enter is pressed.
   */

  updateScroll = () => {
    const { node, state } = this.props
    const { selection } = state

    // If this isn't a block, or it's a wrapping block, abort.
    if (node.kind != 'block') return
    if (node.nodes.first().kind == 'block') return

    // If the selection is blurred, or this block doesn't contain it, abort.
    if (selection.isBlurred) return
    if (!selection.hasEndIn(node)) return

    const el = ReactDOM.findDOMNode(this)
    scrollTo(el)

    this.debug('updateScroll', el)
  }

  /**
   * On drag start, add a serialized representation of the node to the data.
   *
   * @param {Event} e
   */

  onDragStart = (e) => {
    const { node } = this.props
    const encoded = Base64.serializeNode(node)
    const data = e.nativeEvent.dataTransfer
    data.setData(TYPES.NODE, encoded)

    this.debug('onDragStart', e)
  }

  /**
   * Render.
   *
   * @return {Element} element
   */

  render = () => {
    this.debug('render')

    const { node } = this.props
    return node.kind == 'text'
      ? this.renderText()
      : this.renderElement()
  }

  /**
   * Render a `child` node.
   *
   * @param {Node} child
   * @return {Element} element
   */

  renderNode = (child) => {
    return (
      <Node
        key={child.key}
        node={child}
        parent={this.props.node}
        editor={this.props.editor}
        schema={this.props.schema}
        state={this.props.state}
      />
    )
  }

  /**
   * Render an element `node`.
   *
   * @return {Element} element
   */

  renderElement = () => {
    const { editor, node, parent, state } = this.props
    const { Component } = this.state
    const children = node.nodes
      .map(child => this.renderNode(child))
      .toArray()

    // Attributes that the developer must to mix into the element in their
    // custom node renderer component.
    const attributes = {
      'data-key': node.key,
      'onDragStart': this.onDragStart
    }

    // If it's a block node with inline children, add the proper `dir` attribute
    // for text direction.
    if (node.kind == 'block' && node.nodes.first().kind != 'block') {
      const direction = node.getTextDirection()
      if (direction == 'rtl') attributes.dir = 'rtl'
    }

    const element = (
      <Component
        attributes={attributes}
        key={node.key}
        editor={editor}
        parent={parent}
        node={node}
        state={state}
      >
        {children}
      </Component>
    )

    return node.isVoid
      ? <Void {...this.props}>{element}</Void>
      : element
  }

  /**
   * Render a text node.
   *
   * @return {Element} element
   */

  renderText = () => {
    const { node, schema, state } = this.props
    const { document } = state
    const decorators = schema.hasDecorators ? document.getDescendantDecorators(node.key, schema) : []
    const ranges = node.getRanges(decorators)
    let offset = 0

    const leaves = ranges.map((range, i) => {
      const leaf = this.renderLeaf(ranges, range, i, offset)
      offset += range.text.length
      return leaf
    })

    return (

        {leaves}

    )
  }

  /**
   * Render a single leaf node given a `range` and `offset`.
   *
   * @param {List} ranges
   * @param {Range} range
   * @param {Number} index
   * @param {Number} offset
   * @return {Element} leaf
   */

  renderLeaf = (ranges, range, index, offset) => {
    const { node, parent, schema, state } = this.props
    const text = range.text
    const marks = range.marks

    return (
      <Leaf
        key={`${node.key}-${index}`}
        index={index}
        marks={marks}
        node={node}
        parent={parent}
        ranges={ranges}
        schema={schema}
        state={state}
        text={text}
      />
    )
  }

}

/**
 * Export.
 *
 * @type {Component}
 */

export default Node
<|endoftext|>


<commit_before>/**
 * vtkWeb JavaScript Library.
 *
 * This module extend the vtkWeb viewport to add support for WebGL rendering.
 *
 * @class vtkWeb.viewports.vgl
 *
 *     Viewport Factory description:
 *       - Key: vgl
 *       - Stats:
 *         - webgl-fps
 *         - webgl-nb-objects
 *         - webgl-fetch-scene
 *         - webgl-fetch-object
 */
(function (GLOBAL, $) {
  var module = {},
  RENDERER_CSS = {
    "position": "absolute",
    "top"     : "0px",
    "left"    : "0px",
    "right"   : "0px",
    "bottom"  : "0px",
    "z-index" : "0"
  },
  RENDERER_CSS_2D = {
    "z-index" : "1"
  },
  RENDERER_CSS_3D = {
    "z-index" : "0"
  },
  DEFAULT_OPTIONS = {
    keepServerInSynch: false
  },
  FACTORY_KEY = 'vgl',
  FACTORY = {
    'builder': createVGLGeometryDeliveryRenderer,
    'options': DEFAULT_OPTIONS,
    'stats': {
      'webgl-fps': {
        label: 'Framerate:',
        type: 'time',
        convert: function(value) {
          if(value === 0) {
            return 0;
          }
          return Math.floor(1000 / value);
        }
      },
      'webgl-nb-objects': {
        label: 'Number of 3D objects:',
        type: 'value',
        convert: NoOp
      },
      'webgl-fetch-scene': {
        label: 'Fetch scene (ms):',
        type: 'time',
        convert: NoOp
      },
      'webgl-fetch-object': {
        label: 'Fetch object (ms):',
        type: 'time',
        convert: NoOp
      }
    }
  },
  DEFAULT_SHADERS = {},
  mvMatrixStack = [];

  // ----------------------------------------------------------------------

  function NoOp(a) {
    return a;
  }

  // ----------------------------------------------------------------------
  // 3D object handler
  // ----------------------------------------------------------------------

  function create3DObjectHandler() {
    var objectIndex = {}, displayList = {}, sceneJSON;

    // ------------------------------------------------------------------

    function getKey(object) {
      return object.id + '_' + object.md5;
    }

    return {
      fetchMissingObjects: function(fetchMethod, sceneJSON) {
        var fetch = fetchMethod, idx, part, i = 0;
        for(idx in sceneJSON.Objects) {
          var currentObject = sceneJSON.Objects[idx],
          key = getKey(currentObject);
          if(!objectIndex.hasOwnProperty(key)) {
            // Request all the pieces
            for(part = 1; part <= currentObject.parts; part++) {
              fetch(currentObject, part);
            }
          }
        }
      }
    }
  }

  // ----------------------------------------------------------------------
  // Geometry Delivery renderer - factory method
  // ----------------------------------------------------------------------

  function createVGLGeometryDeliveryRenderer(domElement) {
    var m_container = $(domElement),
    m_options = $.extend({}, DEFAULT_OPTIONS, m_container.data('config')),
    m_session = m_options.session,
    m_divContainer = GLOBAL.document.createElement('div'),
    m_canvas2D = GLOBAL.document.createElement('canvas'),
    m_canvas3D = GLOBAL.document.createElement('canvas'),
    m_ctx2d = m_canvas2D.getContext('2d'),
    gl = m_canvas3D.getContext("experimental-webgl") || m_canvas3D.getContext("webgl"),
    m_rendererAttrs = $(m_divContainer).addClass(FACTORY_KEY).css(RENDERER_CSS).append($(m_canvas2D).css(RENDERER_CSS).css(RENDERER_CSS_2D)).append($(m_canvas3D).css(RENDERER_CSS).css(RENDERER_CSS_3D)),
    m_sceneJSON = null,
    m_objectHandler = create3DObjectHandler(),
    m_vglVtkReader = ogs.vgl.vtkReader(),
    m_viewer = null,
    m_interactorStyle,
    originalMouseDown =  document.onmousedown,
    originalMouseUp = document.onmouseup,
    originalMouseMove = document.onmousemove,
    originalContextMenu = document.oncontextmenu,

    m_background = null;

    // Helper functions -------------------------------------------------
    function fetchScene() {
      m_container.trigger({
        type: 'stats',
        stat_id: 'webgl-fetch-scene',
        stat_value: 0
      });
      m_session.call("viewport.webgl.metadata", [Number(m_options.view)]).then(function(data) {
        m_sceneJSON = JSON.parse(data);
        m_vglVtkReader.setVtkScene(m_sceneJSON);
        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-fetch-scene',
          stat_value: 1
        });
        updateScene();
      });
    }

    // ------------------------------------------------------------------

    function fetchObject(sceneObject, part) {
      try {
        var viewId = Number(m_options.view),
        newObject;

        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-fetch-object',
          stat_value: 0
        });
        m_session.call("viewport.webgl.data", [viewId, sceneObject.id, part]).then(function(data) {
          try {
            m_container.trigger({
              type: 'stats',
              stat_id: 'webgl-fetch-object',
              stat_value: 1
            });

            //add object to the reader
            newObject = {
              md5: sceneObject.md5,
              part: part,
              vid: viewId,
              id: sceneObject.id,
              data: data,
              hasTransparency: sceneObject.transparency,
              layer: sceneObject.layer
            };
            if (newObject.layer === 0) {
              m_vglVtkReader.addVtkObjectData(newObject);
            }
            // Redraw the scene
            drawScene();
          } catch(error) {
            console.log(error);
          }
        });
      } catch(error) {
        console.log(error);
      }
    }

    // ------------------------------------------------------------------

    function drawScene() {
      var layer, viewer;

      try {
        if (m_sceneJSON === null || typeof m_sceneJSON === 'undefined') {
          return;
        }

        viewer = m_vglVtkReader.updateViewer(m_canvas3D);

        if (viewer === null) {
          return;
        }

        m_viewer = viewer;

        var width = m_rendererAttrs.width(),
        height = m_rendererAttrs.height(),
        nbObjects;

        // Update frame rate
        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-fps',
          stat_value: 0
        });

        // Clear 2D overlay canvas
        m_ctx2d.canvas.width = width;
        m_ctx2d.canvas.height = height;
        m_ctx2d.clearRect(0, 0, width, height);

        HTMLCanvasElement.prototype.relMouseCoords =
          m_viewer.relMouseCoords;

        document.onmousedown = m_viewer.handleMouseDown;
        document.onmouseup = m_viewer.handleMouseUp;
        document.onmousemove = m_viewer.handleMouseMove;
        document.oncontextmenu = m_viewer.handleContextMenu;

        m_interactorStyle = m_viewer.interactorStyle();
        $(m_interactorStyle).on(ogs.vgl.command.leftButtonPressEvent, m_viewer.render);
        $(m_interactorStyle).on(ogs.vgl.command.middleButtonPressEvent, m_viewer.render);
        $(m_interactorStyle).on(ogs.vgl.command.rightButtonPressEvent, m_viewer.render);

        m_viewer.render();

        // Update frame rate
        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-fps',
          stat_value: 1
        });

        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-nb-objects',
          stat_value: nbObjects
        });
      } catch(error) {
        console.log(error);
      }
    }

    // ------------------------------------------------------------------

    function pushCameraState() {
      if(m_viewer !== null) {
        var cam =  m_viewer.renderWindow().activeRenderer().camera(),
            fp_ = cam.getFocalPoint(),
            up_ = cam.getViewUp(),
            pos_ = cam.getPosition(),
            fp = [fp_[0], fp_[1], fp_[2]],
            up = [up_[0], up_[1], up_[2]],
            pos = [pos_[0], pos_[1], pos_[2]];
        session.call("viewport.camera.update", [Number(m_options.view), fp, up, pos]);
      }
    }

    // ------------------------------------------------------------------

    function updateScene() {
      try{
        if(m_sceneJSON === null || typeof(m_sceneJSON) === "undefined") {
          return;
        }

        // Fetch the object that we are missing
        m_objectHandler.fetchMissingObjects(fetchObject, m_sceneJSON);

        // Draw scene
        drawScene();
      } catch(error) {
        console.log(error);
      }
    }

    // ------------------------------------------------------------------
    // Add rendererAttrs into the DOM
    m_container.append(m_rendererAttrs);

    // ------------------------------------------------------------------
    // Add viewport listener
    m_container.bind('invalidateScene', function() {
      if(m_rendererAttrs.hasClass('active')){
        m_vglVtkReader = ogs.vgl.vtkReader();
        m_canvas3D.width = m_rendererAttrs.width();
        m_canvas3D.height = m_rendererAttrs.height();
        fetchScene();
      }
      else {
        originalMouseDown =  document.onmousedown;
        originalMouseUp = document.onmouseup;
        originalMouseMove = document.onmousemove;
        originalContextMenu = document.oncontextmenu;
      }
    }).bind('render', function(){
      if(m_rendererAttrs.hasClass('active')){
        drawScene();
      }
    }).bind('resetViewId', function(e){
      m_options.view = -1;
    }).bind('mouse', function(event){
      if(m_rendererAttrs.hasClass('active')){
        event.preventDefault();
        pushCameraState();
      }

    }).bind('active', function(){
      if(m_rendererAttrs.hasClass('active')){
        // Ready to render data
        fetchScene();
      }
    });
  }

  // ----------------------------------------------------------------------
  // Init vtkWeb module if needed
  // ----------------------------------------------------------------------
  if (GLOBAL.hasOwnProperty("vtkWeb")) {
    module = GLOBAL.vtkWeb || {};
  } else {
    GLOBAL.vtkWeb = module;
  }

  // ----------------------------------------------------------------------
  // Extend the viewport factory - ONLY IF WEBGL IS SUPPORTED
  // ----------------------------------------------------------------------
  try {
    if (GLOBAL.WebGLRenderingContext && typeof(vec3) != "undefined" && typeof(mat4) != "undefined") {
      var canvas = GLOBAL.document.createElement('canvas'),
      gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
      if(gl) {
        // WebGL is supported
        if(!module.hasOwnProperty('ViewportFactory')) {
          module['ViewportFactory'] = {};
        }
        module.ViewportFactory[FACTORY_KEY] = FACTORY;
      }
    }
  } catch(exception) {
    // nothing to do
  }

  // ----------------------------------------------------------------------
  // Local module registration
  // ----------------------------------------------------------------------
  try {
    // Tests for presence of jQuery and glMatrix, then registers this module
    if ($ !== undefined && module.ViewportFactory[FACTORY_KEY] !== undefined) {
      module.registerModule('vtkweb-viewport-vgl');
    }
  } catch(err) {
    console.error('jQuery or glMatrix is missing or browser does not support WebGL: ' + err.message);
  }

}(window, jQuery));
<commit_msg>vtkWeb: Propagate mouse events to VGL<commit_after>/**
 * vtkWeb JavaScript Library.
 *
 * This module extend the vtkWeb viewport to add support for WebGL rendering.
 *
 * @class vtkWeb.viewports.vgl
 *
 *     Viewport Factory description:
 *       - Key: vgl
 *       - Stats:
 *         - webgl-fps
 *         - webgl-nb-objects
 *         - webgl-fetch-scene
 *         - webgl-fetch-object
 */
(function (GLOBAL, $) {
  var module = {},
  RENDERER_CSS = {
    "position": "absolute",
    "top"     : "0px",
    "left"    : "0px",
    "right"   : "0px",
    "bottom"  : "0px",
    "z-index" : "0"
  },
  RENDERER_CSS_2D = {
    "z-index" : "1"
  },
  RENDERER_CSS_3D = {
    "z-index" : "0"
  },
  DEFAULT_OPTIONS = {
    keepServerInSynch: false
  },
  FACTORY_KEY = 'vgl',
  FACTORY = {
    'builder': createVGLGeometryDeliveryRenderer,
    'options': DEFAULT_OPTIONS,
    'stats': {
      'webgl-fps': {
        label: 'Framerate:',
        type: 'time',
        convert: function(value) {
          if(value === 0) {
            return 0;
          }
          return Math.floor(1000 / value);
        }
      },
      'webgl-nb-objects': {
        label: 'Number of 3D objects:',
        type: 'value',
        convert: NoOp
      },
      'webgl-fetch-scene': {
        label: 'Fetch scene (ms):',
        type: 'time',
        convert: NoOp
      },
      'webgl-fetch-object': {
        label: 'Fetch object (ms):',
        type: 'time',
        convert: NoOp
      }
    }
  },
  DEFAULT_SHADERS = {},
  mvMatrixStack = [];

  // ----------------------------------------------------------------------

  function NoOp(a) {
    return a;
  }

  // ----------------------------------------------------------------------
  // 3D object handler
  // ----------------------------------------------------------------------

  function create3DObjectHandler() {
    var objectIndex = {}, displayList = {}, sceneJSON;

    // ------------------------------------------------------------------

    function getKey(object) {
      return object.id + '_' + object.md5;
    }

    return {
      fetchMissingObjects: function(fetchMethod, sceneJSON) {
        var fetch = fetchMethod, idx, part, i = 0;
        for(idx in sceneJSON.Objects) {
          var currentObject = sceneJSON.Objects[idx],
          key = getKey(currentObject);
          if(!objectIndex.hasOwnProperty(key)) {
            // Request all the pieces
            for(part = 1; part <= currentObject.parts; part++) {
              fetch(currentObject, part);
            }
          }
        }
      }
    }
  }

  // ----------------------------------------------------------------------
  // Geometry Delivery renderer - factory method
  // ----------------------------------------------------------------------

  function createVGLGeometryDeliveryRenderer(domElement) {
    var m_container = $(domElement),
    m_options = $.extend({}, DEFAULT_OPTIONS, m_container.data('config')),
    m_session = m_options.session,
    m_divContainer = GLOBAL.document.createElement('div'),
    m_canvas2D = GLOBAL.document.createElement('canvas'),
    m_canvas3D = GLOBAL.document.createElement('canvas'),
    m_ctx2d = m_canvas2D.getContext('2d'),
    gl = m_canvas3D.getContext("experimental-webgl") || m_canvas3D.getContext("webgl"),
    m_rendererAttrs = $(m_divContainer).addClass(FACTORY_KEY).css(RENDERER_CSS).append($(m_canvas2D).css(RENDERER_CSS).css(RENDERER_CSS_2D)).append($(m_canvas3D).css(RENDERER_CSS).css(RENDERER_CSS_3D)),
    m_sceneJSON = null,
    m_objectHandler = create3DObjectHandler(),
    m_vglVtkReader = ogs.vgl.vtkReader(),
    m_viewer = null,
    m_interactorStyle,
    originalMouseDown =  document.onmousedown,
    originalMouseUp = document.onmouseup,
    originalMouseMove = document.onmousemove,
    originalContextMenu = document.oncontextmenu,

    m_background = null;

    // Helper functions -------------------------------------------------
    function fetchScene() {
      m_container.trigger({
        type: 'stats',
        stat_id: 'webgl-fetch-scene',
        stat_value: 0
      });
      m_session.call("viewport.webgl.metadata", [Number(m_options.view)]).then(function(data) {
        m_sceneJSON = JSON.parse(data);
        m_vglVtkReader.setVtkScene(m_sceneJSON);
        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-fetch-scene',
          stat_value: 1
        });
        updateScene();
      });
    }

    // ------------------------------------------------------------------

    function fetchObject(sceneObject, part) {
      try {
        var viewId = Number(m_options.view),
        newObject;

        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-fetch-object',
          stat_value: 0
        });
        m_session.call("viewport.webgl.data", [viewId, sceneObject.id, part]).then(function(data) {
          try {
            m_container.trigger({
              type: 'stats',
              stat_id: 'webgl-fetch-object',
              stat_value: 1
            });

            //add object to the reader
            newObject = {
              md5: sceneObject.md5,
              part: part,
              vid: viewId,
              id: sceneObject.id,
              data: data,
              hasTransparency: sceneObject.transparency,
              layer: sceneObject.layer
            };
            if (newObject.layer === 0) {
              m_vglVtkReader.addVtkObjectData(newObject);
            }
            // Redraw the scene
            drawScene();
          } catch(error) {
            console.log(error);
          }
        });
      } catch(error) {
        console.log(error);
      }
    }

    // ------------------------------------------------------------------

    function drawScene() {
      var layer, viewer;

      try {
        if (m_sceneJSON === null || typeof m_sceneJSON === 'undefined') {
          return;
        }

        viewer = m_vglVtkReader.updateViewer(m_canvas3D);

        if (viewer === null) {
          return;
        }

        m_viewer = viewer;

        var width = m_rendererAttrs.width(),
        height = m_rendererAttrs.height(),
        nbObjects;

        // Update frame rate
        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-fps',
          stat_value: 0
        });

        // Clear 2D overlay canvas
        m_ctx2d.canvas.width = width;
        m_ctx2d.canvas.height = height;
        m_ctx2d.clearRect(0, 0, width, height);

        HTMLCanvasElement.prototype.relMouseCoords =
          m_viewer.relMouseCoords;

        m_container.on('mouse', function(event) {
          if (m_viewer) {
            if (event.action === 'move') {
              m_viewer.handleMouseMove(event.originalEvent);
            }
            else if (event.action === 'up') {
              m_viewer.handleMouseUp(event.originalEvent);
            }
            else if (event.action === 'down') {
              m_viewer.handleMouseDown(event.originalEvent);
            }
          }
        });

        m_interactorStyle = m_viewer.interactorStyle();
        $(m_interactorStyle).on(ogs.vgl.command.leftButtonPressEvent, m_viewer.render);
        $(m_interactorStyle).on(ogs.vgl.command.middleButtonPressEvent, m_viewer.render);
        $(m_interactorStyle).on(ogs.vgl.command.rightButtonPressEvent, m_viewer.render);

        m_viewer.render();

        // Update frame rate
        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-fps',
          stat_value: 1
        });

        m_container.trigger({
          type: 'stats',
          stat_id: 'webgl-nb-objects',
          stat_value: nbObjects
        });
      } catch(error) {
        console.log(error);
      }
    }

    // ------------------------------------------------------------------

    function pushCameraState() {
      if(m_viewer !== null) {
        var cam =  m_viewer.renderWindow().activeRenderer().camera(),
            fp_ = cam.getFocalPoint(),
            up_ = cam.getViewUp(),
            pos_ = cam.getPosition(),
            fp = [fp_[0], fp_[1], fp_[2]],
            up = [up_[0], up_[1], up_[2]],
            pos = [pos_[0], pos_[1], pos_[2]];
        session.call("viewport.camera.update", [Number(m_options.view), fp, up, pos]);
      }
    }

    // ------------------------------------------------------------------

    function updateScene() {
      try{
        if(m_sceneJSON === null || typeof(m_sceneJSON) === "undefined") {
          return;
        }

        // Fetch the object that we are missing
        m_objectHandler.fetchMissingObjects(fetchObject, m_sceneJSON);

        // Draw scene
        drawScene();
      } catch(error) {
        console.log(error);
      }
    }

    // ------------------------------------------------------------------
    // Add rendererAttrs into the DOM
    m_container.append(m_rendererAttrs);

    // ------------------------------------------------------------------
    // Add viewport listener
    m_container.bind('invalidateScene', function() {
      if(m_rendererAttrs.hasClass('active')){
        m_vglVtkReader = ogs.vgl.vtkReader();
        m_canvas3D.width = m_rendererAttrs.width();
        m_canvas3D.height = m_rendererAttrs.height();
        fetchScene();
      }
      else {
        originalMouseDown =  document.onmousedown;
        originalMouseUp = document.onmouseup;
        originalMouseMove = document.onmousemove;
        originalContextMenu = document.oncontextmenu;
      }
    }).bind('render', function(){
      if(m_rendererAttrs.hasClass('active')){
        drawScene();
      }
    }).bind('resetViewId', function(e){
      m_options.view = -1;
    }).bind('mouse', function(event){
      if(m_rendererAttrs.hasClass('active')){
        event.preventDefault();
        pushCameraState();
      }

    }).bind('active', function(){
      if(m_rendererAttrs.hasClass('active')){
        // Ready to render data
        fetchScene();
      }
    });
  }

  // ----------------------------------------------------------------------
  // Init vtkWeb module if needed
  // ----------------------------------------------------------------------
  if (GLOBAL.hasOwnProperty("vtkWeb")) {
    module = GLOBAL.vtkWeb || {};
  } else {
    GLOBAL.vtkWeb = module;
  }

  // ----------------------------------------------------------------------
  // Extend the viewport factory - ONLY IF WEBGL IS SUPPORTED
  // ----------------------------------------------------------------------
  try {
    if (GLOBAL.WebGLRenderingContext && typeof(vec3) != "undefined" && typeof(mat4) != "undefined") {
      var canvas = GLOBAL.document.createElement('canvas'),
      gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
      if(gl) {
        // WebGL is supported
        if(!module.hasOwnProperty('ViewportFactory')) {
          module['ViewportFactory'] = {};
        }
        module.ViewportFactory[FACTORY_KEY] = FACTORY;
      }
    }
  } catch(exception) {
    // nothing to do
  }

  // ----------------------------------------------------------------------
  // Local module registration
  // ----------------------------------------------------------------------
  try {
    // Tests for presence of jQuery and glMatrix, then registers this module
    if ($ !== undefined && module.ViewportFactory[FACTORY_KEY] !== undefined) {
      module.registerModule('vtkweb-viewport-vgl');
    }
  } catch(err) {
    console.error('jQuery or glMatrix is missing or browser does not support WebGL: ' + err.message);
  }

}(window, jQuery));
<|endoftext|>


<commit_before>'use strict';

const url = require('url');

const nconf = require.main.require('nconf');
const winston = require.main.require('winston');
const validator = require('validator');

const plugins = require.main.require('./src/plugins');
const topics = require.main.require('./src/topics');
const categories = require.main.require('./src/categories');
const posts = require.main.require('./src/posts');
const user = require.main.require('./src/user');
const meta = require.main.require('./src/meta');
const privileges = require.main.require('./src/privileges');
const translator = require.main.require('./src/translator');
const helpers = require.main.require('./src/controllers/helpers');
const SocketPlugins = require.main.require('./src/socket.io/plugins');
const socketMethods = require('./websockets');

const plugin = module.exports;

plugin.socketMethods = socketMethods;

plugin.init = async function (data) {
	const controllers = require('./controllers');
	SocketPlugins.composer = socketMethods;

	data.router.get('/admin/plugins/composer-default', data.middleware.admin.buildHeader, controllers.renderAdminPage);
	data.router.get('/api/admin/plugins/composer-default', controllers.renderAdminPage);
};

plugin.appendConfig = async function (config) {
	config['composer-default'] = await meta.settings.get('composer-default');
	return config;
};

plugin.addAdminNavigation = async function (header) {
	header.plugins.push({
		route: '/plugins/composer-default',
		icon: 'fa-edit',
		name: 'Composer (Default)',
	});
	return header;
};

plugin.addPrefetchTags = async function (hookData) {
	const prefetch = [
		'/assets/src/modules/composer.js', '/assets/src/modules/composer/uploads.js', '/assets/src/modules/composer/drafts.js',
		'/assets/src/modules/composer/tags.js', '/assets/src/modules/composer/categoryList.js', '/assets/src/modules/composer/resize.js',
		'/assets/src/modules/composer/autocomplete.js', '/assets/templates/composer.tpl',
		`/assets/language/${meta.config.defaultLang || 'en-GB'}/topic.json`,
		`/assets/language/${meta.config.defaultLang || 'en-GB'}/modules.json`,
		`/assets/language/${meta.config.defaultLang || 'en-GB'}/tags.json`,
	];

	hookData.links = hookData.links.concat(prefetch.map(path => ({
		rel: 'prefetch',
		href: `${nconf.get('relative_path') + path}?${meta.config['cache-buster']}`,
	})));

	return hookData;
};

plugin.getFormattingOptions = async function () {
	const defaultVisibility = {
		mobile: true,
		desktop: true,

		// op or reply
		main: true,
		reply: true,
	};
	let payload = {
		defaultVisibility,
		options: [
			{
				name: 'tags',
				title: '[[global:tags.tags]]',
				className: 'fa fa-tags',
				visibility: {
					...defaultVisibility,
					desktop: false,
				},
			},
			{
				name: 'zen',
				title: '[[modules:composer.zen_mode]]',
				className: 'fa fa-arrows-alt',
				visibility: defaultVisibility,
			},
		],
	};
	if (parseInt(meta.config.allowTopicsThumbnail, 10) === 1) {
		payload.options.push({
			name: 'thumbs',
			title: '[[topic:composer.thumb_title]]',
			className: 'fa fa-address-card-o',
			visibility: {
				...defaultVisibility,
				reply: false,
			},
		});
	}

	payload = await plugins.hooks.fire('filter:composer.formatting', payload);

	payload.options.forEach((option) => {
		option.visibility = {
			...defaultVisibility,
			...option.visibility || {},
		};
	});

	return payload ? payload.options : null;
};

plugin.filterComposerBuild = async function (hookData) {
	const { req } = hookData;
	const { res } = hookData;

	if (req.query.p) {
		if (!res.locals.isAPI) {
			let a;
			try {
				a = url.parse(req.query.p, true, true);
			} catch (e) {
				return helpers.redirect(res, '/');
			}
			return helpers.redirect(res, `/${(a.path || '').replace(/^\/*/, '')}`);
		}
		res.render('', {});
		return;
	} else if (!req.query.pid && !req.query.tid && !req.query.cid) {
		return helpers.redirect(res, '/');
	}
	const [
		isMainPost,
		postData,
		topicData,
		categoryData,
		isAdmin,
		isMod,
		formatting,
		tagWhitelist,
		globalPrivileges,
		canTagTopics,
		canScheduleTopics,
	] = await Promise.all([
		posts.isMain(req.query.pid),
		getPostData(req),
		getTopicData(req),
		categories.getCategoryFields(req.query.cid, [
			'name', 'icon', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'minTags', 'maxTags',
		]),
		user.isAdministrator(req.uid),
		isModerator(req),
		plugin.getFormattingOptions(),
		getTagWhitelist(req.query, req.uid),
		privileges.global.get(req.uid),
		canTag(req),
		canSchedule(req),
	]);

	const isEditing = !!req.query.pid;
	const isGuestPost = postData && parseInt(postData.uid, 10) === 0;
	const save_id = generateSaveId(req);
	const discardRoute = generateDiscardRoute(req, topicData);
	const body = await generateBody(req, postData);

	let action = 'topics.post';
	let isMain = isMainPost;
	if (req.query.tid) {
		action = 'posts.reply';
	} else if (req.query.pid) {
		action = 'posts.edit';
	} else {
		isMain = true;
	}
	globalPrivileges['topics:tag'] = canTagTopics;
	const cid = parseInt(req.query.cid, 10);
	const topicTitle = topicData && topicData.title ? topicData.title.replace(/%/g, '%').replace(/,/g, ',') : req.query.title;
	return {
		req: req,
		res: res,
		templateData: {
			disabled: !req.query.pid && !req.query.tid && !req.query.cid,
			pid: parseInt(req.query.pid, 10),
			tid: parseInt(req.query.tid, 10),
			cid: cid || (topicData ? topicData.cid : null),
			action: action,
			toPid: parseInt(req.query.toPid, 10),
			discardRoute: discardRoute,

			resizable: false,
			allowTopicsThumbnail: parseInt(meta.config.allowTopicsThumbnail, 10) === 1 && isMain,

			// can't use title property as that is used for page title
			topicTitle: topicTitle,
			titleLength: topicTitle.length,
			topic: topicData,
			thumb: topicData ? topicData.thumb : '',
			body: body,

			isMain: isMain,
			isTopicOrMain: !!req.query.cid || isMain,
			maximumTitleLength: meta.config.maximumTitleLength,
			maximumPostLength: meta.config.maximumPostLength,
			minimumTagLength: meta.config.minimumTagLength || 3,
			maximumTagLength: meta.config.maximumTagLength || 15,
			tagWhitelist: tagWhitelist,
			selectedCategory: cid ? categoryData : null,
			minTags: categoryData.minTags,
			maxTags: categoryData.maxTags,

			isTopic: !!req.query.cid,
			isEditing: isEditing,
			canSchedule: canScheduleTopics,
			showHandleInput: meta.config.allowGuestHandles === 1 &&
				(req.uid === 0 || (isEditing && isGuestPost && (isAdmin || isMod))),
			handle: postData ? postData.handle || '' : undefined,
			formatting: formatting,
			isAdminOrMod: isAdmin || isMod,
			save_id: save_id,
			privileges: globalPrivileges,
		},
	};
};

function generateDiscardRoute(req, topicData) {
	if (req.query.cid) {
		return `${nconf.get('relative_path')}/category/${validator.escape(String(req.query.cid))}`;
	} else if ((req.query.tid || req.query.pid)) {
		if (topicData) {
			return `${nconf.get('relative_path')}/topic/${topicData.slug}`;
		}
		return `${nconf.get('relative_path')}/`;
	}
}

async function generateBody(req, postData) {
	// Quoted reply
	if (req.query.toPid && parseInt(req.query.quoted, 10) === 1 && postData) {
		const username = await user.getUserField(postData.uid, 'username');
		const translated = await translator.translate(`[[modules:composer.user_said, ${username}]]`);
		return `${translated}\n` +
			`> ${postData ? `${postData.content.replace(/\n/g, '\n> ')}\n\n` : ''}`;
	} else if (req.query.body) {
		return validator.escape(String(req.query.body));
	}
	return postData ? postData.content : '';
}

function generateSaveId(req) {
	if (req.query.cid) {
		return ['composer', req.uid, 'cid', req.query.cid].join(':');
	} else if (req.query.tid) {
		return ['composer', req.uid, 'tid', req.query.tid].join(':');
	} else if (req.query.pid) {
		return ['composer', req.uid, 'pid', req.query.pid].join(':');
	}
}

async function getPostData(req) {
	if (!req.query.pid && !req.query.toPid) {
		return null;
	}

	return await posts.getPostData(req.query.pid || req.query.toPid);
}

async function getTopicData(req) {
	if (req.query.tid) {
		return await topics.getTopicData(req.query.tid);
	} else if (req.query.pid) {
		return await topics.getTopicDataByPid(req.query.pid);
	}
	return null;
}

async function isModerator(req) {
	if (!req.loggedIn) {
		return false;
	}
	const cid = cidFromQuery(req.query);
	return await user.isModerator(req.uid, cid);
}

async function canTag(req) {
	if (parseInt(req.query.cid, 10)) {
		return await privileges.categories.can('topics:tag', req.query.cid, req.uid);
	}
	return true;
}

async function canSchedule(req) {
	if (parseInt(req.query.cid, 10)) {
		return await privileges.categories.can('topics:schedule', req.query.cid, req.uid);
	}
	return false;
}

async function getTagWhitelist(query, uid) {
	const cid = await cidFromQuery(query);
	const [tagWhitelist, isAdminOrMod] = await Promise.all([
		categories.getTagWhitelist([cid]),
		privileges.categories.isAdminOrMod(cid, uid),
	]);
	return categories.filterTagWhitelist(tagWhitelist[0], isAdminOrMod);
}

async function cidFromQuery(query) {
	if (query.cid) {
		return query.cid;
	} else if (query.tid) {
		return await topics.getTopicField(query.tid, 'cid');
	} else if (query.pid) {
		return await posts.getCidByPid(query.pid);
	}
	return null;
}
<commit_msg>fix: also listen for req.query.content<commit_after>'use strict';

const url = require('url');

const nconf = require.main.require('nconf');
const winston = require.main.require('winston');
const validator = require('validator');

const plugins = require.main.require('./src/plugins');
const topics = require.main.require('./src/topics');
const categories = require.main.require('./src/categories');
const posts = require.main.require('./src/posts');
const user = require.main.require('./src/user');
const meta = require.main.require('./src/meta');
const privileges = require.main.require('./src/privileges');
const translator = require.main.require('./src/translator');
const helpers = require.main.require('./src/controllers/helpers');
const SocketPlugins = require.main.require('./src/socket.io/plugins');
const socketMethods = require('./websockets');

const plugin = module.exports;

plugin.socketMethods = socketMethods;

plugin.init = async function (data) {
	const controllers = require('./controllers');
	SocketPlugins.composer = socketMethods;

	data.router.get('/admin/plugins/composer-default', data.middleware.admin.buildHeader, controllers.renderAdminPage);
	data.router.get('/api/admin/plugins/composer-default', controllers.renderAdminPage);
};

plugin.appendConfig = async function (config) {
	config['composer-default'] = await meta.settings.get('composer-default');
	return config;
};

plugin.addAdminNavigation = async function (header) {
	header.plugins.push({
		route: '/plugins/composer-default',
		icon: 'fa-edit',
		name: 'Composer (Default)',
	});
	return header;
};

plugin.addPrefetchTags = async function (hookData) {
	const prefetch = [
		'/assets/src/modules/composer.js', '/assets/src/modules/composer/uploads.js', '/assets/src/modules/composer/drafts.js',
		'/assets/src/modules/composer/tags.js', '/assets/src/modules/composer/categoryList.js', '/assets/src/modules/composer/resize.js',
		'/assets/src/modules/composer/autocomplete.js', '/assets/templates/composer.tpl',
		`/assets/language/${meta.config.defaultLang || 'en-GB'}/topic.json`,
		`/assets/language/${meta.config.defaultLang || 'en-GB'}/modules.json`,
		`/assets/language/${meta.config.defaultLang || 'en-GB'}/tags.json`,
	];

	hookData.links = hookData.links.concat(prefetch.map(path => ({
		rel: 'prefetch',
		href: `${nconf.get('relative_path') + path}?${meta.config['cache-buster']}`,
	})));

	return hookData;
};

plugin.getFormattingOptions = async function () {
	const defaultVisibility = {
		mobile: true,
		desktop: true,

		// op or reply
		main: true,
		reply: true,
	};
	let payload = {
		defaultVisibility,
		options: [
			{
				name: 'tags',
				title: '[[global:tags.tags]]',
				className: 'fa fa-tags',
				visibility: {
					...defaultVisibility,
					desktop: false,
				},
			},
			{
				name: 'zen',
				title: '[[modules:composer.zen_mode]]',
				className: 'fa fa-arrows-alt',
				visibility: defaultVisibility,
			},
		],
	};
	if (parseInt(meta.config.allowTopicsThumbnail, 10) === 1) {
		payload.options.push({
			name: 'thumbs',
			title: '[[topic:composer.thumb_title]]',
			className: 'fa fa-address-card-o',
			visibility: {
				...defaultVisibility,
				reply: false,
			},
		});
	}

	payload = await plugins.hooks.fire('filter:composer.formatting', payload);

	payload.options.forEach((option) => {
		option.visibility = {
			...defaultVisibility,
			...option.visibility || {},
		};
	});

	return payload ? payload.options : null;
};

plugin.filterComposerBuild = async function (hookData) {
	const { req } = hookData;
	const { res } = hookData;

	if (req.query.p) {
		if (!res.locals.isAPI) {
			let a;
			try {
				a = url.parse(req.query.p, true, true);
			} catch (e) {
				return helpers.redirect(res, '/');
			}
			return helpers.redirect(res, `/${(a.path || '').replace(/^\/*/, '')}`);
		}
		res.render('', {});
		return;
	} else if (!req.query.pid && !req.query.tid && !req.query.cid) {
		return helpers.redirect(res, '/');
	}
	const [
		isMainPost,
		postData,
		topicData,
		categoryData,
		isAdmin,
		isMod,
		formatting,
		tagWhitelist,
		globalPrivileges,
		canTagTopics,
		canScheduleTopics,
	] = await Promise.all([
		posts.isMain(req.query.pid),
		getPostData(req),
		getTopicData(req),
		categories.getCategoryFields(req.query.cid, [
			'name', 'icon', 'color', 'bgColor', 'backgroundImage', 'imageClass', 'minTags', 'maxTags',
		]),
		user.isAdministrator(req.uid),
		isModerator(req),
		plugin.getFormattingOptions(),
		getTagWhitelist(req.query, req.uid),
		privileges.global.get(req.uid),
		canTag(req),
		canSchedule(req),
	]);

	const isEditing = !!req.query.pid;
	const isGuestPost = postData && parseInt(postData.uid, 10) === 0;
	const save_id = generateSaveId(req);
	const discardRoute = generateDiscardRoute(req, topicData);
	const body = await generateBody(req, postData);

	let action = 'topics.post';
	let isMain = isMainPost;
	if (req.query.tid) {
		action = 'posts.reply';
	} else if (req.query.pid) {
		action = 'posts.edit';
	} else {
		isMain = true;
	}
	globalPrivileges['topics:tag'] = canTagTopics;
	const cid = parseInt(req.query.cid, 10);
	const topicTitle = topicData && topicData.title ? topicData.title.replace(/%/g, '%').replace(/,/g, ',') : req.query.title;
	return {
		req: req,
		res: res,
		templateData: {
			disabled: !req.query.pid && !req.query.tid && !req.query.cid,
			pid: parseInt(req.query.pid, 10),
			tid: parseInt(req.query.tid, 10),
			cid: cid || (topicData ? topicData.cid : null),
			action: action,
			toPid: parseInt(req.query.toPid, 10),
			discardRoute: discardRoute,

			resizable: false,
			allowTopicsThumbnail: parseInt(meta.config.allowTopicsThumbnail, 10) === 1 && isMain,

			// can't use title property as that is used for page title
			topicTitle: topicTitle,
			titleLength: topicTitle.length,
			topic: topicData,
			thumb: topicData ? topicData.thumb : '',
			body: body,

			isMain: isMain,
			isTopicOrMain: !!req.query.cid || isMain,
			maximumTitleLength: meta.config.maximumTitleLength,
			maximumPostLength: meta.config.maximumPostLength,
			minimumTagLength: meta.config.minimumTagLength || 3,
			maximumTagLength: meta.config.maximumTagLength || 15,
			tagWhitelist: tagWhitelist,
			selectedCategory: cid ? categoryData : null,
			minTags: categoryData.minTags,
			maxTags: categoryData.maxTags,

			isTopic: !!req.query.cid,
			isEditing: isEditing,
			canSchedule: canScheduleTopics,
			showHandleInput: meta.config.allowGuestHandles === 1 &&
				(req.uid === 0 || (isEditing && isGuestPost && (isAdmin || isMod))),
			handle: postData ? postData.handle || '' : undefined,
			formatting: formatting,
			isAdminOrMod: isAdmin || isMod,
			save_id: save_id,
			privileges: globalPrivileges,
		},
	};
};

function generateDiscardRoute(req, topicData) {
	if (req.query.cid) {
		return `${nconf.get('relative_path')}/category/${validator.escape(String(req.query.cid))}`;
	} else if ((req.query.tid || req.query.pid)) {
		if (topicData) {
			return `${nconf.get('relative_path')}/topic/${topicData.slug}`;
		}
		return `${nconf.get('relative_path')}/`;
	}
}

async function generateBody(req, postData) {
	// Quoted reply
	if (req.query.toPid && parseInt(req.query.quoted, 10) === 1 && postData) {
		const username = await user.getUserField(postData.uid, 'username');
		const translated = await translator.translate(`[[modules:composer.user_said, ${username}]]`);
		return `${translated}\n` +
			`> ${postData ? `${postData.content.replace(/\n/g, '\n> ')}\n\n` : ''}`;
	} else if (req.query.body || req.query.content) {
		return validator.escape(String(req.query.body || req.query.content));
	}
	return postData ? postData.content : '';
}

function generateSaveId(req) {
	if (req.query.cid) {
		return ['composer', req.uid, 'cid', req.query.cid].join(':');
	} else if (req.query.tid) {
		return ['composer', req.uid, 'tid', req.query.tid].join(':');
	} else if (req.query.pid) {
		return ['composer', req.uid, 'pid', req.query.pid].join(':');
	}
}

async function getPostData(req) {
	if (!req.query.pid && !req.query.toPid) {
		return null;
	}

	return await posts.getPostData(req.query.pid || req.query.toPid);
}

async function getTopicData(req) {
	if (req.query.tid) {
		return await topics.getTopicData(req.query.tid);
	} else if (req.query.pid) {
		return await topics.getTopicDataByPid(req.query.pid);
	}
	return null;
}

async function isModerator(req) {
	if (!req.loggedIn) {
		return false;
	}
	const cid = cidFromQuery(req.query);
	return await user.isModerator(req.uid, cid);
}

async function canTag(req) {
	if (parseInt(req.query.cid, 10)) {
		return await privileges.categories.can('topics:tag', req.query.cid, req.uid);
	}
	return true;
}

async function canSchedule(req) {
	if (parseInt(req.query.cid, 10)) {
		return await privileges.categories.can('topics:schedule', req.query.cid, req.uid);
	}
	return false;
}

async function getTagWhitelist(query, uid) {
	const cid = await cidFromQuery(query);
	const [tagWhitelist, isAdminOrMod] = await Promise.all([
		categories.getTagWhitelist([cid]),
		privileges.categories.isAdminOrMod(cid, uid),
	]);
	return categories.filterTagWhitelist(tagWhitelist[0], isAdminOrMod);
}

async function cidFromQuery(query) {
	if (query.cid) {
		return query.cid;
	} else if (query.tid) {
		return await topics.getTopicField(query.tid, 'cid');
	} else if (query.pid) {
		return await posts.getCidByPid(query.pid);
	}
	return null;
}
<|endoftext|>


<commit_before><commit_msg>fix for isSpinning method<commit_after><|endoftext|>


<commit_before>
export default function getScreenDimension() {

  const body = document.body;
  const html = document.documentElement;

  return {
    viewportWidth: Math.max(html.clientWidth, window.innerWidth || 0),
    viewportHeight: Math.max(html.clientHeight, window.innerHeight || 0),
    documentWidth: html.scrollWidth,
    documentHeight: Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
    pixelRatio: window.devicePixelRatio,
    orientation: typeof window.orientation == 'undefined' ? 0 : Math.abs(window.orientation),
    screenWidth: window.screen.width,
    screenHeight: window.screen.height,
    innerWidth: window.innerHeight,
    innerHeight: window.innerWidth,
  };

}
<commit_msg>provide a fallback value for window.devicePixelRatio when browser's doesn't support it (#13)<commit_after>
export default function getScreenDimension() {

  const body = document.body;
  const html = document.documentElement;

  return {
    viewportWidth: Math.max(html.clientWidth, window.innerWidth || 0),
    viewportHeight: Math.max(html.clientHeight, window.innerHeight || 0),
    documentWidth: html.scrollWidth,
    documentHeight: Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
    pixelRatio: typeof window.devicePixelRatio === 'undefined' ? 1 : window.devicePixelRatio,
    orientation: typeof window.orientation === 'undefined' ? 0 : Math.abs(window.orientation),
    screenWidth: window.screen.width,
    screenHeight: window.screen.height,
    innerWidth: window.innerHeight,
    innerHeight: window.innerWidth,
  };

}
<|endoftext|>