<commit_before>'use strict';

var forEachRight        = require('es5-ext/array/#/for-each-right')
  , ensureArray         = require('es5-ext/array/valid-array')
  , ensureCallable      = require('es5-ext/object/valid-callable')
  , isDocumentFragment  = require('dom-ext/document-fragment/is-document-fragment')
  , isElement           = require('dom-ext/element/is-element')
  , isText              = require('dom-ext/text/is-text')
  , normalize           = require('dom-ext/document/#/normalize')
  , validDocument       = require('dom-ext/html-document/valid-html-document')
  , htmlToDom           = require('dom-ext/html-document/#/html-to-dom')
  , isParentNode        = require('dom-ext/parent-node/is')
  , reEvaluateScripts   = require('html-dom-ext/node#/re-evaluate-scripts')

  , insertsRe = /\x01(\d+)\x01/, justInsertRe = /^\x01(\d+)\x01$/

  , isArray = Array.isArray, forEach = Array.prototype.forEach;

var resolveInserts = function (str, inserts, document) {
	var result, index = str.search(insertsRe), match, normalized;
	if (index === -1) return null;
	result = document.createDocumentFragment();
	while ((index = str.search(insertsRe)) !== -1) {
		if (index > 0) result.appendChild(document.createTextNode(str.slice(0, index)));
		str = str.slice(index);
		match = str.match(insertsRe);
		if ((normalized = normalize.call(document, inserts[match[1]]))) {
			if (isArray(normalized)) normalized.forEach(result.appendChild, result);
			else result.appendChild(normalized);
		}
		str = str.slice(match[0].length);
	}
	if (str.length) result.appendChild(document.createTextNode(str));
	return result;
};

var fixInserts = function (dom, inserts, document) {
	if (isText(dom)) return resolveInserts(dom.data, inserts, document) || dom;
	if (isElement(dom)) {
		forEach.call(dom.attributes, function (attr) {
			var match = attr.value.match(justInsertRe), insert;
			if (match) {
				insert = inserts[match[1]];
				if (typeof insert.toDOMAttr === 'function') {
					insert.toDOMAttr(dom, attr.name);
					return;
				}
				attr.value = String(insert);
				return;
			}
			attr.value = attr.value.replace(insertsRe, function (ignore, index) {
				return String(inserts[index]);
			});
		});
	} else if (!isDocumentFragment(dom)) {
		return dom;
	}
	forEachRight.call(dom.childNodes, function (child, index) {
		var nu = fixInserts(child, inserts, document);
		if (nu === child) return;
		dom.insertBefore(nu, child);
		dom.removeChild(child);
	});
	return dom;
};

module.exports = function (document, tokens/*, options*/) {
	var insertsMap, insertIndex = -1, dom, html, options = Object(arguments[2])
	  , normalizeHtml, normalizeDom;
	if (options.normalizeHtml != null) normalizeHtml = ensureCallable(options.normalizeHtml);
	if (options.normalizeDom != null) normalizeDom = ensureCallable(options.normalizeDom);
	validDocument(document);
	ensureArray(tokens);
	insertsMap = [];
	html = tokens.reduce(function (a, b, index) {
		if (!(index % 2)) return a + b;
		insertsMap[++insertIndex] = b;
		return a + '\x01' + String(insertIndex) + '\x01';
	}, '');
	if (normalizeHtml) html = normalizeHtml(html);
	dom = htmlToDom.call(document, html);
	if (isParentNode(dom)) reEvaluateScripts.call(dom, options.reEvaluateScripts);
	if (normalizeDom) dom = normalizeDom(dom);
	if (insertsMap) dom = fixInserts(dom, insertsMap, document);
	return dom;
};
<commit_msg>Re-evaluate scripts after having full picture<commit_after>'use strict';

var forEachRight        = require('es5-ext/array/#/for-each-right')
  , ensureArray         = require('es5-ext/array/valid-array')
  , ensureCallable      = require('es5-ext/object/valid-callable')
  , isDocumentFragment  = require('dom-ext/document-fragment/is-document-fragment')
  , isElement           = require('dom-ext/element/is-element')
  , isText              = require('dom-ext/text/is-text')
  , normalize           = require('dom-ext/document/#/normalize')
  , validDocument       = require('dom-ext/html-document/valid-html-document')
  , htmlToDom           = require('dom-ext/html-document/#/html-to-dom')
  , isParentNode        = require('dom-ext/parent-node/is')
  , reEvaluateScripts   = require('html-dom-ext/node#/re-evaluate-scripts')

  , insertsRe = /\x01(\d+)\x01/, justInsertRe = /^\x01(\d+)\x01$/

  , isArray = Array.isArray, forEach = Array.prototype.forEach;

var resolveInserts = function (str, inserts, document) {
	var result, index = str.search(insertsRe), match, normalized;
	if (index === -1) return null;
	result = document.createDocumentFragment();
	while ((index = str.search(insertsRe)) !== -1) {
		if (index > 0) result.appendChild(document.createTextNode(str.slice(0, index)));
		str = str.slice(index);
		match = str.match(insertsRe);
		if ((normalized = normalize.call(document, inserts[match[1]]))) {
			if (isArray(normalized)) normalized.forEach(result.appendChild, result);
			else result.appendChild(normalized);
		}
		str = str.slice(match[0].length);
	}
	if (str.length) result.appendChild(document.createTextNode(str));
	return result;
};

var fixInserts = function (dom, inserts, document) {
	if (isText(dom)) return resolveInserts(dom.data, inserts, document) || dom;
	if (isElement(dom)) {
		forEach.call(dom.attributes, function (attr) {
			var match = attr.value.match(justInsertRe), insert;
			if (match) {
				insert = inserts[match[1]];
				if (typeof insert.toDOMAttr === 'function') {
					insert.toDOMAttr(dom, attr.name);
					return;
				}
				attr.value = String(insert);
				return;
			}
			attr.value = attr.value.replace(insertsRe, function (ignore, index) {
				return String(inserts[index]);
			});
		});
	} else if (!isDocumentFragment(dom)) {
		return dom;
	}
	forEachRight.call(dom.childNodes, function (child, index) {
		var nu = fixInserts(child, inserts, document);
		if (nu === child) return;
		dom.insertBefore(nu, child);
		dom.removeChild(child);
	});
	return dom;
};

module.exports = function (document, tokens/*, options*/) {
	var insertsMap, insertIndex = -1, dom, html, options = Object(arguments[2])
	  , normalizeHtml, normalizeDom;
	if (options.normalizeHtml != null) normalizeHtml = ensureCallable(options.normalizeHtml);
	if (options.normalizeDom != null) normalizeDom = ensureCallable(options.normalizeDom);
	validDocument(document);
	ensureArray(tokens);
	insertsMap = [];
	html = tokens.reduce(function (a, b, index) {
		if (!(index % 2)) return a + b;
		insertsMap[++insertIndex] = b;
		return a + '\x01' + String(insertIndex) + '\x01';
	}, '');
	if (normalizeHtml) html = normalizeHtml(html);
	dom = htmlToDom.call(document, html);
	if (normalizeDom) dom = normalizeDom(dom);
	if (insertsMap) dom = fixInserts(dom, insertsMap, document);
	if (isParentNode(dom)) reEvaluateScripts.call(dom, options.reEvaluateScripts);
	return dom;
};
<|endoftext|>


<commit_before>exports.log = jsio.__env.log;
exports.GLOBAL = jsio.__env.global;

var SLICE = Array.prototype.slice;

exports.bind = function(context, method /*, VARGS*/) {
	if(arguments.length > 2) {
		var args = SLICE.call(arguments, 2);
		return typeof method == 'string'
			? function $$boundMethod() {
				if (context[method]) {
					return context[method].apply(context, args.concat(SLICE.call(arguments, 0)));
				} else {
					throw logger.error('No method:', method, 'for context', context);
				}
			}
			: function $$boundMethod() { return method.apply(context, args.concat(SLICE.call(arguments, 0))); }
	} else {
		return typeof method == 'string'
			? function $$boundMethod() {
				if (context[method]) {
					return context[method].apply(context, arguments);
				} else {
					throw logger.error('No method:', method, 'for context', context);
				}
			}
			: function $$boundMethod() { return method.apply(context, arguments); }
	}
}

exports.Class = function(parent, proto) {
	if(!parent) { throw new Error('parent or prototype not provided'); }
	if(!proto) { proto = parent; parent = null; }
	else if(parent instanceof Array) { // multiple inheritance, use at your own risk =)
		proto.prototype = {};
		for(var i = 0, p; p = parent[i]; ++i) {
			for(var item in p.prototype) {
				if(!(item in proto.prototype)) {
					proto.prototype[item] = p.prototype[item];
				}
			}
		}
		parent = parent[0];
	} else {
		proto.prototype = parent.prototype;
	}

	var cls = function() { if(this.init) { return this.init.apply(this, arguments); }}
	cls.prototype = new proto(parent ? function(context, method, args) {
		var args = args || [];
		var target = proto;
		while(target = target.prototype) {
			if(target[method]) {
				return target[method].apply(context, args);
			}
		}
		throw new Error('method ' + method + ' does not exist');
	} : null);
	cls.prototype.constructor = cls;
	return cls;
}

exports.$setTimeout = function(f, t/*, VARGS */) {
	var args = SLICE.call(arguments, 2);
	return setTimeout(function() {
		try {
			f.apply(this, args);
		} catch(e) {
			// log?
		}
	}, t)
}

exports.$setInterval = function(f, t/*, VARGS */) {
	var args = SLICE.call(arguments, 2);
	return setInterval(function() {
		try {
			f.apply(this, args);
		} catch(e) {
			// log?
		}
	}, t)
}

// node doesn't let you call clearTimeout(null)
exports.$clearTimeout = function (timer) { return timer ? clearTimeout(timer) : null; };
exports.$clearInterval = function (timer) { return timer ? clearInterval(timer) : null; };

