<commit_before>/**
 * Calculates the specificity of CSS selectors
 * http://www.w3.org/TR/css3-selectors/#specificity
 *
 * Returns an array of objects with the following properties:
 *  - selector: the input
 *  - specificity: e.g. 0,1,0,0
 *  - parts: array with details about each part of the selector that counts towards the specificity
 */
var SPECIFICITY = (function() {
	var calculate,
		calculateSingle;

	calculate = function(input) {
		var selectors,
			selector,
			i,
			len,
			results = [];

		// Separate input by commas
		selectors = input.split(',');

		for (i = 0, len = selectors.length; i < len; i += 1) {
			selector = selectors[i];
			if (selector.length > 0) {
				results.push(calculateSingle(selector));
			}
		}

		return results;
	};

	// Calculate the specificity for a selector by dividing it into simple selectors and counting them
	calculateSingle = function(input) {
		var selector = input,
			findMatch,
			typeCount = {
				'a': 0,
				'b': 0,
				'c': 0
			},
			parts = [],
			// The following regular expressions assume that selectors matching the preceding regular expressions have been removed
			attributeRegex = /(\[[^\]]+\])/g,
			idRegex = /(#[^\s\+>~\.\[:]+)/g,
			classRegex = /(\.[^\s\+>~\.\[:]+)/g,
			pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/g,
			pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g,
			elementRegex = /([^\s\+>~\.\[:]+)/g;

		// Find matches for a regular expression in a string and push their details to parts
		// Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
		findMatch = function(regex, type) {
			var matches, i, len, match, index, length;
			if (regex.test(selector)) {
				matches = selector.match(regex);
				for (i = 0, len = matches.length; i < len; i += 1) {
					typeCount[type] += 1;
					match = matches[i];
					index = selector.indexOf(match);
					length = match.length;
					parts.push({
						selector: match,
						type: type,
						index: index,
						length: length
					});
					// Replace this simple selector with whitespace so it won't be counted in further simple selectors
					selector = selector.replace(match, Array(length + 1).join(' '));
				}
			}
		};

		// Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
		(function() {
			var regex = /:not\(([^\)]*)\)/g;
			if (regex.test(selector)) {
				selector = selector.replace(regex, '     $1 ');
			}
		}());

		// Remove anything after a left brace in case a user has pasted in a rule, not just a selector
		(function() {
			var regex = /{.*/gm,
				matches, i, len, match;
			if (regex.test(selector)) {
				matches = selector.match(regex);
				for (i = 0, len = matches.length; i < len; i += 1) {
					match = matches[i];
					selector = selector.replace(match, Array(match.length + 1).join(' '));
				}
			}
		}());

		// Add attribute selectors to parts collection (type b)
		findMatch(attributeRegex, 'b');

		// Add ID selectors to parts collection (type a)
		findMatch(idRegex, 'a');

		// Add class selectors to parts collection (type b)
		findMatch(classRegex, 'b');

		// Add pseudo-element selectors to parts collection (type c)
		findMatch(pseudoElementRegex, 'c');

		// Add pseudo-class selectors to parts collection (type b)
		findMatch(pseudoClassRegex, 'b');

		// Remove universal selector and separator characters
		selector = selector.replace(/[\*\s\+>~]/g, ' ');

		// Remove any stray dots or hashes which aren't attached to words
		// These may be present if the user is live-editing this selector
		selector = selector.replace(/[#\.]/g, ' ');

		// The only things left should be element selectors (type c)
		findMatch(elementRegex, 'c');

		// Order the parts in the order they appear in the original selector
		// This is neater for external apps to deal with
		parts.sort(function(a, b) {
			return a.index - b.index;
		});

		return {
			selector: input,
			specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(),
			parts: parts
		};
	};

	return {
		calculate: calculate
	};
}());

// Export for Node JS
if (typeof exports !== 'undefined') {
	exports.calculate = SPECIFICITY.calculate;
}
<commit_msg>Corrected regex for anything after left brace so that it is multiline<commit_after>/**
 * Calculates the specificity of CSS selectors
 * http://www.w3.org/TR/css3-selectors/#specificity
 *
 * Returns an array of objects with the following properties:
 *  - selector: the input
 *  - specificity: e.g. 0,1,0,0
 *  - parts: array with details about each part of the selector that counts towards the specificity
 */
var SPECIFICITY = (function() {
	var calculate,
		calculateSingle;

	calculate = function(input) {
		var selectors,
			selector,
			i,
			len,
			results = [];

		// Separate input by commas
		selectors = input.split(',');

		for (i = 0, len = selectors.length; i < len; i += 1) {
			selector = selectors[i];
			if (selector.length > 0) {
				results.push(calculateSingle(selector));
			}
		}

		return results;
	};

	// Calculate the specificity for a selector by dividing it into simple selectors and counting them
	calculateSingle = function(input) {
		var selector = input,
			findMatch,
			typeCount = {
				'a': 0,
				'b': 0,
				'c': 0
			},
			parts = [],
			// The following regular expressions assume that selectors matching the preceding regular expressions have been removed
			attributeRegex = /(\[[^\]]+\])/g,
			idRegex = /(#[^\s\+>~\.\[:]+)/g,
			classRegex = /(\.[^\s\+>~\.\[:]+)/g,
			pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/g,
			pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g,
			elementRegex = /([^\s\+>~\.\[:]+)/g;

		// Find matches for a regular expression in a string and push their details to parts
		// Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
		findMatch = function(regex, type) {
			var matches, i, len, match, index, length;
			if (regex.test(selector)) {
				matches = selector.match(regex);
				for (i = 0, len = matches.length; i < len; i += 1) {
					typeCount[type] += 1;
					match = matches[i];
					index = selector.indexOf(match);
					length = match.length;
					parts.push({
						selector: match,
						type: type,
						index: index,
						length: length
					});
					// Replace this simple selector with whitespace so it won't be counted in further simple selectors
					selector = selector.replace(match, Array(length + 1).join(' '));
				}
			}
		};

		// Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
		(function() {
			var regex = /:not\(([^\)]*)\)/g;
			if (regex.test(selector)) {
				selector = selector.replace(regex, '     $1 ');
			}
		}());

		// Remove anything after a left brace in case a user has pasted in a rule, not just a selector
		(function() {
			var regex = /{[^]*/gm,
				matches, i, len, match;
			if (regex.test(selector)) {
				matches = selector.match(regex);
				for (i = 0, len = matches.length; i < len; i += 1) {
					match = matches[i];
					selector = selector.replace(match, Array(match.length + 1).join(' '));
				}
			}
		}());

		// Add attribute selectors to parts collection (type b)
		findMatch(attributeRegex, 'b');

		// Add ID selectors to parts collection (type a)
		findMatch(idRegex, 'a');

		// Add class selectors to parts collection (type b)
		findMatch(classRegex, 'b');

		// Add pseudo-element selectors to parts collection (type c)
		findMatch(pseudoElementRegex, 'c');

		// Add pseudo-class selectors to parts collection (type b)
		findMatch(pseudoClassRegex, 'b');

		// Remove universal selector and separator characters
		selector = selector.replace(/[\*\s\+>~]/g, ' ');

		// Remove any stray dots or hashes which aren't attached to words
		// These may be present if the user is live-editing this selector
		selector = selector.replace(/[#\.]/g, ' ');

		// The only things left should be element selectors (type c)
		findMatch(elementRegex, 'c');

		// Order the parts in the order they appear in the original selector
		// This is neater for external apps to deal with
		parts.sort(function(a, b) {
			return a.index - b.index;
		});

		return {
			selector: input,
			specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(),
			parts: parts
		};
	};

	return {
		calculate: calculate
	};
}());

// Export for Node JS
if (typeof exports !== 'undefined') {
	exports.calculate = SPECIFICITY.calculate;
}
<|endoftext|>


<commit_before>"use strict";

var dummy
  , hotplate =  require('hotplate')
  , path = require('path')
;

hotplate.hotEvents.on('pageElements', 'hotDojoWidgets', function( done ){
  done( null, {
    csses: [ 'AlertBar.css', 'EditingWidget.css','_OverlayMixin.css' ],
  });
});

// Simply activate path to client files
hotplate.hotEvents.on( 'clientPath', 'hotDojoWidgets', function( done ){
  done( null, path.join(__dirname, '../client') );
})

<commit_msg>Unsuccesfully tried to add caching of images<commit_after>"use strict";

var dummy
  , hotplate =  require('hotplate')
  , path = require('path')
;

hotplate.hotEvents.on('pageElements', 'hotDojoWidgets', function( done ){
  done( null, {
    csses: [ 'AlertBar.css', 'EditingWidget.css','_OverlayMixin.css' ],
  });
});

// Simply activate path to client files
hotplate.hotEvents.on( 'clientPath', 'hotDojoWidgets', function( done ){
  done( null, path.join(__dirname, '../client') );
})

hotplate.hotEvents.on( 'pageElements', 'hotDojoWidgets', hotplate.cachable( function( done ){

  var imgPath = path.join( hotplate.config.get( 'hotplate.moduleFilesPrefix'), 'hotDojoWidgets', 'reload.png' );

  var headLines = [
    ''
  ];

  done( null, { headLines: headLines } );

}));

<|endoftext|>


<commit_before>MWF.xDesktop.requireApp("process.Xform", "$Module", null, false);

/** @class ReadLog 阅读记录组件。
 * @example
 * //可以在脚本中获取该组件
 * //方法1：
 * var log = this.form.get("name"); //获取组件
 * //方法2
 * var log = this.target; //在组件事件脚本中获取
 * @extends MWF.xApplication.process.Xform.$Module
 * @o2category FormComponents
 * @o2range {Process}
 * @hideconstructor
 */
MWF.xApplication.process.Xform.ReadLog = MWF.APPReadLog =  new Class(
    /** @lends MWF.xApplication.process.Xform.ReadLog# */
    {
        Extends: MWF.APP$Module,
        options: {
            /**
             * 加载数据后事件。
             * @event MWF.xApplication.process.Xform.Log#postLoadData
             * @see {@link https://www.yuque.com/o2oa/ixsnyt/hm5uft#i0zTS|组件事件说明}
             * @example
             * //触发该事件的时候可以获取到流程数据workLog
             * var workLog = this.target.workLog;
             * //可以修改workLog达到定制化流程记录的效果
             * do something
             */
            /**
             * 加载每行流程信息以后触发，可以通过this.event获得下列信息：
             *

             {
            "data" : {}, //当前行流程信息
            "node" : logTaskNode, //当前节点
            "log" : object, //指向流程记录
            "type" : "task"  //"task"表示待办，"taskCompleted"表示已办
        }

             * @event MWF.xApplication.process.Xform.Log#postLoadLine
             * @see {@link https://www.yuque.com/o2oa/ixsnyt/hm5uft#i0zTS|组件事件说明}
             */
            "moduleEvents": ["load", "queryLoad", "postLoad", "postLoadData", "postLoadLine"]
        },
        load: function(){
            this.node.empty();
            if (!this.json.isDelay){
                this.active();
            }
        },
        /**
         * 激活阅读记录。设置了延迟加载的时候，可以通过这个方法来激活
         * @example
         * this.form.get("fieldId").active();
         */
        active: function(){
            this._loadModuleEvents();
            if (this.fireEvent("queryLoad")){
                this._queryLoaded();
                this._loadUserInterface();
                this._loadStyles();
                this._loadDomEvents();
                //this._loadEvents();

                this._afterLoaded();
                this.fireEvent("postLoad");
                this.fireEvent("load");
            }
        },
        _loadUserInterface: function(){
            this.node.setStyle("-webkit-user-select", "text");
            this.node.setStyles(this.form.css.logActivityNode_record);

            this.form.app.action.getReadRecord(this.form.businessData.work.id, function(json){
                this.readLog = json.data;
                this.fireEvent("postLoadData");
                this.loadReadLog();
            }.bind(this));
        },
        loadReadLog: function(){
            switch (this.json.mode){
                case "notShow":
                    return "";
                    break;
                case "script":
                    this.loadReadLogByScript();
                    break;
                default:
                    this.loadReadLogDefault();
            }
        },
        loadReadLogByScript: function(){
            if (this.json.displayScript && this.json.displayScript.code){
                var code = this.json.displayScript.code
                this.readLog.each(function(log){
                    this.form.Macro.environment.log = log;
                    this.form.Macro.environment.list = null;
                    var r = this.form.Macro.exec(code, this);

                    var t = o2.typeOf(r);
                    if (t==="string"){
                        this.node.appendHTML(r);
                    }else if (t==="element"){
                        this.node.appendChild(r);
                    }
                }.bind(this));
            }
        },
        loadReadLogDefault: function(){
            var text = this.json.textStyle;
            var readPersons = [];
            this.lineClass = "logTaskNode";
            this.readLog.each(function(log, i){
                if (log.type == "readCompleted"){
                    var div = new Element("div", {styles: this.form.css[this.lineClass]}).inject(this.node);
                    var leftDiv = new Element("div", {styles: this.form.css.logTaskIconNode_record}).inject(div);
                    var rightDiv = new Element("div", {styles: this.form.css.logTaskTextNode}).inject(div);
                    var html = text.replace(/{person}/g, o2.name.cn(log.person))
                        .replace(/{datetime}/g, log.completedTime)
                        .replace(/{date}/g, log.completedTime.substring(0.10))
                        .replace(/{startDatetime}/g, log.startTime)
                        .replace(/{startDate}/g, log.startTime.substring(0.10))
                        .replace(/{startDatetime}/g, log.startTime)
                        .replace(/{unit}/g, o2.name.cn(log.unit))
                        .replace(/{department}/g, o2.name.cn(log.unit))
                        .replace(/{identity}/g, o2.name.cn(log.identity))
                        .replace(/{activity}/g, o2.name.cn(log.activityName))
                        .replace(/{title}/g, log.title)
                        .replace(/{opinion}/g, log.opinion);
                    rightDiv.appendHTML(html);

                    if (this.lineClass === "logTaskNode"){
                        this.lineClass = "logTaskNode_even";
                    }else{
                        this.lineClass = "logTaskNode";
                    }
                }
                if (!!this.json.isShowRead && log.type == "read"){
                    readPersons.push(o2.name.cn(log.person)+"("+o2.name.cn(log.unit)+")");
                }
            }.bind(this));
            if (readPersons.length){
                var div = new Element("div", {styles: this.form.css[this.lineClass]}).inject(this.node);
                var leftDiv = new Element("div", {styles: this.form.css.logTaskIconNode_record}).inject(div);
                var rightDiv = new Element("div", {styles: this.form.css.logTaskTextNode}).inject(div);
                leftDiv.setStyle("background-image", "url("+"../x_component_process_Xform/$Form/"+this.form.options.style+"/icon/rightRed.png)");
                rightDiv.appendHTML("
"+MWF.xApplication.process.Xform.LP.showReadTitle+": "+readPersons.join(", ")+"
");
            }
        }
    });
<commit_msg>修复阅读记录未填写意见出现undefined的问题<commit_after>MWF.xDesktop.requireApp("process.Xform", "$Module", null, false);

/** @class ReadLog 阅读记录组件。
 * @example
 * //可以在脚本中获取该组件
 * //方法1：
 * var log = this.form.get("name"); //获取组件
 * //方法2
 * var log = this.target; //在组件事件脚本中获取
 * @extends MWF.xApplication.process.Xform.$Module
 * @o2category FormComponents
 * @o2range {Process}
 * @hideconstructor
 */
MWF.xApplication.process.Xform.ReadLog = MWF.APPReadLog =  new Class(
    /** @lends MWF.xApplication.process.Xform.ReadLog# */
    {
        Extends: MWF.APP$Module,
        options: {
            /**
             * 加载数据后事件。
             * @event MWF.xApplication.process.Xform.Log#postLoadData
             * @see {@link https://www.yuque.com/o2oa/ixsnyt/hm5uft#i0zTS|组件事件说明}
             * @example
             * //触发该事件的时候可以获取到流程数据workLog
             * var readLog = this.target.readLog;
             * //可以修改readLog达到定制化流程记录的效果
             * do something
             */
            "moduleEvents": ["load", "queryLoad", "postLoad", "postLoadData", "postLoadLine"]
        },
        load: function(){
            this.node.empty();
            if (!this.json.isDelay){
                this.active();
            }
        },
        /**
         * 激活阅读记录。设置了延迟加载的时候，可以通过这个方法来激活
         * @example
         * this.form.get("fieldId").active();
         */
        active: function(){
            this._loadModuleEvents();
            if (this.fireEvent("queryLoad")){
                this._queryLoaded();
                this._loadUserInterface();
                this._loadStyles();
                this._loadDomEvents();
                //this._loadEvents();

                this._afterLoaded();
                this.fireEvent("postLoad");
                this.fireEvent("load");
            }
        },
        _loadUserInterface: function(){
            this.node.setStyle("-webkit-user-select", "text");
            this.node.setStyles(this.form.css.logActivityNode_record);

            this.form.app.action.getReadRecord(this.form.businessData.work.id, function(json){
                this.readLog = json.data;
                this.fireEvent("postLoadData");
                this.loadReadLog();
            }.bind(this));
        },
        loadReadLog: function(){
            switch (this.json.mode){
                case "notShow":
                    return "";
                    break;
                case "script":
                    this.loadReadLogByScript();
                    break;
                default:
                    this.loadReadLogDefault();
            }
        },
        loadReadLogByScript: function(){
            if (this.json.displayScript && this.json.displayScript.code){
                var code = this.json.displayScript.code;
                this.readLog.each(function(log){
                    this.form.Macro.environment.log = log;
                    this.form.Macro.environment.list = null;
                    var r = this.form.Macro.exec(code, this);

                    var t = o2.typeOf(r);
                    if (t==="string"){
                        this.node.appendHTML(r);
                    }else if (t==="element"){
                        this.node.appendChild(r);
                    }
                }.bind(this));
            }
        },
        loadReadLogDefault: function(){
            var text = this.json.textStyle;
            var readPersons = [];
            this.lineClass = "logTaskNode";
            this.readLog.each(function(log, i){
                if (log.type == "readCompleted"){
                    var div = new Element("div", {styles: this.form.css[this.lineClass]}).inject(this.node);
                    var leftDiv = new Element("div", {styles: this.form.css.logTaskIconNode_record}).inject(div);
                    var rightDiv = new Element("div", {styles: this.form.css.logTaskTextNode}).inject(div);
                    var html = text.replace(/{person}/g, o2.name.cn(log.person || ""))
                        .replace(/{datetime}/g, log.completedTime)
                        .replace(/{date}/g, log.completedTime.substring(0.10))
                        .replace(/{startDatetime}/g, log.startTime)
                        .replace(/{startDate}/g, log.startTime.substring(0.10))
                        .replace(/{startDatetime}/g, log.startTime)
                        .replace(/{unit}/g, o2.name.cn(log.unit))
                        .replace(/{department}/g, o2.name.cn(log.unit))
                        .replace(/{identity}/g, o2.name.cn(log.identity))
                        .replace(/{activity}/g, o2.name.cn(log.activityName))
                        .replace(/{title}/g, log.title || "")
                        .replace(/{opinion}/g, log.opinion || "");
                    rightDiv.appendHTML(html);

                    if (this.lineClass === "logTaskNode"){
                        this.lineClass = "logTaskNode_even";
                    }else{
                        this.lineClass = "logTaskNode";
                    }
                }
                if (!!this.json.isShowRead && log.type == "read"){
                    readPersons.push(o2.name.cn(log.person)+"("+o2.name.cn(log.unit)+")");
                }
            }.bind(this));
            if (readPersons.length){
                var div = new Element("div", {styles: this.form.css[this.lineClass]}).inject(this.node);
                var leftDiv = new Element("div", {styles: this.form.css.logTaskIconNode_record}).inject(div);
                var rightDiv = new Element("div", {styles: this.form.css.logTaskTextNode}).inject(div);
                leftDiv.setStyle("background-image", "url("+"../x_component_process_Xform/$Form/"+this.form.options.style+"/icon/rightRed.png)");
                rightDiv.appendHTML("
"+MWF.xApplication.process.Xform.LP.showReadTitle+": "+readPersons.join(", ")+"
");
            }
        }
    });
<|endoftext|>


<commit_before>$('select').select2();<commit_msg>Fix: Movido javascript de modales a Resources<commit_after>$( document ).ready(function() {

    $('select').select2();

    $('#DeleteModal').on('show.bs.modal', function (event) {
        var button = $(event.relatedTarget) // Button that triggered the modal
        var url = button.data('url') // Extract info from data-* attributes
        // If necessary, you could initiate an AJAX request here (and then do the updating in a callback).
        // Update the modal's content. We'll use jQuery here, but you could use a data binding library or other methods instead.
        var modal = $(this)
        modal.find('#delete').attr('href',url)
    })

    $('#ShareModal').on('show.bs.modal', function (event) {
        var button = $(event.relatedTarget) // Button that triggered the modal
        var url = button.data('url') // Extract info from data-* attributes
        // If necessary, you could initiate an AJAX request here (and then do the updating in a callback).
        // Update the modal's content. We'll use jQuery here, but you could use a data binding library or other methods instead.
        var modal = $(this)
        modal.find('#share').attr('value', url)
    })
});
<|endoftext|>


<commit_before>/* Loads a URL then starts looking for links.
  Emits a full page whenever a new link is found. */
import url from 'url'
import snapshot from './snapshot'
import jsdom from 'jsdom'

export default class Crawler {
  constructor(baseUrl) {
    this.baseUrl = baseUrl
    const { protocol, host, path } = url.parse(baseUrl)
    this.protocol = protocol
    this.host = host
    this.paths = [path]
    this.processed = {}
  }

  crawl(handler) {
    this.handler = handler
    console.log(`🕷   Starting crawling ${this.baseUrl}`)
    return this.snap()
      .then(() => console.log(`🕸   Finished crawling.`))
  }

  snap() {
    let path = this.paths.shift()
    if (!path) return Promise.resolve()
    if (this.processed[path]) {
      return this.snap()
    } else {
      this.processed[path] = true
    }
    return snapshot(this.protocol, this.host, path).then(window => {
      const html = jsdom.serializeDocument(window.document)
      this.extractNewLinks(window, path)
      this.handler({ path, html })
      return this.snap()
    }, err => {
      console.log(err)
    })
  }

  extractNewLinks(window, currentPath) {
    const document = window.document
    const tagAttributeMap = {
      'a': 'href',
      'iframe': 'src'
    }

    Object.keys(tagAttributeMap).forEach(tagName => {
      const urlAttribute = tagAttributeMap[tagName]
      Array.from(document.querySelectorAll(`${tagName}[${urlAttribute}]`)).forEach(element => {
        const { protocol, host, path } = url.parse(element.getAttribute(urlAttribute))
        if (protocol || host) return
        const relativePath = url.resolve(currentPath, path)
        if (!this.processed[relativePath]) this.paths.push(relativePath)
      })
    })
  }
}
<commit_msg>attempt to read paths from package.json<commit_after>/* Loads a URL then starts looking for links.
  Emits a full page whenever a new link is found. */
import url from 'url'
import snapshot from './snapshot'
import jsdom from 'jsdom'
import path from 'path'

const pkg = require(path.join(process.cwd(), 'package.json'));
const paths = pkg.reactSnapshot.paths

export default class Crawler {
  constructor(baseUrl) {
    this.baseUrl = baseUrl
    const { protocol, host, path } = url.parse(baseUrl)
    this.protocol = protocol
    this.host = host
    this.paths = [path,...paths]
    this.processed = {}
  }

  crawl(handler) {
    this.handler = handler
    console.log(`🕷   Starting crawling ${this.baseUrl}`)
    return this.snap()
      .then(() => console.log(`🕸   Finished crawling.`))
  }

  snap() {
    let path = this.paths.shift()
    if (!path) return Promise.resolve()
    if (this.processed[path]) {
      return this.snap()
    } else {
      this.processed[path] = true
    }
    return snapshot(this.protocol, this.host, path).then(window => {
      const html = jsdom.serializeDocument(window.document)
      this.extractNewLinks(window, path)
      this.handler({ path, html })
      return this.snap()
    }, err => {
      console.log(err)
    })
  }

  extractNewLinks(window, currentPath) {
    const document = window.document
    const tagAttributeMap = {
      'a': 'href',
      'iframe': 'src'
    }

    Object.keys(tagAttributeMap).forEach(tagName => {
      const urlAttribute = tagAttributeMap[tagName]
      Array.from(document.querySelectorAll(`${tagName}[${urlAttribute}]`)).forEach(element => {
        const { protocol, host, path } = url.parse(element.getAttribute(urlAttribute))
        if (protocol || host) return
        const relativePath = url.resolve(currentPath, path)
        if (!this.processed[relativePath]) this.paths.push(relativePath)
      })
    })
  }
}
<|endoftext|>


<commit_before>import EventEmitter from 'events'
import { readFile } from './fs.js'

import File from './File.js'
import Waiter from './Waiter.js'
import Watcher from './Watcher.js'

export default class Defiler extends EventEmitter {
	constructor() {
		super()

		this._origFiles = new Map()
		this._files = new Map()
		this._ready = null

		this._waiter = new Waiter()

		this._watchers = []

		this._transforms = []
		this._filePromises = new Map()
		this._customGenerators = new Map()
		this._dependents = new Map()

		this._processing = false
		this._queue = []
	}

	// read-only getters

	get ready() {
		return this._ready
	}

	get origFiles() {
		return this._origFiles
	}

	get files() {
		return this._files
	}

	get origPaths() {
		return [...this._origFiles.keys()].sort()
	}

	// pre-exec (configuration) methods

	add(config) {
		this._checkBeforeExec('add')

		// add dir

		if (config.dir) {
			let { dir, read = true, watch = true, debounce = 50 } = config
			let watcher = new Watcher(dir, watch, debounce)
			this._watchers.push({ watcher, dir, read, watch })
		}

		// add transform

		if (config.transform) {
			let { transform, if: if_ } = config
			this._transforms.push({ transform, if: if_ })
		}

		// add generated file

		if (config.generator) {
			let { path, generator } = config
			this._customGenerators.set(path, generator)
		}

		return this
	}

	// exec

	exec() {
		this._checkBeforeExec('exec')
		this._processing = true
		this._ready = (async () => {
			await Promise.all(
				this._watchers.map(async ({ watcher, dir, read, watch }) => {
					if (watch) {
						watcher.on('', ({ event, path, stat }) => {
							this._enqueue({ event, dir, path, stat, read })
						})
					}
					for (let { path, stat } of await watcher.init()) {
						let promise = this._processPhysicalFile(dir, path, stat, read)
						this._waiter.add(promise)
						this._filePromises.set(path, promise)
						this._origFiles.set(path, null)
					}
				}),
			)

			for (let path of this._customGenerators.keys()) {
				let promise = this._handleGeneratedFile(path)
				this._waiter.add(promise)
				this._filePromises.set(path, promise)
			}

			await this._waiter.done

			let _processDependents = this._processDependents.bind(this)
			this.on('file', _processDependents)
			this.on('deleted', _processDependents)

			this._filePromises = null
			this._processing = false
		})()

		return this
	}

	// post-exec methods

	async get(path, dependent) {
		this._checkAfterExec('get')
		if (Array.isArray(path)) return Promise.all(path.map(path => this.get(path, dependent)))
		if (dependent) {
			if (this._dependents.has(dependent)) {
				this._dependents.get(dependent).add(path)
			} else {
				this._dependents.set(dependent, new Set([path]))
			}
		}
		if (this._filePromises) await this._filePromises.get(path)
		return this._files.get(path)
	}

	async refile(path) {
		this._checkAfterExec('refile')
		if (this._customGenerators.has(path)) {
			await this._handleGeneratedFile(path)
		} else if (this._origFiles.has(path)) {
			await this._processFile(this._origFiles.get(path))
		}
	}

	async addFile(file) {
		this._checkAfterExec('addFile')
		let { path } = file
		if (!(file instanceof File)) file = Object.assign(new File(), file)
		await this._waiter.add(this._transformFile(file))
		this._files.set(path, file)
		this.emit('file', { defiler: this, path, file })
	}

	// private methods

	_checkBeforeExec(methodName) {
		if (this._ready) throw new Error(`Cannot call ${methodName} after calling exec`)
	}

	_checkAfterExec(methodName) {
		if (!this._ready) throw new Error(`Cannot call ${methodName} before calling exec`)
	}

	async _enqueue(event) {
		this._queue.push(event)
		if (this._processing) return
		this._processing = true
		while (this._queue.length) {
			let { event, dir, path, stat, read } = this._queue.shift()
			if (event === '+') {
				await this._processPhysicalFile(dir, path, stat, read)
			} else if (event === '-') {
				this._origFiles.delete(path)
				this._files.delete(path)
				this.emit('deleted', { defiler: this, path })
			}
		}
		this._processing = false
	}

	async _processPhysicalFile(dir, path, fileStat, read) {
		let origFile = new File(path)
		origFile.stat = fileStat
		if (read) origFile.bytes = await readFile(dir + '/' + path)
		this._origFiles.set(path, origFile)
		this.emit('origFile', { defiler: this, file: origFile })
		await this._processFile(origFile)
	}

	async _processFile(origFile) {
		let file = Object.assign(new File(), origFile)
		await this._transformFile(file)
		this._files.set(origFile.path, file)
		this.emit('file', { defiler: this, path: origFile.path, file })
	}

	async _transformFile(file) {
		let { path } = file
		try {
			for (let { transform, if: if_ } of this._transforms) {
				if (!if_ || (await if_({ defiler: this, path, file }))) {
					await transform({
						defiler: this,
						path,
						file,
						get: dependency => this.get(dependency, path),
					})
				}
			}
		} catch (error) {
			this.emit('error', { defiler: this, path, file, error })
		}
	}

	async _handleGeneratedFile(path) {
		let file
		try {
			file = new File(path)
			await this._customGenerators.get(path)({
				defiler: this,
				file,
				get: dependency => this.get(dependency, path),
			})
			await this.addFile(file)
		} catch (error) {
			this.emit('error', { defiler: this, path, file, error })
		}
	}

	_processDependents({ path }) {
		let dependents = new Set()
		for (let [dependent, dependencies] of this._dependents.entries()) {
			if (dependencies.has(path)) {
				dependents.add(dependent)
				this._dependents.delete(dependent)
			}
		}
		for (let dependent of dependents) this.refile(dependent)
	}
}
<commit_msg>normalize input directories<commit_after>import EventEmitter from 'events'
import { readFile } from './fs.js'
import { resolve } from 'path'

import File from './File.js'
import Waiter from './Waiter.js'
import Watcher from './Watcher.js'

export default class Defiler extends EventEmitter {
	constructor() {
		super()

		this._origFiles = new Map()
		this._files = new Map()
		this._ready = null

		this._waiter = new Waiter()

		this._watchers = []

		this._transforms = []
		this._filePromises = new Map()
		this._customGenerators = new Map()
		this._dependents = new Map()

		this._processing = false
		this._queue = []
	}

	// read-only getters

	get ready() {
		return this._ready
	}

	get origFiles() {
		return this._origFiles
	}

	get files() {
		return this._files
	}

	get origPaths() {
		return [...this._origFiles.keys()].sort()
	}

	// pre-exec (configuration) methods

	add(config) {
		this._checkBeforeExec('add')

		// add dir

		if (config.dir) {
			let { dir, read = true, watch = true, debounce = 50 } = config
			dir = resolve(dir).replace(/\\/g, '/')
			let watcher = new Watcher(dir, watch, debounce)
			this._watchers.push({ watcher, dir, read, watch })
		}

		// add transform

		if (config.transform) {
			let { transform, if: if_ } = config
			this._transforms.push({ transform, if: if_ })
		}

		// add generated file

		if (config.generator) {
			let { path, generator } = config
			this._customGenerators.set(path, generator)
		}

		return this
	}

	// exec

	exec() {
		this._checkBeforeExec('exec')
		this._processing = true
		this._ready = (async () => {
			await Promise.all(
				this._watchers.map(async ({ watcher, dir, read, watch }) => {
					if (watch) {
						watcher.on('', ({ event, path, stat }) => {
							this._enqueue({ event, dir, path, stat, read })
						})
					}
					for (let { path, stat } of await watcher.init()) {
						let promise = this._processPhysicalFile(dir, path, stat, read)
						this._waiter.add(promise)
						this._filePromises.set(path, promise)
						this._origFiles.set(path, null)
					}
				}),
			)

			for (let path of this._customGenerators.keys()) {
				let promise = this._handleGeneratedFile(path)
				this._waiter.add(promise)
				this._filePromises.set(path, promise)
			}

			await this._waiter.done

			let _processDependents = this._processDependents.bind(this)
			this.on('file', _processDependents)
			this.on('deleted', _processDependents)

			this._filePromises = null
			this._processing = false
		})()

		return this
	}

	// post-exec methods

	async get(path, dependent) {
		this._checkAfterExec('get')
		if (Array.isArray(path)) return Promise.all(path.map(path => this.get(path, dependent)))
		if (dependent) {
			if (this._dependents.has(dependent)) {
				this._dependents.get(dependent).add(path)
			} else {
				this._dependents.set(dependent, new Set([path]))
			}
		}
		if (this._filePromises) await this._filePromises.get(path)
		return this._files.get(path)
	}

	async refile(path) {
		this._checkAfterExec('refile')
		if (this._customGenerators.has(path)) {
			await this._handleGeneratedFile(path)
		} else if (this._origFiles.has(path)) {
			await this._processFile(this._origFiles.get(path))
		}
	}

	async addFile(file) {
		this._checkAfterExec('addFile')
		let { path } = file
		if (!(file instanceof File)) file = Object.assign(new File(), file)
		await this._waiter.add(this._transformFile(file))
		this._files.set(path, file)
		this.emit('file', { defiler: this, path, file })
	}

	// private methods

	_checkBeforeExec(methodName) {
		if (this._ready) throw new Error(`Cannot call ${methodName} after calling exec`)
	}

	_checkAfterExec(methodName) {
		if (!this._ready) throw new Error(`Cannot call ${methodName} before calling exec`)
	}

	async _enqueue(event) {
		this._queue.push(event)
		if (this._processing) return
		this._processing = true
		while (this._queue.length) {
			let { event, dir, path, stat, read } = this._queue.shift()
			if (event === '+') {
				await this._processPhysicalFile(dir, path, stat, read)
			} else if (event === '-') {
				this._origFiles.delete(path)
				this._files.delete(path)
				this.emit('deleted', { defiler: this, path })
			}
		}
		this._processing = false
	}

	async _processPhysicalFile(dir, path, fileStat, read) {
		let origFile = new File(path)
		origFile.stat = fileStat
		if (read) origFile.bytes = await readFile(dir + '/' + path)
		this._origFiles.set(path, origFile)
		this.emit('origFile', { defiler: this, file: origFile })
		await this._processFile(origFile)
	}

	async _processFile(origFile) {
		let file = Object.assign(new File(), origFile)
		await this._transformFile(file)
		this._files.set(origFile.path, file)
		this.emit('file', { defiler: this, path: origFile.path, file })
	}

	async _transformFile(file) {
		let { path } = file
		try {
			for (let { transform, if: if_ } of this._transforms) {
				if (!if_ || (await if_({ defiler: this, path, file }))) {
					await transform({
						defiler: this,
						path,
						file,
						get: dependency => this.get(dependency, path),
					})
				}
			}
		} catch (error) {
			this.emit('error', { defiler: this, path, file, error })
		}
	}

	async _handleGeneratedFile(path) {
		let file
		try {
			file = new File(path)
			await this._customGenerators.get(path)({
				defiler: this,
				file,
				get: dependency => this.get(dependency, path),
			})
			await this.addFile(file)
		} catch (error) {
			this.emit('error', { defiler: this, path, file, error })
		}
	}

	_processDependents({ path }) {
		let dependents = new Set()
		for (let [dependent, dependencies] of this._dependents.entries()) {
			if (dependencies.has(path)) {
				dependents.add(dependent)
				this._dependents.delete(dependent)
			}
		}
		for (let dependent of dependents) this.refile(dependent)
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright 2012 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, brackets: false */

/**
 * Utilities for creating and managing standard modal dialogs.
 */
define(function (require, exports, module) {
    'use strict';

    var DIALOG_BTN_CANCEL = "cancel",
        DIALOG_BTN_OK = "ok",
        DIALOG_BTN_DONTSAVE = "dontsave",
        DIALOG_CANCELED = "_canceled";

    // TODO: (issue #258) In future, we should templatize the HTML for the dialogs rather than having
    // it live directly in the HTML.
    var DIALOG_ID_ERROR = "error-dialog",
        DIALOG_ID_SAVE_CLOSE = "save-close-dialog",
        DIALOG_ID_EXT_CHANGED = "ext-changed-dialog",
        DIALOG_ID_EXT_DELETED = "ext-deleted-dialog";

    function _dismissDialog(dlg, buttonId) {
        dlg.data("buttonId", buttonId);
        dlg.modal(true).hide();
    }

    function _hasButton(dlg, buttonId) {
        return dlg.find("[data-button-id='" + buttonId + "']");
    }

    var _handleKeyDown = function (e) {
        var primaryBtn = this.find(".primary"),
            buttonId = null,
            which = String.fromCharCode(e.which);

        if (e.which === 13) {
            // Click primary button
            if (primaryBtn) {
                buttonId = primaryBtn.attr("data-button-id");
            }
        } else if (e.which === 32) {
            // Space bar on focused button
            this.find(".dialog-button:focus").click();
        } else if (brackets.platform === "mac") {
            // CMD+D Don't Save
            if (e.metaKey && (which === 'D')) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            // FIXME (issue #418) CMD+. Cancel swallowed by native shell
            } else if (e.metaKey && (e.which === 190)) {
                buttonId = DIALOG_BTN_CANCEL;
            }
        } else { // if (brackets.platform === "win") {
            // 'N' Don't Save
            if (which === 'N') {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            }
        }

        if (buttonId) {
            _dismissDialog(this, buttonId);
        } else {
            // Stop the event if not handled by this dialog
            e.stopPropagation();
            e.preventDefault();
        }
    };

    /**
     * General purpose modal dialog. Assumes that:
     * -- the root tag of the dialog is marked with a unique class name (passed as dlgClass), as well as the
     *    classes "template modal hide".
     * -- the HTML for the dialog contains elements with "title" and "message" classes, as well as a number
     *    of elements with "dialog-button" class, each of which has a "data-button-id".
     *
     * @param {string} dlgClass The class of the dialog node in the HTML.
     * @param {string} title The title of the error dialog. Can contain HTML markup.
     * @param {string} message The message to display in the error dialog. Can contain HTML markup.
     * @return {Deferred} a $.Deferred() that will be resolved with the ID of the clicked button when the dialog
     *     is dismissed. Never rejected.
     */
    function showModalDialog(dlgClass, title, message, callback) {
        var result = $.Deferred();

        // We clone the HTML rather than using it directly so that if two dialogs of the same
        // type happen to show up, they can appear at the same time. (This is an edge case that
        // shouldn't happen often, but we can't prevent it from happening since everything is
        // asynchronous.)
        var dlg = $("." + dlgClass + ".template")
            .clone()
            .removeClass("template")
            .addClass("instance")
            .appendTo(document.body);

        // Set title and message
        $(".dialog-title", dlg).html(title);
        $(".dialog-message", dlg).html(message);

        var handleKeyDown = _handleKeyDown.bind(dlg);

        // Pipe dialog-closing notification back to client code
        dlg.one("hidden", function () {
            var buttonId = dlg.data("buttonId");
            if (!buttonId) {    // buttonId will be undefined if closed via Bootstrap's "x" button
                buttonId = DIALOG_BTN_CANCEL;
            }

            // Let call stack return before notifying that dialog has closed; this avoids issue #191
            // if the handler we're triggering might show another dialog (as long as there's no
            // fade-out animation)
            setTimeout(function () {
                result.resolve(buttonId);
            }, 0);

            // Remove the dialog instance from the DOM.
            dlg.remove();

            // Remove keydown event handler
            document.body.removeEventListener("keydown", handleKeyDown, true);
        }).one("shown", function () {
            // Set focus to the default button
            var primaryBtn = dlg.find(".primary");

            if (primaryBtn) {
                primaryBtn.focus();
            }

            // Listen for dialog keyboard shortcuts
            document.body.addEventListener("keydown", handleKeyDown, true);
        });

        // Click handler for buttons
        dlg.one("click", ".dialog-button", function (e) {
            _dismissDialog(dlg, $(this).attr("data-button-id"));
        });

        // Run the dialog
        dlg.modal({
            backdrop: "static",
            show: true,
            keyboard: true
        });
        return result;
    }

    /**
     * Immediately closes any dialog instances with the given class. The dialog callback for each instance will
     * be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
     */
    function cancelModalDialogIfOpen(dlgClass) {
        $("." + dlgClass + ".instance").each(function (dlg) {
            if (dlg.is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
                _dismissDialog(dlg, DIALOG_CANCELED);
            }
        });
    }

    exports.DIALOG_BTN_CANCEL = DIALOG_BTN_CANCEL;
    exports.DIALOG_BTN_OK = DIALOG_BTN_OK;
    exports.DIALOG_BTN_DONTSAVE = DIALOG_BTN_DONTSAVE;
    exports.DIALOG_CANCELED = DIALOG_CANCELED;

    exports.DIALOG_ID_ERROR = DIALOG_ID_ERROR;
    exports.DIALOG_ID_SAVE_CLOSE = DIALOG_ID_SAVE_CLOSE;
    exports.DIALOG_ID_EXT_CHANGED = DIALOG_ID_EXT_CHANGED;
    exports.DIALOG_ID_EXT_DELETED = DIALOG_ID_EXT_DELETED;

    exports.showModalDialog = showModalDialog;
    exports.cancelModalDialogIfOpen = cancelModalDialogIfOpen;
});<commit_msg>Allow keyboard events in dialog form elements<commit_after>/*
 * Copyright 2012 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, brackets: false */

/**
 * Utilities for creating and managing standard modal dialogs.
 */
define(function (require, exports, module) {
    'use strict';

    var DIALOG_BTN_CANCEL = "cancel",
        DIALOG_BTN_OK = "ok",
        DIALOG_BTN_DONTSAVE = "dontsave",
        DIALOG_CANCELED = "_canceled";

    // TODO: (issue #258) In future, we should templatize the HTML for the dialogs rather than having
    // it live directly in the HTML.
    var DIALOG_ID_ERROR = "error-dialog",
        DIALOG_ID_SAVE_CLOSE = "save-close-dialog",
        DIALOG_ID_EXT_CHANGED = "ext-changed-dialog",
        DIALOG_ID_EXT_DELETED = "ext-deleted-dialog";

    function _dismissDialog(dlg, buttonId) {
        dlg.data("buttonId", buttonId);
        dlg.modal(true).hide();
    }

    function _hasButton(dlg, buttonId) {
        return dlg.find("[data-button-id='" + buttonId + "']");
    }

    var _handleKeyDown = function (e) {
        var primaryBtn = this.find(".primary"),
            buttonId = null,
            which = String.fromCharCode(e.which);

        if (e.which === 13) {
            // Click primary button
            if (primaryBtn) {
                buttonId = primaryBtn.attr("data-button-id");
            }
        } else if (e.which === 32) {
            // Space bar on focused button
            this.find(".dialog-button:focus").click();
        } else if (brackets.platform === "mac") {
            // CMD+D Don't Save
            if (e.metaKey && (which === 'D')) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            // FIXME (issue #418) CMD+. Cancel swallowed by native shell
            } else if (e.metaKey && (e.which === 190)) {
                buttonId = DIALOG_BTN_CANCEL;
            }
        } else { // if (brackets.platform === "win") {
            // 'N' Don't Save
            if (which === 'N') {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            }
        }

        if (buttonId) {
            _dismissDialog(this, buttonId);
        } else if (!($.contains(this.get(0), e.target)) ||
                  (this.filter(":input").length === 0)) {
            // Stop the event if the target is not inside the dialog
            // or if the target is not a form element.
            // TODO (issue #414): more robust handling of dialog scoped
            //                    vs. global key bindings
            e.stopPropagation();
            e.preventDefault();
        }
    };

    /**
     * General purpose modal dialog. Assumes that:
     * -- the root tag of the dialog is marked with a unique class name (passed as dlgClass), as well as the
     *    classes "template modal hide".
     * -- the HTML for the dialog contains elements with "title" and "message" classes, as well as a number
     *    of elements with "dialog-button" class, each of which has a "data-button-id".
     *
     * @param {string} dlgClass The class of the dialog node in the HTML.
     * @param {string} title The title of the error dialog. Can contain HTML markup.
     * @param {string} message The message to display in the error dialog. Can contain HTML markup.
     * @return {Deferred} a $.Deferred() that will be resolved with the ID of the clicked button when the dialog
     *     is dismissed. Never rejected.
     */
    function showModalDialog(dlgClass, title, message, callback) {
        var result = $.Deferred();

        // We clone the HTML rather than using it directly so that if two dialogs of the same
        // type happen to show up, they can appear at the same time. (This is an edge case that
        // shouldn't happen often, but we can't prevent it from happening since everything is
        // asynchronous.)
        var dlg = $("." + dlgClass + ".template")
            .clone()
            .removeClass("template")
            .addClass("instance")
            .appendTo(document.body);

        // Set title and message
        $(".dialog-title", dlg).html(title);
        $(".dialog-message", dlg).html(message);

        var handleKeyDown = _handleKeyDown.bind(dlg);

        // Pipe dialog-closing notification back to client code
        dlg.one("hidden", function () {
            var buttonId = dlg.data("buttonId");
            if (!buttonId) {    // buttonId will be undefined if closed via Bootstrap's "x" button
                buttonId = DIALOG_BTN_CANCEL;
            }

            // Let call stack return before notifying that dialog has closed; this avoids issue #191
            // if the handler we're triggering might show another dialog (as long as there's no
            // fade-out animation)
            setTimeout(function () {
                result.resolve(buttonId);
            }, 0);

            // Remove the dialog instance from the DOM.
            dlg.remove();

            // Remove keydown event handler
            document.body.removeEventListener("keydown", handleKeyDown, true);
        }).one("shown", function () {
            // Set focus to the default button
            var primaryBtn = dlg.find(".primary");

            if (primaryBtn) {
                primaryBtn.focus();
            }

            // Listen for dialog keyboard shortcuts
            document.body.addEventListener("keydown", handleKeyDown, true);
        });

        // Click handler for buttons
        dlg.one("click", ".dialog-button", function (e) {
            _dismissDialog(dlg, $(this).attr("data-button-id"));
        });

        // Run the dialog
        dlg.modal({
            backdrop: "static",
            show: true,
            keyboard: true
        });
        return result;
    }

    /**
     * Immediately closes any dialog instances with the given class. The dialog callback for each instance will
     * be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
     */
    function cancelModalDialogIfOpen(dlgClass) {
        $("." + dlgClass + ".instance").each(function (dlg) {
            if (dlg.is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
                _dismissDialog(dlg, DIALOG_CANCELED);
            }
        });
    }

    exports.DIALOG_BTN_CANCEL = DIALOG_BTN_CANCEL;
    exports.DIALOG_BTN_OK = DIALOG_BTN_OK;
    exports.DIALOG_BTN_DONTSAVE = DIALOG_BTN_DONTSAVE;
    exports.DIALOG_CANCELED = DIALOG_CANCELED;

    exports.DIALOG_ID_ERROR = DIALOG_ID_ERROR;
    exports.DIALOG_ID_SAVE_CLOSE = DIALOG_ID_SAVE_CLOSE;
    exports.DIALOG_ID_EXT_CHANGED = DIALOG_ID_EXT_CHANGED;
    exports.DIALOG_ID_EXT_DELETED = DIALOG_ID_EXT_DELETED;

    exports.showModalDialog = showModalDialog;
    exports.cancelModalDialogIfOpen = cancelModalDialogIfOpen;
});<|endoftext|>


<commit_before>/**
 * Construct the fastest available display object.
 * @returns {Display}
 * @constructor
 */
function Display(canvas) {
    var gl = canvas.getContext('webgl') ||
            canvas.getContext('experimental-webgl');
    if (gl == null) {
        return new Display2D(canvas.getContext('2d'));
    } else {
        return new DisplayGL(gl);
    }
}

Display.prototype.fl = 4;
Display.prototype.scale = 0.6;
Display.prototype.translate = P(0, 0, 1.5);
Display.prototype.rotate = P(0, 0, 0);
Display.prototype.dirty = true;
Display.prototype.points = null;
Display.prototype.lines = null;

Display.error = function(message) {
    message = message || 'unimplemented method';
    return function() {
        throw new Error(message);
    };
};

Display.prototype.clear = Display.error();
Display.prototype.render = Display.error();

/**
 * @param {Point} point
 * @param {Point} color
 * @returns {Display} this
 * @method
 */
Display.prototype.addPoint = function(point, color) {
    this.dirty = true;
    this.points.push({point: point, color: color.toColor()});
    return this;
};

/**
 * @param {Point} a
 * @param {Point} b
 * @returns {Display} this
 * @method
 */
Display.prototype.addLine = function(a, b, color) {
    this.dirty = true;
    this.lines.push({a: a, b: b, color: color.toColor()});
    return this;
};

/**
 * @returns {Display} this
 * @method
 */
Display.prototype.clearData = function() {
    this.dirty = true;
    this.points.length = 0;
    this.lines.length = 0;
    return this;
};
<commit_msg>Put a try-catch around WebGL init (stupid Firefox).<commit_after>/**
 * Construct the fastest available display object.
 * @returns {Display}
 * @constructor
 */
function Display(canvas) {
    try {
        var gl = canvas.getContext('webgl') ||
                canvas.getContext('experimental-webgl');
    } catch (e) {
        gl = null;
    }
    if (gl == null) {
        return new Display2D(canvas.getContext('2d'));
    } else {
        return new DisplayGL(gl);
    }
}

Display.prototype.fl = 4;
Display.prototype.scale = 0.6;
Display.prototype.translate = P(0, 0, 1.5);
Display.prototype.rotate = P(0, 0, 0);
Display.prototype.dirty = true;
Display.prototype.points = null;
Display.prototype.lines = null;

Display.error = function(message) {
    message = message || 'unimplemented method';
    return function() {
        throw new Error(message);
    };
};

Display.prototype.clear = Display.error();
Display.prototype.render = Display.error();

/**
 * @param {Point} point
 * @param {Point} color
 * @returns {Display} this
 * @method
 */
Display.prototype.addPoint = function(point, color) {
    this.dirty = true;
    this.points.push({point: point, color: color.toColor()});
    return this;
};

/**
 * @param {Point} a
 * @param {Point} b
 * @returns {Display} this
 * @method
 */
Display.prototype.addLine = function(a, b, color) {
    this.dirty = true;
    this.lines.push({a: a, b: b, color: color.toColor()});
    return this;
};

/**
 * @returns {Display} this
 * @method
 */
Display.prototype.clearData = function() {
    this.dirty = true;
    this.points.length = 0;
    this.lines.length = 0;
    return this;
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import SPELLS from 'common/SPELLS';

class HolyPriestSpreadsheet extends React.Component {
  static propTypes = {
    parser: PropTypes.object.isRequired,
  };

  render() {
    const { parser } = this.props;

    const styles = {
      cellBorder: { borderTop: '.5px solid #dddddd' },
      table: { borderBottom: '1px solid #dddddd', borderTop: '1px solid #dddddd', align: 'left', padding: '8px', float: 'left', margin: '2px' },
    };

    const getAbility = spellId => parser._modules.abilityTracker.getAbility(spellId);

    const overhealingSpell = spellId => ((getAbility(spellId).healingOverheal || 0) / ((getAbility(spellId).healingOverheal || 0) + (getAbility(spellId).healingEffective || 0)) || 0).toFixed(5);

    const cpm = spellId => (getAbility(spellId).casts / Math.floor(parser.fightDuration / 1000 / 60) || 0).toFixed(5);

    const targetsPerCast = (spellId, healId) => {
      const ability = getAbility(spellId);
      const heal = healId === undefined ? getAbility(spellId) : getAbility(healId);

      if (ability.ability == null) {
        return 'N/A';
      }

      const count = heal.healingHits / ability.casts;
      if (count) {
        return count.toFixed(5);
      }

      return 1;
    };

    const castEfficiency = (spellId) => {
      const efficiency = parser._modules.CastEfficiency.getCastEfficiencyForSpellId(spellId, true);
      if (!efficiency) {
        return 'N/A';
      }
      else if (efficiency.efficiency) {
        return efficiency.efficiency.toFixed(5);
      }
      else if (efficiency.maxCasts !== Infinity) {
        return '0.00000';
      } else {
        return 1;
      }
    };

    const rawHealing = (spellId) => {
      const ability = getAbility(spellId);
      if (ability.ability) {
        return ability.healingEffective + ability.healingOverheal;
      }
      return 0;
    };

    return (

Please use the below table to populate the Player Log section of the Holy Priest Spreadsheet by Niphyr. Link to the sheet

                Intellect Rating
                {parser.selectedCombatant._combatantInfo.intellect}

                Critical Strike Rating
                {parser.selectedCombatant._combatantInfo.critSpell}

                Haste Rating
                {parser.selectedCombatant._combatantInfo.hasteSpell}

                Mastery Rating
                {parser.selectedCombatant._combatantInfo.mastery}

                Versatility Rating
                {parser.selectedCombatant._combatantInfo.versatilityHealingDone}

                Leech
                {parser.selectedCombatant._combatantInfo.leech}

                Avoidance
                {parser.selectedCombatant._combatantInfo.avoidance}

                Speed
                {parser.selectedCombatant._combatantInfo.speed}

                Healing Done
                {parser._modules.healingDone._total._regular}

                Self Healing
                {parser._modules.healingReceived.HealingReceivedSelf}

                Damage Taken
                {parser._modules.damageTaken._total._regular}

                Deaths
                {parser._modules.deathTracker.deaths.length}

                Ending Mana
                {parser._modules.manaValues.endingMana}

                Fight Length
                {Math.floor(parser.fightDuration / 1000)}

                Sanctify CDR (s)
                {parser._modules.serendipity.sanctify.rawReduction / 1000}

                Serenity CDR (s)
                {parser._modules.serendipity.serenity.rawReduction / 1000}

                Sanctify CDR wasted (s)
                {parser._modules.serendipity.sanctify.overcast / 1000}

                Serenity CDR wasted (s)
                {parser._modules.serendipity.serenity.overcast / 1000}

                Total Healing from Azerite Traits
                0

                Renews refreshed by Enduring Renewal
                0

                Renews from benediction
                0

                Spell Name
                Raw Healing
                Overheal %
                CPM
                Casting Efficiency %
                Average Targets / Cast

                Heal
                {rawHealing(SPELLS.GREATER_HEAL.id)}
                {overhealingSpell(SPELLS.GREATER_HEAL.id)}
                {cpm(SPELLS.GREATER_HEAL.id)}
                {castEfficiency(SPELLS.GREATER_HEAL.id)}
                {targetsPerCast(SPELLS.GREATER_HEAL.id)}

                Flash Heal
                {rawHealing(SPELLS.FLASH_HEAL.id)}
                {overhealingSpell(SPELLS.FLASH_HEAL.id)}
                {cpm(SPELLS.FLASH_HEAL.id)}
                {castEfficiency(SPELLS.FLASH_HEAL.id)}
                {targetsPerCast(SPELLS.FLASH_HEAL.id)}

                Renew
                {rawHealing(SPELLS.RENEW.id)}
                {overhealingSpell(SPELLS.RENEW.id)}
                {cpm(SPELLS.RENEW.id)}
                {castEfficiency(SPELLS.RENEW.id)}
                {targetsPerCast(SPELLS.RENEW.id)}

                Prayer of Healing
                {rawHealing(SPELLS.PRAYER_OF_HEALING.id)}
                {overhealingSpell(SPELLS.PRAYER_OF_HEALING.id)}
                {cpm(SPELLS.PRAYER_OF_HEALING.id)}
                {castEfficiency(SPELLS.PRAYER_OF_HEALING.id)}
                {targetsPerCast(SPELLS.PRAYER_OF_HEALING.id)}

                Prayer of Mending
                {rawHealing(SPELLS.PRAYER_OF_MENDING_HEAL.id)}
                {overhealingSpell(SPELLS.PRAYER_OF_MENDING_HEAL.id)}
                {cpm(SPELLS.PRAYER_OF_MENDING_CAST.id)}
                {castEfficiency(SPELLS.PRAYER_OF_MENDING_CAST.id)}
                {targetsPerCast(SPELLS.PRAYER_OF_MENDING_CAST.id, SPELLS.PRAYER_OF_MENDING_HEAL.id)}

                Holy Word: Serenity
                {rawHealing(SPELLS.HOLY_WORD_SERENITY.id)}
                {overhealingSpell(SPELLS.HOLY_WORD_SERENITY.id)}
                {cpm(SPELLS.HOLY_WORD_SERENITY.id)}
                {castEfficiency(SPELLS.HOLY_WORD_SERENITY.id)}
                {targetsPerCast(SPELLS.HOLY_WORD_SERENITY.id)}

                Holy Word: Sanctify
                {rawHealing(SPELLS.HOLY_WORD_SANCTIFY.id)}
                {overhealingSpell(SPELLS.HOLY_WORD_SANCTIFY.id)}
                {(getAbility(SPELLS.HOLY_WORD_SANCTIFY.id).healingHits / Math.floor(parser.fightDuration / 1000 / 60) || 0).toFixed(5)}
                {castEfficiency(SPELLS.HOLY_WORD_SANCTIFY.id)}
                {targetsPerCast(SPELLS.HOLY_WORD_SANCTIFY.id)}

                Guardian Spirit
                0
                {overhealingSpell(SPELLS.GUARDIAN_SPIRIT.id)}
                {cpm(SPELLS.GUARDIAN_SPIRIT.id)}
                {castEfficiency(SPELLS.GUARDIAN_SPIRIT.id)}
                1

                Divine Hymn
                {rawHealing(SPELLS.DIVINE_HYMN_HEAL.id)}
                {overhealingSpell(SPELLS.DIVINE_HYMN_HEAL.id)}
                {cpm(SPELLS.DIVINE_HYMN_CAST.id)}
                {castEfficiency(SPELLS.DIVINE_HYMN_CAST.id)}
                {targetsPerCast(SPELLS.DIVINE_HYMN_CAST.id, SPELLS.DIVINE_HYMN_HEAL.id)}

                Desperate Prayer
                {rawHealing(SPELLS.DESPERATE_PRAYER.id)}
                {overhealingSpell(SPELLS.DESPERATE_PRAYER.id)}
                {cpm(SPELLS.DESPERATE_PRAYER.id)}
                {castEfficiency(SPELLS.DESPERATE_PRAYER.id)}
                1

                Cosmic Ripple
                {rawHealing(SPELLS.COSMIC_RIPPLE_HEAL.id)}
                {overhealingSpell(SPELLS.COSMIC_RIPPLE_HEAL.id)}
                N/A
                N/A
                {parser._modules.cosmicRipple.totalHits / parser._modules.cosmicRipple.totalRipples}

                Binding Heal
                {rawHealing(SPELLS.BINDING_HEAL_TALENT.id)}
                {overhealingSpell(SPELLS.BINDING_HEAL_TALENT.id)}
                {cpm(SPELLS.BINDING_HEAL_TALENT.id)}
                {castEfficiency(SPELLS.BINDING_HEAL_TALENT.id)}
                {targetsPerCast(SPELLS.BINDING_HEAL_TALENT.id)}

                Circle of Healing
                {rawHealing(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}
                {overhealingSpell(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}
                {cpm(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}
                {castEfficiency(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}
                {targetsPerCast(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}

                Divine Star
                {rawHealing(SPELLS.DIVINE_STAR_TALENT.id)}
                {overhealingSpell(SPELLS.DIVINE_STAR_TALENT.id)}
                {cpm(SPELLS.DIVINE_STAR_TALENT.id)}
                {castEfficiency(SPELLS.DIVINE_STAR_TALENT.id)}
                {targetsPerCast(SPELLS.DIVINE_STAR_TALENT.id)}

                Halo
                {rawHealing(SPELLS.HALO_TALENT.id)}
                {overhealingSpell(SPELLS.HALO_TALENT.id)}
                {cpm(SPELLS.HALO_TALENT.id)}
                {castEfficiency(SPELLS.HALO_TALENT.id)}
                {targetsPerCast(SPELLS.HALO_TALENT.id)}

                Holy Word: Salvation
                {rawHealing(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}
                {overhealingSpell(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}
                {cpm(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}
                {castEfficiency(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}
                {targetsPerCast(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}

    );
  }
}

export default HolyPriestSpreadsheet;
<commit_msg>Fixing bug with halo output on spreadsheet<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import SPELLS from 'common/SPELLS';

class HolyPriestSpreadsheet extends React.Component {
  static propTypes = {
    parser: PropTypes.object.isRequired,
  };

  render() {
    const { parser } = this.props;

    const styles = {
      cellBorder: { borderTop: '.5px solid #dddddd' },
      table: { borderBottom: '1px solid #dddddd', borderTop: '1px solid #dddddd', align: 'left', padding: '8px', float: 'left', margin: '2px' },
    };

    const getAbility = spellId => parser._modules.abilityTracker.getAbility(spellId);

    const overhealingSpell = spellId => ((getAbility(spellId).healingOverheal || 0) / ((getAbility(spellId).healingOverheal || 0) + (getAbility(spellId).healingEffective || 0)) || 0).toFixed(5);

    const cpm = spellId => (getAbility(spellId).casts / Math.floor(parser.fightDuration / 1000 / 60) || 0).toFixed(5);

    const targetsPerCast = (spellId, healId) => {
      const ability = getAbility(spellId);
      const heal = healId === undefined ? getAbility(spellId) : getAbility(healId);

      if (ability.ability == null) {
        return 'N/A';
      }

      const count = heal.healingHits / ability.casts;
      if (count) {
        return count.toFixed(5);
      }

      return 1;
    };

    const castEfficiency = (spellId) => {
      const efficiency = parser._modules.CastEfficiency.getCastEfficiencyForSpellId(spellId, true);
      if (!efficiency) {
        return 'N/A';
      }
      else if (efficiency.efficiency) {
        return efficiency.efficiency.toFixed(5);
      }
      else if (efficiency.maxCasts !== Infinity) {
        return '0.00000';
      } else {
        return 1;
      }
    };

    const rawHealing = (spellId) => {
      const ability = getAbility(spellId);
      if (ability.ability) {
        return ability.healingEffective + ability.healingOverheal;
      }
      return 0;
    };

    return (

Please use the below table to populate the Player Log section of the Holy Priest Spreadsheet by Niphyr. Link to the sheet

                Intellect Rating
                {parser.selectedCombatant._combatantInfo.intellect}

                Critical Strike Rating
                {parser.selectedCombatant._combatantInfo.critSpell}

                Haste Rating
                {parser.selectedCombatant._combatantInfo.hasteSpell}

                Mastery Rating
                {parser.selectedCombatant._combatantInfo.mastery}

                Versatility Rating
                {parser.selectedCombatant._combatantInfo.versatilityHealingDone}

                Leech
                {parser.selectedCombatant._combatantInfo.leech}

                Avoidance
                {parser.selectedCombatant._combatantInfo.avoidance}

                Speed
                {parser.selectedCombatant._combatantInfo.speed}

                Healing Done
                {parser._modules.healingDone._total._regular}

                Self Healing
                {parser._modules.healingReceived.HealingReceivedSelf}

                Damage Taken
                {parser._modules.damageTaken._total._regular}

                Deaths
                {parser._modules.deathTracker.deaths.length}

                Ending Mana
                {parser._modules.manaValues.endingMana}

                Fight Length
                {Math.floor(parser.fightDuration / 1000)}

                Sanctify CDR (s)
                {parser._modules.serendipity.sanctify.rawReduction / 1000}

                Serenity CDR (s)
                {parser._modules.serendipity.serenity.rawReduction / 1000}

                Sanctify CDR wasted (s)
                {parser._modules.serendipity.sanctify.overcast / 1000}

                Serenity CDR wasted (s)
                {parser._modules.serendipity.serenity.overcast / 1000}

                Total Healing from Azerite Traits
                0

                Renews refreshed by Enduring Renewal
                0

                Renews from benediction
                0

                Spell Name
                Raw Healing
                Overheal %
                CPM
                Casting Efficiency %
                Average Targets / Cast

                Heal
                {rawHealing(SPELLS.GREATER_HEAL.id)}
                {overhealingSpell(SPELLS.GREATER_HEAL.id)}
                {cpm(SPELLS.GREATER_HEAL.id)}
                {castEfficiency(SPELLS.GREATER_HEAL.id)}
                {targetsPerCast(SPELLS.GREATER_HEAL.id)}

                Flash Heal
                {rawHealing(SPELLS.FLASH_HEAL.id)}
                {overhealingSpell(SPELLS.FLASH_HEAL.id)}
                {cpm(SPELLS.FLASH_HEAL.id)}
                {castEfficiency(SPELLS.FLASH_HEAL.id)}
                {targetsPerCast(SPELLS.FLASH_HEAL.id)}

                Renew
                {rawHealing(SPELLS.RENEW.id)}
                {overhealingSpell(SPELLS.RENEW.id)}
                {cpm(SPELLS.RENEW.id)}
                {castEfficiency(SPELLS.RENEW.id)}
                {targetsPerCast(SPELLS.RENEW.id)}

                Prayer of Healing
                {rawHealing(SPELLS.PRAYER_OF_HEALING.id)}
                {overhealingSpell(SPELLS.PRAYER_OF_HEALING.id)}
                {cpm(SPELLS.PRAYER_OF_HEALING.id)}
                {castEfficiency(SPELLS.PRAYER_OF_HEALING.id)}
                {targetsPerCast(SPELLS.PRAYER_OF_HEALING.id)}

                Prayer of Mending
                {rawHealing(SPELLS.PRAYER_OF_MENDING_HEAL.id)}
                {overhealingSpell(SPELLS.PRAYER_OF_MENDING_HEAL.id)}
                {cpm(SPELLS.PRAYER_OF_MENDING_CAST.id)}
                {castEfficiency(SPELLS.PRAYER_OF_MENDING_CAST.id)}
                {targetsPerCast(SPELLS.PRAYER_OF_MENDING_CAST.id, SPELLS.PRAYER_OF_MENDING_HEAL.id)}

                Holy Word: Serenity
                {rawHealing(SPELLS.HOLY_WORD_SERENITY.id)}
                {overhealingSpell(SPELLS.HOLY_WORD_SERENITY.id)}
                {cpm(SPELLS.HOLY_WORD_SERENITY.id)}
                {castEfficiency(SPELLS.HOLY_WORD_SERENITY.id)}
                {targetsPerCast(SPELLS.HOLY_WORD_SERENITY.id)}

                Holy Word: Sanctify
                {rawHealing(SPELLS.HOLY_WORD_SANCTIFY.id)}
                {overhealingSpell(SPELLS.HOLY_WORD_SANCTIFY.id)}
                {(getAbility(SPELLS.HOLY_WORD_SANCTIFY.id).healingHits / Math.floor(parser.fightDuration / 1000 / 60) || 0).toFixed(5)}
                {castEfficiency(SPELLS.HOLY_WORD_SANCTIFY.id)}
                {targetsPerCast(SPELLS.HOLY_WORD_SANCTIFY.id)}

                Guardian Spirit
                0
                {overhealingSpell(SPELLS.GUARDIAN_SPIRIT.id)}
                {cpm(SPELLS.GUARDIAN_SPIRIT.id)}
                {castEfficiency(SPELLS.GUARDIAN_SPIRIT.id)}
                1

                Divine Hymn
                {rawHealing(SPELLS.DIVINE_HYMN_HEAL.id)}
                {overhealingSpell(SPELLS.DIVINE_HYMN_HEAL.id)}
                {cpm(SPELLS.DIVINE_HYMN_CAST.id)}
                {castEfficiency(SPELLS.DIVINE_HYMN_CAST.id)}
                {targetsPerCast(SPELLS.DIVINE_HYMN_CAST.id, SPELLS.DIVINE_HYMN_HEAL.id)}

                Desperate Prayer
                {rawHealing(SPELLS.DESPERATE_PRAYER.id)}
                {overhealingSpell(SPELLS.DESPERATE_PRAYER.id)}
                {cpm(SPELLS.DESPERATE_PRAYER.id)}
                {castEfficiency(SPELLS.DESPERATE_PRAYER.id)}
                1

                Cosmic Ripple
                {rawHealing(SPELLS.COSMIC_RIPPLE_HEAL.id)}
                {overhealingSpell(SPELLS.COSMIC_RIPPLE_HEAL.id)}
                N/A
                N/A
                {parser._modules.cosmicRipple.totalHits / parser._modules.cosmicRipple.totalRipples}

                Binding Heal
                {rawHealing(SPELLS.BINDING_HEAL_TALENT.id)}
                {overhealingSpell(SPELLS.BINDING_HEAL_TALENT.id)}
                {cpm(SPELLS.BINDING_HEAL_TALENT.id)}
                {castEfficiency(SPELLS.BINDING_HEAL_TALENT.id)}
                {targetsPerCast(SPELLS.BINDING_HEAL_TALENT.id)}

                Circle of Healing
                {rawHealing(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}
                {overhealingSpell(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}
                {cpm(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}
                {castEfficiency(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}
                {targetsPerCast(SPELLS.CIRCLE_OF_HEALING_TALENT.id)}

                Divine Star
                {rawHealing(SPELLS.DIVINE_STAR_TALENT.id)}
                {overhealingSpell(SPELLS.DIVINE_STAR_TALENT.id)}
                {cpm(SPELLS.DIVINE_STAR_TALENT.id)}
                {castEfficiency(SPELLS.DIVINE_STAR_TALENT.id)}
                {targetsPerCast(SPELLS.DIVINE_STAR_TALENT.id)}

                Halo
                {rawHealing(SPELLS.HALO_HEAL.id)}
                {overhealingSpell(SPELLS.HALO_HEAL.id)}
                {cpm(SPELLS.HALO_TALENT.id)}
                {castEfficiency(SPELLS.HALO_TALENT.id)}
                {targetsPerCast(SPELLS.HALO_TALENT.id, SPELLS.HALO_HEAL.id)}

                Holy Word: Salvation
                {rawHealing(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}
                {overhealingSpell(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}
                {cpm(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}
                {castEfficiency(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}
                {targetsPerCast(SPELLS.HOLY_WORD_SALVATION_TALENT.id)}

    );
  }
}

export default HolyPriestSpreadsheet;
<|endoftext|>


<commit_before>/* global chrome */
import React, { Component } from 'react';
import PropTypes from 'prop-types'

class Options extends Component {
  constructor(props) {
    super(props);
    let version = '1.1.1';
    if (typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined' && typeof chrome.runtime.getManifest !== 'undefined') {
      version = chrome.runtime.getManifest().version;
    }
    this.state = {
      version
    };
  }

  optionUpdater = (type, name) => {
    return (e) => {
      if (type === 'checkbox') {
        this.props.setOption(name, e.target.checked);
      } else if (type === 'input') {
        console.log(e);
        this.props.setOption(name, e.target.value);
      }
    }
  }

  render() {
    const optionMap = {
      General: [
        { name: 'showGTMLoad', description: 'show data layer presence', type: 'checkbox' },
        { name: 'collapseNested', description: 'auto-collapse nested data layer variables', type: 'checkbox' },
        { name: 'collapseGTMNativeEvents', description: 'auto-collapse gtm.* events', type: 'checkbox' },
        { name: 'hideEmpty', description: 'hide empty data layer variables', type: 'checkbox' },
        { name: 'showArrayIndices', description: 'show array indices', type: 'checkbox' },
        { name: 'showTimestamps', description: 'show navigation timestamps', type: 'checkbox' },
        { name: 'showFriendlyNames', description: 'show friendly names for query parameters where available', type: 'checkbox' },
        { name: 'blockTags', description: 'block tags from firing (experimental, requires Chrome dev/beta channel)', type: 'checkbox' },
        { name: 'dontDecode', description: 'don\'t decode query string values', type: 'checkbox' },
      ],
      Tags: [
        { name: 'showUniversal', description: 'show Universal Analytics tags', type: 'checkbox' },
        { name: 'showClassic', description: 'show GA Classic tags', type: 'checkbox' },
        { name: 'showFloodlight', description: 'show Floodlight tags', type: 'checkbox' },
        { name: 'showSitecatalyst', description: 'show Adobe Analytics tags', type: 'checkbox' },
        { name: 'ignoredTags', description: Ignored IDs (separated by semicolons), type: 'input', placeholder: 'UA-XXX-Y;UA-AAA-B' },
        { name: 'dataLayers', description: Additional data layer objects (separated by semicolons), type: 'input', placeholder: 'digitalData;testDataLayer' },
      ]
    };

    const versionHistory = [
      {
        version: '1.1',
        changes: [
        'initial support for Adobe Launch',
        'toggle friendly param names',
        'toggle param URL decoding',
        ]
      },
      {
        version: '1.0',
        changes: [
        'rewritten UI',
        'import/export functionality',
        'auto collapse gtm.*',
        'fix displayed GTM IDs',
        'misc. bug fixes'
        ]
      }
    ];

    return (
      <div id={`sub${this.props.index}`} className={`pure-menu pure-menu-open`}>

            dataslayer Options

              {this.state.version}

                {
                  Object.keys(optionMap).map((section) =>
                    (<form className="pure-form pure-form-stacked" key={section}>

                              {section}

                      {
                        optionMap[section].map((option) =>
                        (

                                {
                                  option.type === 'checkbox' &&
                                  (

                                      <input
                                        id={option.name}
                                        type="checkbox"
                                        checked={this.props.options[option.name]}
                                        onChange={this.optionUpdater('checkbox', option.name)}
                                      />
                                      {option.description}

                                  	)
                                }
                                {
                                  option.type === 'input' &&
                                  (

                                    {option.description}

                                    <input
                                      placeholder={option.placeholder}
                                      defaultValue={(this.props.options[option.name] || []).join(';')}
                                      onChange={this.optionUpdater('input', option.name)}
                                    />

                                  	)
                                }

)
                        )
                      }
                    </form>)
                  )
                }

                        About

                        by
                        Sean Adams

                        documentation
                        dataslayer.org/documentation

                        open source
                        GitHub

                        report bug / request feature
                        GitHub issues (preferred)

                        email

                        leave a review

                            Chrome Web Store

                        links
                        dataslayer.org

                            Chrome Web Store

                            release notes

                        made with
                        React

                        Pure

                        jQuery

                        data-layer-helper

                        leanModal

                        O.o polyfill by MaxArt2501

                            Open Sans

                            (license)

                            Madebyoliver icons
                            from www.flaticon.com

What's New

                            x.x.* are bug fix releases

                        {
                          versionHistory.map(({ version, changes }) =>
                            (

                              {version}

                                {changes.map((note, i) => (<span key={`${note}i`}>{note}
))}

)
                          )
                        }

    );
  }
}

Options.propTypes = {
  options: PropTypes.object,
  setOption: PropTypes.func,
};

export default Options;
<commit_msg>add github contributors link to options<commit_after>/* global chrome */
import React, { Component } from 'react';
import PropTypes from 'prop-types'

class Options extends Component {
  constructor(props) {
    super(props);
    let version = '1.1.1';
    if (typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined' && typeof chrome.runtime.getManifest !== 'undefined') {
      version = chrome.runtime.getManifest().version;
    }
    this.state = {
      version
    };
  }

  optionUpdater = (type, name) => {
    return (e) => {
      if (type === 'checkbox') {
        this.props.setOption(name, e.target.checked);
      } else if (type === 'input') {
        console.log(e);
        this.props.setOption(name, e.target.value);
      }
    }
  }

  render() {
    const optionMap = {
      General: [
        { name: 'showGTMLoad', description: 'show data layer presence', type: 'checkbox' },
        { name: 'collapseNested', description: 'auto-collapse nested data layer variables', type: 'checkbox' },
        { name: 'collapseGTMNativeEvents', description: 'auto-collapse gtm.* events', type: 'checkbox' },
        { name: 'hideEmpty', description: 'hide empty data layer variables', type: 'checkbox' },
        { name: 'showArrayIndices', description: 'show array indices', type: 'checkbox' },
        { name: 'showTimestamps', description: 'show navigation timestamps', type: 'checkbox' },
        { name: 'showFriendlyNames', description: 'show friendly names for query parameters where available', type: 'checkbox' },
        { name: 'blockTags', description: 'block tags from firing (experimental, requires Chrome dev/beta channel)', type: 'checkbox' },
        { name: 'dontDecode', description: 'don\'t decode query string values', type: 'checkbox' },
      ],
      Tags: [
        { name: 'showUniversal', description: 'show Universal Analytics tags', type: 'checkbox' },
        { name: 'showClassic', description: 'show GA Classic tags', type: 'checkbox' },
        { name: 'showFloodlight', description: 'show Floodlight tags', type: 'checkbox' },
        { name: 'showSitecatalyst', description: 'show Adobe Analytics tags', type: 'checkbox' },
        { name: 'ignoredTags', description: Ignored IDs (separated by semicolons), type: 'input', placeholder: 'UA-XXX-Y;UA-AAA-B' },
        { name: 'dataLayers', description: Additional data layer objects (separated by semicolons), type: 'input', placeholder: 'digitalData;testDataLayer' },
      ]
    };

    const versionHistory = [
      {
        version: '1.1',
        changes: [
        'initial support for Adobe Launch',
        'toggle friendly param names',
        'toggle param URL decoding',
        ]
      },
      {
        version: '1.0',
        changes: [
        'rewritten UI',
        'import/export functionality',
        'auto collapse gtm.*',
        'fix displayed GTM IDs',
        'misc. bug fixes'
        ]
      }
    ];

    return (
      <div id={`sub${this.props.index}`} className={`pure-menu pure-menu-open`}>

            dataslayer Options

              {this.state.version}

                {
                  Object.keys(optionMap).map((section) =>
                    (<form className="pure-form pure-form-stacked" key={section}>

                              {section}

                      {
                        optionMap[section].map((option) =>
                        (

                                {
                                  option.type === 'checkbox' &&
                                  (

                                      <input
                                        id={option.name}
                                        type="checkbox"
                                        checked={this.props.options[option.name]}
                                        onChange={this.optionUpdater('checkbox', option.name)}
                                      />
                                      {option.description}

                                  	)
                                }
                                {
                                  option.type === 'input' &&
                                  (

                                    {option.description}

                                    <input
                                      placeholder={option.placeholder}
                                      defaultValue={(this.props.options[option.name] || []).join(';')}
                                      onChange={this.optionUpdater('input', option.name)}
                                    />

                                  	)
                                }

)
                        )
                      }
                    </form>)
                  )
                }

                        About

                        by
                        Sean Adams

                        contributors

                        documentation
                        dataslayer.org/documentation

                        open source
                        GitHub

                        report bug / request feature
                        GitHub issues (preferred)

                        email

                        leave a review

                            Chrome Web Store

                        links
                        dataslayer.org

                            Chrome Web Store

                            release notes

                        made with
                        React

                        Pure

                        jQuery

                        data-layer-helper

                        leanModal

                        O.o polyfill by MaxArt2501

                            Open Sans

                            (license)

                            Madebyoliver icons
                            from www.flaticon.com

What's New

                            x.x.* are bug fix releases

                        {
                          versionHistory.map(({ version, changes }) =>
                            (

                              {version}

                                {changes.map((note, i) => (<span key={`${note}i`}>{note}
))}

)
                          )
                        }

    );
  }
}

Options.propTypes = {
  options: PropTypes.object,
  setOption: PropTypes.func,
};

export default Options;
<|endoftext|>