// keep logging local variables out of other closures in this file!
exports.logging = (function() {

	// logging namespace, this is what is exported
	var logging = {
			DEBUG: 1,
			LOG: 2,
			INFO: 3,
			WARN: 4,
			ERROR: 5
		},
		loggers = {}, // effectively globals - all loggers and a global production state
		production = false;

	logging.setProduction = function(prod) { production = !!prod; }
	logging.get = function(name) {
		return loggers.hasOwnProperty(name) ? loggers[name]
			: (loggers[name] = new Logger(name));
	}
	logging.set = function(name, _logger) {
		loggers[name] = _logger;
	}

	logging.getAll = function() { return loggers; }

	logging.__create = function(pkg, ctx) { ctx.logger = logging.get(pkg); }

	var Logger = exports.Class(function() {
		this.init = function(name, level) {
			this._name = name;
			this._level = level || logging.LOG;
			this._listener = exports.log;
		}

		this.setLevel = function(level) { this._level = level; }

		function makeLogFunction(level, type) {
			return function() {
				if (!production && level >= this._level) {
					return this._listener.apply(this._listener, [type, this._name].concat(SLICE.call(arguments)));
				}
				return arguments[0];
			}
		}

		this.setListener = function(listener) { log = listener; }
		this.debug = makeLogFunction(logging.DEBUG, "DEBUG");
		this.log = makeLogFunction(logging.LOG, "LOG");
		this.info = makeLogFunction(logging.INFO, "INFO");
		this.warn = makeLogFunction(logging.WARN, "WARN");
		this.error = makeLogFunction(logging.ERROR, "ERROR");
	});

	return logging;
})();

var logger = exports.logging.get('jsiocore');
<commit_msg>add a configurable prefix to logging for making iframe debugging easier (all logs in the iframe share a prefix<commit_after>exports.log = jsio.__env.log;
exports.GLOBAL = jsio.__env.global;

var SLICE = Array.prototype.slice;

exports.bind = function(context, method /*, VARGS*/) {
	if(arguments.length > 2) {
		var args = SLICE.call(arguments, 2);
		return typeof method == 'string'
			? function $$boundMethod() {
				if (context[method]) {
					return context[method].apply(context, args.concat(SLICE.call(arguments, 0)));
				} else {
					throw logger.error('No method:', method, 'for context', context);
				}
			}
			: function $$boundMethod() { return method.apply(context, args.concat(SLICE.call(arguments, 0))); }
	} else {
		return typeof method == 'string'
			? function $$boundMethod() {
				if (context[method]) {
					return context[method].apply(context, arguments);
				} else {
					throw logger.error('No method:', method, 'for context', context);
				}
			}
			: function $$boundMethod() { return method.apply(context, arguments); }
	}
}

exports.Class = function(parent, proto) {
	if(!parent) { throw new Error('parent or prototype not provided'); }
	if(!proto) { proto = parent; parent = null; }
	else if(parent instanceof Array) { // multiple inheritance, use at your own risk =)
		proto.prototype = {};
		for(var i = 0, p; p = parent[i]; ++i) {
			for(var item in p.prototype) {
				if(!(item in proto.prototype)) {
					proto.prototype[item] = p.prototype[item];
				}
			}
		}
		parent = parent[0];
	} else {
		proto.prototype = parent.prototype;
	}

	var cls = function() { if(this.init) { return this.init.apply(this, arguments); }}
	cls.prototype = new proto(parent ? function(context, method, args) {
		var args = args || [];
		var target = proto;
		while(target = target.prototype) {
			if(target[method]) {
				return target[method].apply(context, args);
			}
		}
		throw new Error('method ' + method + ' does not exist');
	} : null);
	cls.prototype.constructor = cls;
	return cls;
}

exports.$setTimeout = function(f, t/*, VARGS */) {
	var args = SLICE.call(arguments, 2);
	return setTimeout(function() {
		try {
			f.apply(this, args);
		} catch(e) {
			// log?
		}
	}, t)
}

exports.$setInterval = function(f, t/*, VARGS */) {
	var args = SLICE.call(arguments, 2);
	return setInterval(function() {
		try {
			f.apply(this, args);
		} catch(e) {
			// log?
		}
	}, t)
}

// node doesn't let you call clearTimeout(null)
exports.$clearTimeout = function (timer) { return timer ? clearTimeout(timer) : null; };
exports.$clearInterval = function (timer) { return timer ? clearInterval(timer) : null; };

// keep logging local variables out of other closures in this file!
exports.logging = (function() {

	// logging namespace, this is what is exported
	var logging = {
			DEBUG: 1,
			LOG: 2,
			INFO: 3,
			WARN: 4,
			ERROR: 5
		},
		loggers = {}, // effectively globals - all loggers and a global production state
		production = false;
	var gPrefix = '';
	logging.setPrefix = function(prefix) { gPrefix = prefix + ' '; }
	logging.setProduction = function(prod) { production = !!prod; }
	logging.get = function(name) {
		return loggers.hasOwnProperty(name) ? loggers[name]
			: (loggers[name] = new Logger(name));
	}
	logging.set = function(name, _logger) {
		loggers[name] = _logger;
	}

	logging.getAll = function() { return loggers; }

	logging.__create = function(pkg, ctx) { ctx.logger = logging.get(pkg); }

	var Logger = exports.Class(function() {
		this.init = function(name, level) {
			this._name = name;
			this._level = level || logging.LOG;
			this._listener = exports.log;
		}

		this.setLevel = function(level) { this._level = level; }

		function makeLogFunction(level, type) {
			return function() {
				if (!production && level >= this._level) {
					var prefix = type + ' ' + gPrefix + this._name;
					return this._listener.apply(this._listener, [prefix].concat(SLICE.call(arguments)));
				}
				return arguments[0];
			}
		}

		this.setListener = function(listener) { log = listener; }
		this.debug = makeLogFunction(logging.DEBUG, "DEBUG");
		this.log = makeLogFunction(logging.LOG, "LOG");
		this.info = makeLogFunction(logging.INFO, "INFO");
		this.warn = makeLogFunction(logging.WARN, "WARN");
		this.error = makeLogFunction(logging.ERROR, "ERROR");
	});

	return logging;
})();

var logger = exports.logging.get('jsiocore');
<|endoftext|>


<commit_before>
/**
 * Perform an click action on the given element
 * @param  {String}   action  The action to perform (click or doubleClick)
 * @param  {String}   type    Type of the element (link or selector)
 * @param  {String}   element Element selector
 */
module.exports = () => {

   global.Bank_url="http://www.demo.guru99.com/v4/"
   global.ExpectedBank_url="http://www.demo.guru99.com/v4/"
   global.userNameTextbox = "input[name='uid']";
   global.passwordTextbox = "input[name='password']";
   global.loginButton = "input[name='btnLogin']";
   global.balanceEnquiryLink = "a[href='BalEnqInput.php']";
   global.AccountNumberTextBox = "input[name='accountno']";
   global.amountTextbox = "input[name='ammount']";

   global.loginPageTitle = " Guru99 Bank Home Page";
   global.homePageTitle = " Guru99 Bank Manager HomePage";
   global.errorMessage = "#spanMessage";

   global.submitButton = "input[name='AccSubmit']";
   global.BalanceText = ".//*[@id='balenquiry']/tbody/tr[16]/td[2]";
   global.ExpectedBalance = "10000";

   global.descriptionTextbox = "input[name='desc']";

   global.depositLink = "a[href='DepositInput.php']";
   global.withdrawalLink = "a[href='WithdrawalInput.php']";
   global.newAccountLink = "a[href='addAccount.php']";
   global.editAccountLink = "a[href='editAccount.php']";
   global.deleteAccountLink = "a[href='deleteAccountInput.php']";
   global.fundTransferLink = "a[href='FundTransInput.php']";
   global.miniStatementLink = "a[href='MiniStatementInput.php']";
   //global.ditAccountLink = "a[href='editAccount.php']";

   global.customerIdTextbox = "input[name='cusid']";
   global.addNewAccount_AccountTypeDropdown = "//select[@name='selaccount']";

   global.addNewAccount_AccountTypeSavings = "//select[@name='selaccount']/option[1]";
   global.addNewAccount_AccountTypeCurrent = "//select[@name='selaccount']/option[2]";
   global.inputForAccountTypeDropdown = "Current";

   global.initialDepositTextbox = "input[name='inideposit']";
   global.addNewAccount_SubmitButton = "input[value='submit']";
   global.accountIDText = ".//*[@id='account']/tbody/tr[4]/td[2]";

   global.editAccount_AccountTypeCurrent = "//select[@name='a_type']/option[1]";
   global.editAccount_AccountTypeSavings = "//select[@name='a_type']/option[2]";

   global.payersAccountTextbox = "input[name='payersaccount']";
   global.payeeAccountTextbox = "input[name='payeeaccount']";

   global.MiniStateTransactionTypeForWithdrawal = ".//*[@id='ministmt']/tbody/tr[3]/td[3]";
   global.MiniStateTransactionTypeForDeposit = ".//*[@id='ministmt']/tbody/tr[2]/td[3]";
   global.MiniStateAmountForWithdrawal = ".//*[@id='ministmt']/tbody/tr[3]/td[2]";
   global.MiniStateAmountForDeposit = ".//*[@id='ministmt']/tbody/tr[2]/td[2]";

   global.Var_MiniStateTransactionTypeForWithdrawal = "w";
   global.var_MiniStateTransactionTypeForDeposit = "d";
   global.Var_MiniStateAmountForWithdrawal = "5000";
   global.var_MiniStateAmountForDeposit = "5000";

};

<commit_msg>POM added<commit_after>
/**
 * Perform an click action on the given element
 * @param  {String}   action  The action to perform (click or doubleClick)
 * @param  {String}   type    Type of the element (link or selector)
 * @param  {String}   element Element selector
 */
module.exports = () => {

   global.Bank_url="http://www.demo.guru99.com/v4/"
   global.ExpectedBank_url="http://www.demo.guru99.com/v4/"
   global.userNameTextbox = "input[name='uid']";
   global.passwordTextbox = "input[name='password']";
   global.loginButton = "input[name='btnLogin']";
   global.balanceEnquiryLink = "a[href='BalEnqInput.php']";
   global.AccountNumberTextBox = "input[name='accountno']";
   global.amountTextbox = "input[name='ammount']";

   global.loginPageTitle = " Guru99 Bank Home Page";
   global.homePageTitle = " Guru99 Bank Manager HomePage";
   global.errorMessage = "#spanMessage";

   global.submitButton = "input[name='AccSubmit']";
   global.BalanceText = ".//*[@id='balenquiry']/tbody/tr[16]/td[2]";
   global.ExpectedBalance = "10000";

   global.descriptionTextbox = "input[name='desc']";

   global.depositLink = "a[href='DepositInput.php']";
   global.withdrawalLink = "a[href='WithdrawalInput.php']";
   global.newAccountLink = "a[href='addAccount.php']";
   global.editAccountLink = "a[href='editAccount.php']";
   global.deleteAccountLink = "a[href='deleteAccountInput.php']";
   global.fundTransferLink = "a[href='FundTransInput.php']";
   global.miniStatementLink = "a[href='MiniStatementInput.php']";
   //global.ditAccountLink = "a[href='editAccount.php']";

   global.customerIdTextbox = "input[name='cusid']";
   global.addNewAccount_AccountTypeDropdown = "//select[@name='selaccount']";

   global.addNewAccount_AccountTypeSavings = "//select[@name='selaccount']/option[1]";
   global.addNewAccount_AccountTypeCurrent = "//select[@name='selaccount']/option[2]";
   global.inputForAccountTypeDropdown = "Current";

   global.initialDepositTextbox = "input[name='inideposit']";
   global.addNewAccount_SubmitButton = "input[value='submit']";
   global.accountIDText = ".//*[@id='account']/tbody/tr[4]/td[2]";

   global.editAccount_AccountTypeCurrent = "//select[@name='a_type']/option[1]";
   global.editAccount_AccountTypeSavings = "//select[@name='a_type']/option[2]";

   global.payersAccountTextbox = "input[name='payersaccount']";
   global.payeeAccountTextbox = "input[name='payeeaccount']";

   global.Var_MiniStateTransactionTypeForWithdrawal = "w";
   global.var_MiniStateTransactionTypeForDeposit = "d";
   global.Var_MiniStateAmountForWithdrawal = "5000";
   global.var_MiniStateAmountForDeposit = "5000";

    MiniStateTransactionTypeForWithdrawal: { get: function () { return browser.element('.//*[@id='ministmt']/tbody/tr[3]/td[3]'); } },
    MiniStateTransactionTypeForDeposit: { get: function () { return browser.element('.//*[@id='ministmt']/tbody/tr[2]/td[3]'); } },
    MiniStateAmountForWithdrawal:     { get: function () { return browser.element('.//*[@id='ministmt']/tbody/tr[3]/td[2]'); } },
    MiniStateAmountForDeposit:    { get: function () { return browser.element('.//*[@id='ministmt']/tbody/tr[2]/td[2]'); } },

};

<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import DefaultPane from 'part:@sanity/components/panes/default'
import styles from './styles/ErrorPane.css'

export default function ErrorPane(props) {
  return (
    <DefaultPane title="Error" isSelected={false} isCollapsed={false}>

{props.children}

    </DefaultPane>
  )
}

ErrorPane.propTypes = {
  children: PropTypes.node.isRequired
}
<commit_msg>[desk-tool] Center content of error pane<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import DefaultPane from 'part:@sanity/components/panes/default'
import styles from './styles/ErrorPane.css'

export default function ErrorPane(props) {
  return (
    <DefaultPane title="Error" isSelected={false} isCollapsed={false} isScrollable={false}>

{props.children}

    </DefaultPane>
  )
}

ErrorPane.propTypes = {
  children: PropTypes.node.isRequired
}
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import Head from 'next/head';
import stringSimilarity from 'string-similarity';
import { nl2br, linkify } from '../util/text';

import app from 'components/App';
import ArticleInfo from 'components/ArticleInfo';
import ArticleItem from 'components/ArticleItem';
import CurrentReplies from 'components/CurrentReplies';
import RelatedReplies from 'components/RelatedReplies';
import ReplySearch from 'components/ReplySearch/ReplySearch.js';
import ReplyForm from 'components/ReplyForm';
import ReplyRequestReason from 'components/ReplyRequestReason';
import {
  load,
  loadAuth,
  submitReply,
  connectReply,
  searchReplies,
  searchRepiedArticle,
  updateArticleReplyStatus,
  voteReply,
  reset,
  voteReplyRequest,
} from 'ducks/articleDetail';

import { detailStyle, tabMenuStyle } from './article.styles';

class ArticlePage extends React.Component {
  state = {
    tab: 'search', // 'new, 'related', 'search'
  };

  handleConnect = ({ target: { value: replyId } }) => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(connectReply(id, replyId)).then(this.scrollToReplySection);
  };

  handleSearchReply = ({ target: { value: queryString } }, after) => {
    const { dispatch } = this.props;
    dispatch(searchReplies({ q: queryString, after }));
    dispatch(searchRepiedArticle({ q: queryString }));
  };

  handleSubmit = reply => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(submitReply({ ...reply, articleId: id })).then(
      this.scrollToReplySection
    );
  };

  handleReplyConnectionDelete = conn => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(
      updateArticleReplyStatus(id, conn.get('replyId'), 'DELETED')
    );
  };

  handleReplyConnectionRestore = conn => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(
      updateArticleReplyStatus(id, conn.get('replyId'), 'NORMAL')
    ).then(this.scrollToReplySection);
  };

  handleVoteReplyRequest = (replyRequestId, voteType, indexOfReplyRequests) => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    dispatch(
      voteReplyRequest(id, replyRequestId, voteType, indexOfReplyRequests)
    );
  };

  handleReplyConnectionVote = (conn, vote) => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(voteReply(id, conn.get('replyId'), vote));
  };

  handleTabChange = tab => () => {
    this.setState({ tab });
  };

  scrollToReplySection = () => {
    if (!this._replySectionEl) return;
    this._replySectionEl.scrollIntoView({ behavior: 'smooth' });
  };

  componentWillUnmount() {
    const { dispatch } = this.props;
    dispatch(reset());
  }

  renderTabMenu = () => {
    const { data } = this.props;
    const { tab } = this.state;
    const relatedReplyCount = data.get('relatedReplies').size;

    return (

        <li
          onClick={this.handleTabChange('new')}
          className={`tab ${tab === 'new' ? 'active' : ''}`}
        >
          撰寫回應

          {relatedReplyCount === 0 ? (
            '無相關回應'
          ) : (

              使用相關回應 {relatedReplyCount}

          )}

        <li
          onClick={this.handleTabChange('search')}
          className={`tab ${tab === 'search' ? 'active' : ''}`}
        >
          搜尋

    );
  };

  renderNewReplyTab = () => {
    const { data, isReplyLoading } = this.props;
    const { tab } = this.state;

    const article = data.get('article');
    const relatedReplies = data.get('relatedReplies');
    const searchArticles = data.get('searchArticles');
    const searchReplies = data.get('searchReplies');

    const articleText = article.get('text', '');
    const getArticleSimilarity = relatedArticleText =>
      stringSimilarity.compareTwoStrings(articleText, relatedArticleText);

    switch (tab) {
      case 'new':
        return (
          <ReplyForm onSubmit={this.handleSubmit} disabled={isReplyLoading} />
        );

      case 'related':
        return (
          <RelatedReplies
            onConnect={this.handleConnect}
            relatedReplies={relatedReplies}
            getArticleSimilarity={getArticleSimilarity}
          />
        );

      case 'search':
        return (
          <ReplySearch
            onConnect={this.handleConnect}
            onSearch={this.handleSearchReply}
            articles={searchArticles}
            replies={searchReplies}
          />
        );

      default:
        return null;
    }
  };

  render() {
    const { data, isLoading, isReplyLoading, authId } = this.props;

    const article = data.get('article');
    const replyConnections = data.get('replyConnections');
    const relatedArticles = data.get('relatedArticles');

    if (isLoading && article === null) {
      return
Loading...
;
    }

    if (article === null) {
      return
Article not found.
;
    }

    const slicedArticleTitle = article.get('text').slice(0, 15);

    return (

          <title>{slicedArticleTitle}⋯⋯ | Cofacts 真的假的</title>

訊息原文

            <ArticleInfo article={article} />

            {nl2br(
              linkify(article.get('text'), { props: { target: '_blank' } })
            )}

          {article.get('replyRequests').map((replyRequest, index) => {
            return (
              <ReplyRequestReason
                key={`reason-${index}`}
                index={index}
                articleId={article.get('id')}
                replyRequest={replyRequest}
                isArticleCreator={index === 0}
                onVoteReason={this.handleVoteReplyRequest}
              />
            );
          })}

 (this._replySectionEl = replySectionEl)}
        >

現有回應

          <CurrentReplies
            authId={authId}
            replyConnections={replyConnections}
            disabled={isReplyLoading}
            onDelete={this.handleReplyConnectionDelete}
            onRestore={this.handleReplyConnectionRestore}
            onVote={this.handleReplyConnectionVote}
          />

增加新回應

          {this.renderTabMenu()}

{this.renderNewReplyTab()}

        {relatedArticles.size ? (

你可能也會對這些類似文章有興趣

              {relatedArticles.map(article => (
                <ArticleItem key={article.get('id')} article={article} />
              ))}

        ) : (
          ''
        )}

    );
  }
}

function initFn(dispatch, { query: { id } }) {
  return dispatch(load(id));
}
function bootstrapFn(dispatch, { query: { id } }) {
  return dispatch(loadAuth(id));
}

function mapStateToProps({ articleDetail, auth }) {
  return {
    authId: auth.getIn(['user', 'id']),
    isLoading: articleDetail.getIn(['state', 'isLoading']),
    isReplyLoading: articleDetail.getIn(['state', 'isReplyLoading']),
    data: articleDetail.get('data'),
  };
}

export default compose(app(initFn, bootstrapFn), connect(mapStateToProps))(
  ArticlePage
);
<commit_msg>fix lint prettier<commit_after>import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import Head from 'next/head';
import stringSimilarity from 'string-similarity';
import { nl2br, linkify } from '../util/text';

import app from 'components/App';
import ArticleInfo from 'components/ArticleInfo';
import ArticleItem from 'components/ArticleItem';
import CurrentReplies from 'components/CurrentReplies';
import RelatedReplies from 'components/RelatedReplies';
import ReplySearch from 'components/ReplySearch/ReplySearch.js';
import ReplyForm from 'components/ReplyForm';
import ReplyRequestReason from 'components/ReplyRequestReason';
import {
  load,
  loadAuth,
  submitReply,
  connectReply,
  searchReplies,
  searchRepiedArticle,
  updateArticleReplyStatus,
  voteReply,
  reset,
  voteReplyRequest,
} from 'ducks/articleDetail';

import { detailStyle, tabMenuStyle } from './article.styles';

class ArticlePage extends React.Component {
  state = {
    tab: 'search', // 'new, 'related', 'search'
  };

  handleConnect = ({ target: { value: replyId } }) => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(connectReply(id, replyId)).then(this.scrollToReplySection);
  };

  handleSearchReply = ({ target: { value: queryString } }, after) => {
    const { dispatch } = this.props;
    dispatch(searchReplies({ q: queryString, after }));
    dispatch(searchRepiedArticle({ q: queryString }));
  };

  handleSubmit = reply => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(submitReply({ ...reply, articleId: id })).then(
      this.scrollToReplySection
    );
  };

  handleReplyConnectionDelete = conn => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(
      updateArticleReplyStatus(id, conn.get('replyId'), 'DELETED')
    );
  };

  handleReplyConnectionRestore = conn => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(
      updateArticleReplyStatus(id, conn.get('replyId'), 'NORMAL')
    ).then(this.scrollToReplySection);
  };

  handleVoteReplyRequest = (replyRequestId, voteType, indexOfReplyRequests) => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    dispatch(
      voteReplyRequest(id, replyRequestId, voteType, indexOfReplyRequests)
    );
  };

  handleReplyConnectionVote = (conn, vote) => {
    const {
      dispatch,
      query: { id },
    } = this.props;
    return dispatch(voteReply(id, conn.get('replyId'), vote));
  };

  handleTabChange = tab => () => {
    this.setState({ tab });
  };

  scrollToReplySection = () => {
    if (!this._replySectionEl) return;
    this._replySectionEl.scrollIntoView({ behavior: 'smooth' });
  };

  componentWillUnmount() {
    const { dispatch } = this.props;
    dispatch(reset());
  }

  renderTabMenu = () => {
    const { data } = this.props;
    const { tab } = this.state;
    const relatedReplyCount = data.get('relatedReplies').size;

    return (

        <li
          onClick={this.handleTabChange('new')}
          className={`tab ${tab === 'new' ? 'active' : ''}`}
        >
          撰寫回應

          {relatedReplyCount === 0 ? (
            '無相關回應'
          ) : (

              使用相關回應 {relatedReplyCount}

          )}

        <li
          onClick={this.handleTabChange('search')}
          className={`tab ${tab === 'search' ? 'active' : ''}`}
        >
          搜尋

    );
  };

  renderNewReplyTab = () => {
    const { data, isReplyLoading } = this.props;
    const { tab } = this.state;

    const article = data.get('article');
    const relatedReplies = data.get('relatedReplies');
    const searchArticles = data.get('searchArticles');
    const searchReplies = data.get('searchReplies');

    const articleText = article.get('text', '');
    const getArticleSimilarity = relatedArticleText =>
      stringSimilarity.compareTwoStrings(articleText, relatedArticleText);

    switch (tab) {
      case 'new':
        return (
          <ReplyForm onSubmit={this.handleSubmit} disabled={isReplyLoading} />
        );

      case 'related':
        return (
          <RelatedReplies
            onConnect={this.handleConnect}
            relatedReplies={relatedReplies}
            getArticleSimilarity={getArticleSimilarity}
          />
        );

      case 'search':
        return (
          <ReplySearch
            onConnect={this.handleConnect}
            onSearch={this.handleSearchReply}
            articles={searchArticles}
            replies={searchReplies}
          />
        );

      default:
        return null;
    }
  };

  render() {
    const { data, isLoading, isReplyLoading, authId } = this.props;

    const article = data.get('article');
    const replyConnections = data.get('replyConnections');
    const relatedArticles = data.get('relatedArticles');

    if (isLoading && article === null) {
      return
Loading...
;
    }

    if (article === null) {
      return
Article not found.
;
    }

    const slicedArticleTitle = article.get('text').slice(0, 15);

    return (

          <title>{slicedArticleTitle}⋯⋯ | Cofacts 真的假的</title>

訊息原文

            <ArticleInfo article={article} />

            {nl2br(
              linkify(article.get('text'), { props: { target: '_blank' } })
            )}

            {article.get('replyRequests').map((replyRequest, index) => {
              return (
                <ReplyRequestReason
                  key={`reason-${index}`}
                  index={index}
                  articleId={article.get('id')}
                  replyRequest={replyRequest}
                  isArticleCreator={index === 0}
                  onVoteReason={this.handleVoteReplyRequest}
                />
              );
            })}

 (this._replySectionEl = replySectionEl)}
        >

現有回應

          <CurrentReplies
            authId={authId}
            replyConnections={replyConnections}
            disabled={isReplyLoading}
            onDelete={this.handleReplyConnectionDelete}
            onRestore={this.handleReplyConnectionRestore}
            onVote={this.handleReplyConnectionVote}
          />

增加新回應

          {this.renderTabMenu()}

{this.renderNewReplyTab()}

        {relatedArticles.size ? (

你可能也會對這些類似文章有興趣

              {relatedArticles.map(article => (
                <ArticleItem key={article.get('id')} article={article} />
              ))}

        ) : (
          ''
        )}

    );
  }
}

function initFn(dispatch, { query: { id } }) {
  return dispatch(load(id));
}
function bootstrapFn(dispatch, { query: { id } }) {
  return dispatch(loadAuth(id));
}

function mapStateToProps({ articleDetail, auth }) {
  return {
    authId: auth.getIn(['user', 'id']),
    isLoading: articleDetail.getIn(['state', 'isLoading']),
    isReplyLoading: articleDetail.getIn(['state', 'isReplyLoading']),
    data: articleDetail.get('data'),
  };
}

export default compose(app(initFn, bootstrapFn), connect(mapStateToProps))(
  ArticlePage
);
<|endoftext|>


<commit_before>const resolve = require('path').resolve;
const pullAll = require('lodash/pullAll');
const uniq = require('lodash/uniq');

const ReactBoilerplate = {
  // This refers to the react-boilerplate version this project is based on.
  version: '3.4.0',

  /**
   * The DLL Plugin provides a dramatic speed increase to webpack build and hot module reloading
   * by caching the module metadata for all of our npm dependencies. We enable it by default
   * in development.
   *
   *
   * To disable the DLL Plugin, set this value to false.
   */
  dllPlugin: {
    defaults: {
      /**
       * we need to exclude dependencies which are not intended for the browser
       * by listing them here.
       */
      exclude: [
        'chalk',
        'compression',
        'cross-env',
        'express',
        'ip',
        'minimist',
        'sanitize.css',
        'nodemailer',
        'mongoose',
        'mongoose-delete',
        'bcrypt',
        'jsonwebtoken',
        'passport',
        'passport-jwt',
        'ical-generator',
        'autosuggest-highlight',
      ],

      /**
       * Specify any additional dependencies here. We include core-js and lodash
       * since a lot of our dependencies depend on them and they get picked up by webpack.
       */
      include: ['core-js', 'eventsource-polyfill', 'babel-polyfill', 'lodash'],

      // The path where the DLL manifest and bundle will get built
      path: resolve('../node_modules/react-boilerplate-dlls'),
    },

    entry(pkg) {
      const dependencyNames = Object.keys(pkg.dependencies);
      const exclude = pkg.dllPlugin.exclude || ReactBoilerplate.dllPlugin.defaults.exclude;
      const include = pkg.dllPlugin.include || ReactBoilerplate.dllPlugin.defaults.include;
      const includeDependencies = uniq(dependencyNames.concat(include));

      return {
        reactBoilerplateDeps: pullAll(includeDependencies, exclude),
      };
    },
  },
};

module.exports = ReactBoilerplate;
<commit_msg>fix npm install issue with dotenv<commit_after>const resolve = require('path').resolve;
const pullAll = require('lodash/pullAll');
const uniq = require('lodash/uniq');

const ReactBoilerplate = {
  // This refers to the react-boilerplate version this project is based on.
  version: '3.4.0',

  /**
   * The DLL Plugin provides a dramatic speed increase to webpack build and hot module reloading
   * by caching the module metadata for all of our npm dependencies. We enable it by default
   * in development.
   *
   *
   * To disable the DLL Plugin, set this value to false.
   */
  dllPlugin: {
    defaults: {
      /**
       * we need to exclude dependencies which are not intended for the browser
       * by listing them here.
       */
      exclude: [
        'chalk',
        'compression',
        'cross-env',
        'dotenv',
        'express',
        'ip',
        'minimist',
        'sanitize.css',
        'nodemailer',
        'mongoose',
        'mongoose-delete',
        'bcrypt',
        'jsonwebtoken',
        'passport',
        'passport-jwt',
        'ical-generator',
        'autosuggest-highlight',
      ],

      /**
       * Specify any additional dependencies here. We include core-js and lodash
       * since a lot of our dependencies depend on them and they get picked up by webpack.
       */
      include: ['core-js', 'eventsource-polyfill', 'babel-polyfill', 'lodash'],

      // The path where the DLL manifest and bundle will get built
      path: resolve('../node_modules/react-boilerplate-dlls'),
    },

    entry(pkg) {
      const dependencyNames = Object.keys(pkg.dependencies);
      const exclude = pkg.dllPlugin.exclude || ReactBoilerplate.dllPlugin.defaults.exclude;
      const include = pkg.dllPlugin.include || ReactBoilerplate.dllPlugin.defaults.include;
      const includeDependencies = uniq(dependencyNames.concat(include));

      return {
        reactBoilerplateDeps: pullAll(includeDependencies, exclude),
      };
    },
  },
};

module.exports = ReactBoilerplate;
<|endoftext|>


<commit_before>"use strict";

const SyncStrategy = require("./SyncStrategy");

const defaults = {
    syncsBufferLength: 5,
    maxReEnactSteps: 60,  // maximum number of steps to re-enact
    RTTEstimate: 2,       // estimate the RTT as two steps (for updateRate=6, that's 200ms)
    extrapolate: 2,       // player performs method "X" which means extrapolate to match server time. that 100 + (0..100)
    localObjBending: 0.1, // amount of bending towards position of sync object
    remoteObjBending: 0.6 // amount of bending towards position of sync object
};

class ExtrapolateStrategy extends SyncStrategy {

    constructor(clientEngine, inputOptions) {

        const options = Object.assign({}, defaults, inputOptions);
        super(clientEngine, options);

        this.newSync = null;
        this.recentInputs = {};
        this.gameEngine = this.clientEngine.gameEngine;
        this.gameEngine.on('client.postStep', this.extrapolate.bind(this));
        this.gameEngine.on('client.syncReceived', this.collectSync.bind(this));
        this.gameEngine.on('client.preInput', this.clientInputSave.bind(this));
    }

    // keep a buffer of inputs so that we can replay them on extrapolation
    clientInputSave(inputData) {

        // if no inputs have been stored for this step, create an array
        if (!this.recentInputs[inputData.step]) {
            this.recentInputs[inputData.step] = [];
        }
        this.recentInputs[inputData.step].push(inputData);
    }

    // collect a sync and its events
    collectSync(e) {
        // TODO avoid editing the input event

        // keep a reference of events by object id
        e.syncObjects = {};
        e.syncEvents.forEach(sEvent => {
            let o = sEvent.objectInstance;
            if (!e.syncObjects[o.id]) {
                e.syncObjects[o.id] = [];
            }
            e.syncObjects[o.id].push(sEvent);
        });

        // keep a reference of events by step
        e.syncSteps = {};
        e.syncEvents.forEach(sEvent => {

            // add an entry for this step and event-name
            if (!e.syncSteps[sEvent.stepCount]) e.syncSteps[sEvent.stepCount] = {};
            if (!e.syncSteps[sEvent.stepCount][sEvent.eventName]) e.syncSteps[sEvent.stepCount][sEvent.eventName] = [];
            e.syncSteps[sEvent.stepCount][sEvent.eventName].push(sEvent);
        });

        // remember this sync
        this.newSync = e;
    }

    // add an object to our world
    addNewObject(objId, newObj, options) {

        let curObj = newObj.class.newFrom(newObj);
        this.gameEngine.addObjectToWorld(curObj, options);
        console.log(`adding new object ${curObj}`);

        // if this game keeps a physics engine on the client side,
        // we need to update it as well
        // TODO: why not have this call inside (gameEngine.addObjectToWorld() above?)
        if (this.gameEngine.physicsEngine && curObj.hasOwnProperty('initPhysicsObject')) {
            curObj.initPhysicsObject(this.gameEngine.physicsEngine);
        }

        return curObj;
    }

    // clean up the input buffer
    cleanRecentInputs() {
        let firstReplayStep = this.gameEngine.world.stepCount - this.options.extrapolate;
        for (let input of Object.keys(this.recentInputs)) {
            if (this.recentInputs[input].step < firstReplayStep) {
                delete this.recentInputs[input];
            }
        }
    }

    // apply a new sync
    applySync() {
        if (!this.newSync) {
            return;
        }

        this.gameEngine.trace.debug('extrapolate applying sync');

        //
        //    scan all the objects in the sync
        //
        // 1. if the object has a local shadow, adopt the server object,
        //    and destroy the shadow
        //
        // 2. if the object exists locally, sync to the server object,
        //    later we will re-enact the missing steps and then bend to
        //    the current position
        //
        // 3. if the object is new, just create it
        //
        let world = this.gameEngine.world;
        let serverStep = -1;
        for (let ids of Object.keys(this.newSync.syncObjects)) {

            // TODO: we are currently taking only the first event out of
            // the events that may have arrived for this object
            let ev = this.newSync.syncObjects[ids][0];

            let curObj = world.objects[ev.objectInstance.id];
            serverStep = Math.max(serverStep, ev.stepCount);

            let localShadowObj = this.gameEngine.findLocalShadow(ev.objectInstance);
            if (localShadowObj) {

                // case 1: this object as a local shadow object on the client
                this.gameEngine.trace.debug(`object ${ev.objectInstance.id} replacing local shadow ${localShadowObj.id}`);
                let newObj = this.addNewObject(ev.objectInstance.id, ev.objectInstance, { visible: false });
                newObj.saveState(localShadowObj);
                localShadowObj.destroy();
                delete this.gameEngine.world.objects[localShadowObj.id];

            } else if (curObj) {

                // case 2: this object already exists locally
                this.gameEngine.trace.trace(`object before syncTo: ${curObj.toString()}`);
                curObj.saveState();
                curObj.syncTo(ev.objectInstance);
                this.gameEngine.trace.trace(`object after syncTo: ${curObj.toString()} synced to step[${ev.stepCount}]`);

            } else {

                // case 3: object does not exist.  create it now
                this.addNewObject(ev.objectInstance.id, ev.objectInstance);
            }
        }

        //
        // reenact the steps that we want to extrapolate forwards
        //
        this.cleanRecentInputs();
        this.gameEngine.trace.debug(`extrapolate re-enacting steps from [${serverStep}] to [${world.stepCount}]`);
        if (serverStep < world.stepCount - this.options.maxReEnactSteps) {
            serverStep = world.stepCount - this.options.maxReEnactSteps;
            this.gameEngine.trace.info(`too many steps to re-enact.  Starting from [${serverStep}] to [${world.stepCount}]`);
        }

        this.gameEngine.serverStep = serverStep;
        let clientStep = world.stepCount;
        for (world.stepCount = serverStep; world.stepCount < clientStep;) {
            if (this.recentInputs[world.stepCount]) {
                this.recentInputs[world.stepCount].forEach(inputData => {

                    // only movement inputs are re-enacted
                    if (!inputData.inputOptions || !inputData.inputOptions.movement) return;

                    this.gameEngine.trace.trace(`extrapolate re-enacting movement input[${inputData.messageIndex}]: ${inputData.input}`);
                    this.gameEngine.processInput(inputData, this.clientEngine.playerId);
                });
            }

            // run the game engine step in "reenact" mode
            this.gameEngine.step(true);
        }

        //
        // bend back to original state
        //
        for (let objId of Object.keys(world.objects)) {

            // shadow objects are not bent
            if (objId >= this.gameEngine.options.clientIDSpace)
                continue;

            let obj = world.objects[objId];
            // TODO: using == instead of === because of string/number mismatch
            let bending = (objId == this.clientEngine.playerId) ? this.options.localObjBending : this.options.remoteObjBending;
            obj.bendToSavedState(bending, this.gameEngine.worldSettings);
            if (obj.renderObject && obj.renderObject.visible === false) {
                obj.updateRenderObject();
                obj.renderObject.visible = true;
            }
            this.gameEngine.trace.trace(`object[${objId}] bending=${bending} values (dx, dy, dphi) = (${obj.bendingX},${obj.bendingY},${obj.bendingAngle})`);
        }

        // trace object state after sync
        for (let objId of Object.keys(world.objects)) {
            this.gameEngine.trace.trace(`object after extrapolate replay: ${world.objects[objId].toString()}`);
        }

        // destroy uneeded objects
        // TODO: use this.forEachSyncObject instead of for-loop
        //       you will need to loop over prevObj instead of nextObj
        for (let objId of Object.keys(world.objects)) {
            if (objId < this.gameEngine.options.clientIDSpace && !this.newSync.syncObjects.hasOwnProperty(objId)) {
                world.objects[objId].destroy();
                delete this.gameEngine.world.objects[objId];
            }
        }

        this.newSync = null;
    }

    // Perform client-side extrapolation.
    extrapolate() {

        // if there is a sync from the server, apply it now
        this.applySync();
    }
}

module.exports = ExtrapolateStrategy;
<commit_msg>use copyFrom instead of newFrom<commit_after>"use strict";

const SyncStrategy = require("./SyncStrategy");

const defaults = {
    syncsBufferLength: 5,
    maxReEnactSteps: 60,  // maximum number of steps to re-enact
    RTTEstimate: 2,       // estimate the RTT as two steps (for updateRate=6, that's 200ms)
    extrapolate: 2,       // player performs method "X" which means extrapolate to match server time. that 100 + (0..100)
    localObjBending: 0.1, // amount of bending towards position of sync object
    remoteObjBending: 0.6 // amount of bending towards position of sync object
};

class ExtrapolateStrategy extends SyncStrategy {

    constructor(clientEngine, inputOptions) {

        const options = Object.assign({}, defaults, inputOptions);
        super(clientEngine, options);

        this.newSync = null;
        this.recentInputs = {};
        this.gameEngine = this.clientEngine.gameEngine;
        this.gameEngine.on('client.postStep', this.extrapolate.bind(this));
        this.gameEngine.on('client.syncReceived', this.collectSync.bind(this));
        this.gameEngine.on('client.preInput', this.clientInputSave.bind(this));
    }

    // keep a buffer of inputs so that we can replay them on extrapolation
    clientInputSave(inputData) {

        // if no inputs have been stored for this step, create an array
        if (!this.recentInputs[inputData.step]) {
            this.recentInputs[inputData.step] = [];
        }
        this.recentInputs[inputData.step].push(inputData);
    }

    // collect a sync and its events
    collectSync(e) {
        // TODO avoid editing the input event

        // keep a reference of events by object id
        e.syncObjects = {};
        e.syncEvents.forEach(sEvent => {
            let o = sEvent.objectInstance;
            if (!e.syncObjects[o.id]) {
                e.syncObjects[o.id] = [];
            }
            e.syncObjects[o.id].push(sEvent);
        });

        // keep a reference of events by step
        e.syncSteps = {};
        e.syncEvents.forEach(sEvent => {

            // add an entry for this step and event-name
            if (!e.syncSteps[sEvent.stepCount]) e.syncSteps[sEvent.stepCount] = {};
            if (!e.syncSteps[sEvent.stepCount][sEvent.eventName]) e.syncSteps[sEvent.stepCount][sEvent.eventName] = [];
            e.syncSteps[sEvent.stepCount][sEvent.eventName].push(sEvent);
        });

        // remember this sync
        this.newSync = e;
    }

    // add an object to our world
    addNewObject(objId, newObj, options) {

        let curObj = new newObj.constructor();
        curObj.copyFrom(newObj);
        this.gameEngine.addObjectToWorld(curObj, options);
        console.log(`adding new object ${curObj}`);

        // if this game keeps a physics engine on the client side,
        // we need to update it as well
        // TODO: why not have this call inside (gameEngine.addObjectToWorld() above?)
        if (this.gameEngine.physicsEngine && curObj.hasOwnProperty('initPhysicsObject')) {
            curObj.initPhysicsObject(this.gameEngine.physicsEngine);
        }

        return curObj;
    }

    // clean up the input buffer
    cleanRecentInputs() {
        let firstReplayStep = this.gameEngine.world.stepCount - this.options.extrapolate;
        for (let input of Object.keys(this.recentInputs)) {
            if (this.recentInputs[input].step < firstReplayStep) {
                delete this.recentInputs[input];
            }
        }
    }

    // apply a new sync
    applySync() {
        if (!this.newSync) {
            return;
        }

        this.gameEngine.trace.debug('extrapolate applying sync');

        //
        //    scan all the objects in the sync
        //
        // 1. if the object has a local shadow, adopt the server object,
        //    and destroy the shadow
        //
        // 2. if the object exists locally, sync to the server object,
        //    later we will re-enact the missing steps and then bend to
        //    the current position
        //
        // 3. if the object is new, just create it
        //
        let world = this.gameEngine.world;
        let serverStep = -1;
        for (let ids of Object.keys(this.newSync.syncObjects)) {

            // TODO: we are currently taking only the first event out of
            // the events that may have arrived for this object
            let ev = this.newSync.syncObjects[ids][0];

            let curObj = world.objects[ev.objectInstance.id];
            serverStep = Math.max(serverStep, ev.stepCount);

            let localShadowObj = this.gameEngine.findLocalShadow(ev.objectInstance);
            if (localShadowObj) {

                // case 1: this object as a local shadow object on the client
                this.gameEngine.trace.debug(`object ${ev.objectInstance.id} replacing local shadow ${localShadowObj.id}`);
                let newObj = this.addNewObject(ev.objectInstance.id, ev.objectInstance, { visible: false });
                newObj.saveState(localShadowObj);
                localShadowObj.destroy();
                delete this.gameEngine.world.objects[localShadowObj.id];

            } else if (curObj) {

                // case 2: this object already exists locally
                this.gameEngine.trace.trace(`object before syncTo: ${curObj.toString()}`);
                curObj.saveState();
                curObj.syncTo(ev.objectInstance);
                this.gameEngine.trace.trace(`object after syncTo: ${curObj.toString()} synced to step[${ev.stepCount}]`);

            } else {

                // case 3: object does not exist.  create it now
                this.addNewObject(ev.objectInstance.id, ev.objectInstance);
            }
        }

        //
        // reenact the steps that we want to extrapolate forwards
        //
        this.cleanRecentInputs();
        this.gameEngine.trace.debug(`extrapolate re-enacting steps from [${serverStep}] to [${world.stepCount}]`);
        if (serverStep < world.stepCount - this.options.maxReEnactSteps) {
            serverStep = world.stepCount - this.options.maxReEnactSteps;
            this.gameEngine.trace.info(`too many steps to re-enact.  Starting from [${serverStep}] to [${world.stepCount}]`);
        }

        this.gameEngine.serverStep = serverStep;
        let clientStep = world.stepCount;
        for (world.stepCount = serverStep; world.stepCount < clientStep;) {
            if (this.recentInputs[world.stepCount]) {
                this.recentInputs[world.stepCount].forEach(inputData => {

                    // only movement inputs are re-enacted
                    if (!inputData.inputOptions || !inputData.inputOptions.movement) return;

                    this.gameEngine.trace.trace(`extrapolate re-enacting movement input[${inputData.messageIndex}]: ${inputData.input}`);
                    this.gameEngine.processInput(inputData, this.clientEngine.playerId);
                });
            }

            // run the game engine step in "reenact" mode
            this.gameEngine.step(true);
        }

        //
        // bend back to original state
        //
        for (let objId of Object.keys(world.objects)) {

            // shadow objects are not bent
            if (objId >= this.gameEngine.options.clientIDSpace)
                continue;

            let obj = world.objects[objId];
            // TODO: using == instead of === because of string/number mismatch
            let bending = (objId == this.clientEngine.playerId) ? this.options.localObjBending : this.options.remoteObjBending;
            obj.bendToSavedState(bending, this.gameEngine.worldSettings);
            if (obj.renderObject && obj.renderObject.visible === false) {
                obj.updateRenderObject();
                obj.renderObject.visible = true;
            }
            this.gameEngine.trace.trace(`object[${objId}] bending=${bending} values (dx, dy, dphi) = (${obj.bendingX},${obj.bendingY},${obj.bendingAngle})`);
        }

        // trace object state after sync
        for (let objId of Object.keys(world.objects)) {
            this.gameEngine.trace.trace(`object after extrapolate replay: ${world.objects[objId].toString()}`);
        }

        // destroy uneeded objects
        // TODO: use this.forEachSyncObject instead of for-loop
        //       you will need to loop over prevObj instead of nextObj
        for (let objId of Object.keys(world.objects)) {
            if (objId < this.gameEngine.options.clientIDSpace && !this.newSync.syncObjects.hasOwnProperty(objId)) {
                world.objects[objId].destroy();
                delete this.gameEngine.world.objects[objId];
            }
        }

        this.newSync = null;
    }

    // Perform client-side extrapolation.
    extrapolate() {

        // if there is a sync from the server, apply it now
        this.applySync();
    }
}

module.exports = ExtrapolateStrategy;
<|endoftext|>


<commit_before>/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  moduleNameMapper: {
    '^lodash-es$': 'lodash',
  },
  preset: 'ts-jest',
  rootDir: './frontend',
  testEnvironment: 'jsdom',
  testPathIgnorePatterns: ['.next', 'node_modules'],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
  // https://github.com/zeit/next.js/issues/8663#issue-490553899
  globals: {
    // we must specify a custom tsconfig for tests because we need the typescript transform
    // to transform jsx into js rather than leaving it jsx such as the next build requires. you
    // can see this setting in tsconfig.jest.json -> "jsx": "react"
    'ts-jest': {
      tsconfig: 'tsconfig.jest.json',
    },
  },
};
<commit_msg>Update jest config<commit_after>/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  moduleNameMapper: {
    '^lodash-es$': 'lodash',
  },
  preset: 'ts-jest',
  rootDir: '.',
  testEnvironment: 'jsdom',
  testPathIgnorePatterns: ['.next', 'node_modules'],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
  // https://github.com/zeit/next.js/issues/8663#issue-490553899
  globals: {
    // we must specify a custom tsconfig for tests because we need the typescript transform
    // to transform jsx into js rather than leaving it jsx such as the next build requires. you
    // can see this setting in tsconfig.jest.json -> "jsx": "react"
    'ts-jest': {
      tsconfig: 'tsconfig.jest.json',
    },
  },
};
<|endoftext|>


<commit_before>$(document).ready(function() {

  function removeVideoContent() {
    // Remove the src so the player itself gets removed, a$movieTileis the
    // only reliable way to ensure the video stops playing in IE
    $('#trailer-video').attr('src', '');
    $('#trailer-video-container').empty();
    $('.movie-info').remove();
  }

  $('.close-btn').on('click', function (e) {
    removeVideoContent();
  });

  function addVideoContent(curMovieTile) {
    removeVideoContent();
    var trailerYouTubeId = curMovieTile.attr('data-trailer-youtube-id')
    var storyline = curMovieTile.attr('data-storyline')
    var link = curMovieTile.attr('data-link')
    var sourceUrl = 'http://www.youtube.com/embed/' + trailerYouTubeId + '?autoplay=1&html5=1';

    $('#trailer-video-container').empty().append($('', {
      'id': 'trailer-video',
      'type': 'text-html',
      'src': sourceUrl,
      'frameborder': 0
    }));

    var element = '

STORY: ' + storyline +
                  '

IMDb Link: ' + link + '
';
    $('.modal-content').append(element)
  }

  var $movieTile = $('.movie-tile');

  $movieTile.on('click', function() {
    addVideoContent($(this));
  });

  // Animate in the movies when the page loads
  $(document).ready(function () {
    $movieTile.hide().first().show('fast', function showNext() {
      $(this).next('div').show('fast', showNext);
    });
  });
});
<commit_msg>Update script.js<commit_after>$(document).ready(function () {

  function removeVideoContent() {
    // Remove the src so the player itself gets removed, as this is the
    // only reliable way to ensure the video stops playing in IE
    $('#trailer-video').attr('src', '');
    $('#trailer-video-container').empty();
    $('.movie-info').remove();
  }

  $('.close-btn').on('click', function (e) {
    removeVideoContent();
  });

  function addVideoContent(curMovieTile) {
    removeVideoContent();
    var trailerYouTubeId = curMovieTile.attr('data-trailer-youtube-id')
    var storyline = curMovieTile.attr('data-storyline')
    var link = curMovieTile.attr('data-link')
    var sourceUrl = 'http://www.youtube.com/embed/' + trailerYouTubeId + '?autoplay=1&html5=1';

    $('#trailer-video-container').empty().append($('', {
      'id': 'trailer-video',
      'type': 'text-html',
      'src': sourceUrl,
      'frameborder': 0
    }));

    var element = '

STORY: ' + storyline +
      '

IMDb Link: ' + link + '
';
    $('.modal-content').append(element)
  }

  var $movieTile = $('.movie-tile');

  $movieTile.on('click', function () {
    addVideoContent($(this));
  });

  // Animate in the movies when the page loads
  $(document).ready(function () {
    $movieTile.hide().first().show('fast', function showNext() {
      $(this).next('div').show('fast', showNext);
    });
  });
});
<|endoftext|>


<commit_before>"use strict";
var $, Option, ExclusionRulesOption, ExclusionRulesOnPopupOption,
bgSettings, bgExclusions, initPopupPage, BG,
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) {
  for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
  function ctor() { this.constructor = child; } ctor.prototype = parent.prototype;
  child.prototype = new ctor(); child.__super__ = parent.prototype;
  return child;
};

$ = document.getElementById.bind(document);
BG = chrome.extension.getBackgroundPage();
bgSettings = BG.Settings;
bgExclusions = BG.Exclusions;

function Option(field, onUpdated) {
  this.field = field;
  this.element = $(this.field);
  this.onUpdated = onUpdated;
  this.previous = null;
  this.fetch();
  Option.all.push(this);
}

Option.all = [];
Option.syncToFrontend = false;

Option.prototype.fetch = function() {
  this.populateElement(this.previous = bgSettings.get(this.field));
};

Option.prototype.save = function() {
  var value = this.readValueFromElement();
  if (!this.areEqual(value, this.previous)) {
    bgSettings.set(this.field, this.previous = value);
    if (this.field in bgSettings.bufferToLoad) {
      Option.syncToFrontend = true;
    }
  }
};

Option.prototype.areEqual = function(a, b) {
  return a === b;
};

ExclusionRulesOption = (function(_super) {
  __extends(ExclusionRulesOption, _super);

  function ExclusionRulesOption() {
    this.onRemoveRow = this.onRemoveRow.bind(this);
    this.template = $('exclusionRuleTemplate').content.children[0];
    ExclusionRulesOption.__super__.constructor.apply(this, arguments);
    $("exclusionAddButton").addEventListener("click", this.addRule.bind(this, null));
    this.element.addEventListener("input", this.onUpdated);
  }

  ExclusionRulesOption.prototype.addRule = function(pattern) {
    var element, exclusionScrollBox;
    element = this.appendRule(this.element, {
      pattern: pattern || "",
      passKeys: ""
    });
    this.getPattern(element).focus();
    exclusionScrollBox = $("exclusionScrollBox");
    exclusionScrollBox.scrollTop = exclusionScrollBox.scrollHeight;
    this.onUpdated();
    return element;
  };

  ExclusionRulesOption.prototype.populateElement = function(rules) {
    var frag = document.createDocumentFragment(), head;
    head = this.element.querySelector('tr');
    frag.appendChild(head);
    this.element.innerHTML = "";
    rules.forEach(this.appendRule.bind(this, frag));
    this.element.appendChild(frag);
  };

  ExclusionRulesOption.prototype.appendRule = function(list, rule) {
    var row;
    row = document.importNode(this.template, true);
    row.querySelector('.pattern').value = rule.pattern;
    row.querySelector('.passKeys').value = rule.passKeys;
    this.getRemoveButton(row).addEventListener("click", this.onRemoveRow);
    list.appendChild(row);
    return row;
  };

  ExclusionRulesOption.prototype.onRemoveRow = function(event) {
    var row1 = event.target.parentNode.parentNode;
    row1.parentNode.removeChild(row1);
    this.onUpdated();
  };

  ExclusionRulesOption.prototype.readValueFromElement = function(part) {
    var element, passKeys, pattern, rules, _i, _len, _ref, wchRegex;
    rules = [];
    wchRegex = /\s+/;
    _ref = this.element.getElementsByClassName("exclusionRuleInstance");
    part = (part === true);
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      element = _ref[_i];
      if (part && element.style.display === "none") {
        continue;
      }
      pattern = this.getPattern(element).value.trim();
      if (!pattern) {
        continue;
      }
      passKeys = this.getPassKeys(element).value.replace(wchRegex, " ").trim();
      rules.push({
        pattern: pattern,
        passKeys: passKeys
      });
    }
    if (JSON.stringify(rules) === JSON.stringify(bgSettings.defaults[this.field])) {
      rules = bgSettings.defaults[this.field];
    }
    return rules;
  };

  ExclusionRulesOption.prototype.flatten = function(rule) {
    return (rule && rule.pattern) ? (rule.pattern + "\r" + rule.passKeys) : "";
  };

  ExclusionRulesOption.prototype.areEqual = function(a, b) {
    return a.map(this.flatten).join("\n") === b.map(this.flatten).join("\n");
  };

  ExclusionRulesOption.prototype.getPattern = function(element) {
    return element.querySelector(".pattern");
  };

  ExclusionRulesOption.prototype.getPassKeys = function(element) {
    return element.querySelector(".passKeys");
  };

  ExclusionRulesOption.prototype.getRemoveButton = function(element) {
    return element.querySelector(".exclusionRemoveButton");
  };

  return ExclusionRulesOption;

})(Option);

ExclusionRulesOnPopupOption = (function(_super) {
  __extends(ExclusionRulesOnPopupOption, _super);

  function ExclusionRulesOnPopupOption(url) {
    this.url = url;
    this.onInput = this.onInput.bind(this);
    ExclusionRulesOnPopupOption.__super__.constructor.apply(this,
      2 <= arguments.length ? Array.prototype.slice.call(arguments, 1) : []);
  }

  ExclusionRulesOnPopupOption.prototype.addRule = function() {
    var element = ExclusionRulesOnPopupOption.__super__.addRule.call(this,
      this.generateDefaultPattern());
    this.activatePatternWatcher(element);
    this.getPassKeys(element).focus();
    return element;
  };

  ExclusionRulesOnPopupOption.prototype.populateElement = function(rules) {
    var element, elements, haveMatch, pattern, _i, _len;
    ExclusionRulesOnPopupOption.__super__.populateElement.call(this, rules);
    elements = this.element.getElementsByClassName("exclusionRuleInstance");
    haveMatch = -1;
    for (_i = 0, _len = elements.length; _i < _len; _i++) {
      element = elements[_i];
      this.activatePatternWatcher(element);
      pattern = this.getPattern(element).value.trim();
      if (bgExclusions.re[pattern](this.url)) {
        haveMatch = _i;
      } else {
        element.style.display = "none";
      }
    }
    if (haveMatch >= 0) {
      this.getPassKeys(elements[haveMatch]).focus();
    } else {
      this.addRule();
    }
  };

  ExclusionRulesOnPopupOption.prototype.activatePatternWatcher = function(element) {
    this.getPattern(element).addEventListener("input", this.onInput);
  };

  ExclusionRulesOnPopupOption.prototype.onInput = function(event) {
    var patternElement = event.target;
    if (bgExclusions.getRegex(patternElement.value)(this.url)) {
      patternElement.title = patternElement.style.color = "";
    } else {
      patternElement.style.color = "red";
      patternElement.title = "Red text means that the pattern does not\nmatch the current URL.";
    }
  }

  ExclusionRulesOnPopupOption.prototype.httpRegex = /^https?:\/\/./;
  ExclusionRulesOnPopupOption.prototype.urlRegex = /^[a-z]{3,}:\/\/./;
  ExclusionRulesOnPopupOption.prototype.generateDefaultPattern = function() {
    return this.httpRegex.test(this.url)
      ? ("https?://" + this.url.split("/", 3)[2] + "/")
      : this.urlRegex.test(this.url)
      ? (this.url.split("/", 3).join("/") + "/")
      : this.url;
  };

  return ExclusionRulesOnPopupOption;

})(ExclusionRulesOption);

initPopupPage = function(tab) {
  var exclusions, onUpdated, saveOptions, updateState, url, hasNew, status;
  exclusions = null;
  tab = tab[0];
  url = bgSettings.urlForTab[tab.id] || tab.url;
  hasNew = false;
  var escapeRegex = /[&<>]/g, escapeCallback = function(c, n) {
    n = c.charCodeAt(0);
    return (n === 60) ? "<" : (n === 62) ? ">" : "&";
  },
  updateState = function() {
    var pass = bgExclusions.getTemp(url, exclusions.readValueFromElement(true));
    $("state").innerHTML = "Vimium++ will " + (pass
      ? "exclude: " + pass.replace(escapeRegex, escapeCallback) + ""
      : pass !== null ? "be disabled" : "be enabled");
  };
  onUpdated = function() {
    if (status != 1) {
      status = 1;
      var btn = $("saveOptions");
      $("helpText").innerHTML = "Type Ctrl-Enter to save and close.";
      btn.removeAttribute("disabled");
      btn.innerHTML = "Save Changes";
    }
    if (exclusions) {
      hasNew = true;
      updateState();
    }
  };
  saveOptions = function() {
    var btn = $("saveOptions");
    if (btn.disabled) {
      return;
    }
    exclusions.save();
    hasNew = false;
    btn.innerHTML = "Saved";
    btn.disabled = true;
    status = 0;
    // here, since the popup page is showing, needIcon must be true
    // although the tab calls window.onfocus after this popup page closes,
    // it is too early for the tab to know new exclusion rules.
    BG.g_requestHandlers.setIcon(tab.id, null, bgExclusions.getPattern(url));
  };
  $("saveOptions").onclick = saveOptions;
  document.addEventListener("keyup", function(event) {
    if (event.ctrlKey && event.keyCode === 13) {
      saveOptions();
      setTimeout(window.close, 300);
    }
  });
  exclusions = new ExclusionRulesOnPopupOption(url, "exclusionRules", onUpdated);
  updateState();
  $("optionsLink").href = bgSettings.CONST.OptionsPage;
  window.onunload = function() {
    if (hasNew) {
      bgExclusions.rebuildRegex();
    }
  }
};

var onDOMLoaded;
document.addEventListener("DOMContentLoaded", onDOMLoaded = function() {
  document.removeEventListener("DOMContentLoaded", onDOMLoaded);
  onDOMLoaded = null;
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "exclusions.html", true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      $("exclusionScrollBox").innerHTML = xhr.responseText;
      if (window.location.pathname.endsWith("popup.html")) {
        chrome.tabs.query({currentWindow: true, active: true}, initPopupPage);
      } else if (location.pathname.endsWith("options.html") >= 0) {
        initOptionsPage();
      }
    }
  };
  xhr.send();
});
<commit_msg>exclusionRules: add event listeners to @element<commit_after>"use strict";
var $, Option, ExclusionRulesOption, ExclusionRulesOnPopupOption,
bgSettings, bgExclusions, initPopupPage, BG,
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) {
  for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
  function ctor() { this.constructor = child; } ctor.prototype = parent.prototype;
  child.prototype = new ctor(); child.__super__ = parent.prototype;
  return child;
};

$ = document.getElementById.bind(document);
BG = chrome.extension.getBackgroundPage();
bgSettings = BG.Settings;
bgExclusions = BG.Exclusions;

function Option(field, onUpdated) {
  this.field = field;
  this.element = $(this.field);
  this.onUpdated = onUpdated;
  this.previous = null;
  this.fetch();
  Option.all.push(this);
}

Option.all = [];
Option.syncToFrontend = false;

Option.prototype.fetch = function() {
  this.populateElement(this.previous = bgSettings.get(this.field));
};

Option.prototype.save = function() {
  var value = this.readValueFromElement();
  if (!this.areEqual(value, this.previous)) {
    bgSettings.set(this.field, this.previous = value);
    if (this.field in bgSettings.bufferToLoad) {
      Option.syncToFrontend = true;
    }
  }
};

Option.prototype.areEqual = function(a, b) {
  return a === b;
};

ExclusionRulesOption = (function(_super) {
  __extends(ExclusionRulesOption, _super);

  function ExclusionRulesOption() {
    this.template = $('exclusionRuleTemplate').content.children[0];
    ExclusionRulesOption.__super__.constructor.apply(this, arguments);
    $("exclusionAddButton").addEventListener("click", this.addRule.bind(this, null));
    this.element.addEventListener("input", this.onUpdated);
    this.element.addEventListener("click", this.onRemoveRow.bind(this));
  }

  ExclusionRulesOption.prototype.addRule = function(pattern) {
    var element, exclusionScrollBox;
    element = this.appendRule(this.element, {
      pattern: pattern || "",
      passKeys: ""
    });
    this.getPattern(element).focus();
    exclusionScrollBox = this.element.parentElement;
    exclusionScrollBox.scrollTop = exclusionScrollBox.scrollHeight;
    if (pattern) {
      this.onUpdated();
    }
    return element;
  };

  ExclusionRulesOption.prototype.populateElement = function(rules) {
    var frag = document.createDocumentFragment(), head;
    head = this.element.querySelector('tr');
    frag.appendChild(head);
    this.element.innerHTML = "";
    rules.forEach(this.appendRule.bind(this, frag));
    this.element.appendChild(frag);
  };

  ExclusionRulesOption.prototype.appendRule = function(list, rule) {
    var row;
    row = document.importNode(this.template, true);
    row.querySelector('.pattern').value = rule.pattern;
    row.querySelector('.passKeys').value = rule.passKeys;
    list.appendChild(row);
    return row;
  };

  ExclusionRulesOption.prototype.onRemoveRow = function(event) {
    var row1 = event.target.parentNode.parentNode;
    if (! row1.classList.contains("exclusionRuleInstance")) {
      return;
    }
    row1.parentNode.removeChild(row1);
    this.onUpdated();
  };

  ExclusionRulesOption.prototype.readValueFromElement = function(part) {
    var element, passKeys, pattern, rules, _i, _len, _ref, wchRegex;
    rules = [];
    wchRegex = /\s+/;
    _ref = this.element.getElementsByClassName("exclusionRuleInstance");
    part = (part === true);
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      element = _ref[_i];
      if (part && element.style.display === "none") {
        continue;
      }
      pattern = this.getPattern(element).value.trim();
      if (!pattern) {
        continue;
      }
      passKeys = this.getPassKeys(element).value.replace(wchRegex, " ").trim();
      rules.push({
        pattern: pattern,
        passKeys: passKeys
      });
    }
    if (JSON.stringify(rules) === JSON.stringify(bgSettings.defaults[this.field])) {
      rules = bgSettings.defaults[this.field];
    }
    return rules;
  };

  ExclusionRulesOption.prototype.flatten = function(rule) {
    return (rule && rule.pattern) ? (rule.pattern + "\r" + rule.passKeys) : "";
  };

  ExclusionRulesOption.prototype.areEqual = function(a, b) {
    return a.map(this.flatten).join("\n") === b.map(this.flatten).join("\n");
  };

  ExclusionRulesOption.prototype.getPattern = function(element) {
    return element.querySelector(".pattern");
  };

  ExclusionRulesOption.prototype.getPassKeys = function(element) {
    return element.querySelector(".passKeys");
  };

  ExclusionRulesOption.prototype.getRemoveButton = function(element) {
    return element.querySelector(".exclusionRemoveButton");
  };

  return ExclusionRulesOption;

})(Option);

ExclusionRulesOnPopupOption = (function(_super) {
  __extends(ExclusionRulesOnPopupOption, _super);

  function ExclusionRulesOnPopupOption(url) {
    this.url = url;
    ExclusionRulesOnPopupOption.__super__.constructor.apply(this,
      2 <= arguments.length ? Array.prototype.slice.call(arguments, 1) : []);
    this.element.addEventListener("input", this.onInput.bind(this));
  }

  ExclusionRulesOnPopupOption.prototype.addRule = function() {
    ExclusionRulesOnPopupOption.__super__.addRule.call(this, this.generateDefaultPattern());
  };

  ExclusionRulesOnPopupOption.prototype.populateElement = function(rules) {
    var element, elements, haveMatch, pattern, _i, _len;
    ExclusionRulesOnPopupOption.__super__.populateElement.call(this, rules);
    elements = this.element.getElementsByClassName("exclusionRuleInstance");
    haveMatch = -1;
    for (_i = 0, _len = elements.length; _i < _len; _i++) {
      element = elements[_i];
      pattern = this.getPattern(element).value.trim();
      if (bgExclusions.re[pattern](this.url)) {
        haveMatch = _i;
      } else {
        element.style.display = "none";
      }
    }
    if (haveMatch >= 0) {
      this.getPassKeys(elements[haveMatch]).focus();
    } else {
      this.addRule();
    }
  };

  ExclusionRulesOnPopupOption.prototype.onInput = function(event) {
    var patternElement = event.target;
    if (!patternElement.classList.contains("pattern")) {
      return;
    }
    if (bgExclusions.getRegex(patternElement.value)(this.url)) {
      patternElement.title = patternElement.style.color = "";
    } else {
      patternElement.style.color = "red";
      patternElement.title = "Red text means that the pattern does not\nmatch the current URL.";
    }
  }

  ExclusionRulesOnPopupOption.prototype.httpRegex = /^https?:\/\/./;
  ExclusionRulesOnPopupOption.prototype.urlRegex = /^[a-z]{3,}:\/\/./;
  ExclusionRulesOnPopupOption.prototype.generateDefaultPattern = function() {
    return this.httpRegex.test(this.url)
      ? ("https?://" + this.url.split("/", 3)[2] + "/")
      : this.urlRegex.test(this.url)
      ? (this.url.split("/", 3).join("/") + "/")
      : this.url;
  };

  return ExclusionRulesOnPopupOption;

})(ExclusionRulesOption);

initPopupPage = function(tab) {
  var exclusions, onUpdated, saveOptions, updateState, url, hasNew, status;
  exclusions = null;
  tab = tab[0];
  url = bgSettings.urlForTab[tab.id] || tab.url;
  hasNew = false;
  var escapeRegex = /[&<>]/g, escapeCallback = function(c, n) {
    n = c.charCodeAt(0);
    return (n === 60) ? "<" : (n === 62) ? ">" : "&";
  },
  updateState = function() {
    var pass = bgExclusions.getTemp(url, exclusions.readValueFromElement(true));
    $("state").innerHTML = "Vimium++ will " + (pass
      ? "exclude: " + pass.replace(escapeRegex, escapeCallback) + ""
      : pass !== null ? "be disabled" : "be enabled");
  };
  onUpdated = function() {
    if (status != 1) {
      status = 1;
      var btn = $("saveOptions");
      $("helpText").innerHTML = "Type Ctrl-Enter to save and close.";
      btn.removeAttribute("disabled");
      btn.innerHTML = "Save Changes";
    }
    if (exclusions) {
      hasNew = true;
      updateState();
    }
  };
  saveOptions = function() {
    var btn = $("saveOptions");
    if (btn.disabled) {
      return;
    }
    exclusions.save();
    hasNew = false;
    btn.innerHTML = "Saved";
    btn.disabled = true;
    status = 0;
    // here, since the popup page is showing, needIcon must be true
    // although the tab calls window.onfocus after this popup page closes,
    // it is too early for the tab to know new exclusion rules.
    BG.g_requestHandlers.setIcon(tab.id, null, bgExclusions.getPattern(url));
  };
  $("saveOptions").onclick = saveOptions;
  document.addEventListener("keyup", function(event) {
    if (event.ctrlKey && event.keyCode === 13) {
      saveOptions();
      setTimeout(window.close, 300);
    }
  });
  exclusions = new ExclusionRulesOnPopupOption(url, "exclusionRules", onUpdated);
  updateState();
  $("optionsLink").href = bgSettings.CONST.OptionsPage;
  window.onunload = function() {
    if (hasNew) {
      bgExclusions.rebuildRegex();
    }
  }
};

var onDOMLoaded;
document.addEventListener("DOMContentLoaded", onDOMLoaded = function() {
  document.removeEventListener("DOMContentLoaded", onDOMLoaded);
  onDOMLoaded = null;
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "exclusions.html", true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      $("exclusionScrollBox").innerHTML = xhr.responseText;
      if (window.location.pathname.endsWith("popup.html")) {
        chrome.tabs.query({currentWindow: true, active: true}, initPopupPage);
      } else if (location.pathname.endsWith("options.html") >= 0) {
        initOptionsPage();
      }
    }
  };
  xhr.send();
});
<|endoftext|>