<commit_before>(function(root) {

// TODO: Define the following
// - nextSection
// - sectionType

// Ugly (yet minimal) hack to make it work on node and on the browser
try {require} catch(e) {require = function(){}};
function requireAs(libpath, varname) {
    if (!root[varname])
        return require(libpath);
	return root[varname];
}

var _ = requireAs("underscore", "_");

var MARKER = {
    LINK:       "#",
    TITLE:      "*",
    CHAPTER:    "**",
    SCENE:      "***",
    SCRIPT_END: "************",
	DEFAULTS: "---[defaults]---",
	FIGURES:    "---[figures]---",
}

var TPAIRS = {
    LINK:   ['[' ,  ']'],
    CHAP:   ['{' ,  '}'],
    IMG:    ['<' ,  '>'],
    INPUT:  ['--', '--'],
    VAR:    ['++', '++'],
};

function parseScript(scripts) {
    var lines = scripts.split('\n');
    var index = 0;

    var titleIndex = getTitleIndex(index, lines);
    if (titleIndex < 0) {
        return {
            storyTitle: "",
            synopsis: "",
            chapters: [],
            error: "Story title not found"
        };
    }

    return {
        storyTitle: trimMarker(lines[titleIndex]).trim(),
        synopsis: findSynopsis(titleIndex, lines).trim(),
        chapters: parseChapters(lines),
        defaultBindings: parseBindings(lines, titleIndex),
		mappings: getMappings(lines),
		figures: parseFigures(lines),
    }
}

function makeLink(key, val) {
	return MARKER.LINK+" "+key+": "+val;
}

function updateFigures(figures, script) {
	figures = _.clone(figures);
	var lines = script.split("\n");
	var i = nextFigureIndex(0, lines);
	var end = nextLineMarkers(i+1, lines);
	if (end < 0) end = lines.length;

	if (i < 0) {
		lines.push(MARKER.FIGURES);
		i = end;
	}

	while(i < end) {
		var j = nextLinkIndex(i, lines);
		if (j < 0)
			break;
		i = j;
		var m = parseLink(lines[i]);
		var val = figures[m.key];
		if (val) {
			lines[i] = makeLink(m.key, val);
			delete figures[m.key];
		} else if (val === null){
			lines.splice(i, 1); // delete
			i--;
			delete figures[m.key];
		}
	}

	var links = _.map(figures, function(val, key) {
		return makeLink(key, val);
	});
	lines.splice.apply(lines, [i+1, -1].concat(links));
	return lines.join("\n");
}

function parseFigures(lines) {
	var  i = 0;
	while (i < lines.length) {
		var line = lines[i];
		if (isMarkedWith(line, MARKER.FIGURES))
			break;
		i++;
	}
	var end = nextFigureIndex(i, lines);
	if (end < 0) end = lines.length;
    lines = lines.slice(i, end);
    return getMappings(lines) || {};
}

function parseBindings(lines, index) {
    var i = nextChapterIndex(index, lines);
    if (i < 0) {
        i = lines.length;
    }
    lines = lines.slice(index+1, i);
    return getMappings(lines) || {};
}

function parseChapters(lines, index) {
    lines.push(MARKER.SCRIPT_END);
    var grouped = groupWith(lines, index, nextChapterIndex);

	var warnings = [];
	var counter = new Counter();
    var chapters =  _.map(grouped, function(lines) {
        var chapterData = parseMarkedData(lines[0]);
        var scenes = [];
        if (lines.length >= 2) {
            var scenes = parseScenes(lines, 0);
        }
		var label = chapterData.label;

		counter.inc(label);
		if (counter.get(label) > 1)
			warnings.push("Chapter label " + label + " is duplicated");

        return {
            label: chapterData.label,
            title: chapterData.text,
            scenes: scenes,
        }
        // - combine each mappings of each scene
        // - scene.label -> scene.label
        // - create a tree from the mappings
        // - where the root is the first scene
        // - give each path a unique label
    });
	chapters.warnings = warnings;
	return chapters;
}

function Counter() {
	this.count = 0;
	this.store = {};
}
Counter.prototype.get = function(key) {
	return this.store[key] || 0;
}
Counter.prototype.inc = function(key) {
	var store = this.store;
	if (!store[key])
		store[key] = 0;
	store[key]++;
}

function parseScenes(lines, index) {
    lines.push(MARKER.SCRIPT_END);
    var grouped = groupWith(lines, index, nextSceneIndex);

	var warnings = [];
	var counter = new Counter();
    var scenes = _.map(grouped, function(lines) {

        var sceneData = parseMarkedData(lines[0]);
        var text = getSceneText(lines, 1).trim();
        var mappings = getMappings(lines);
        var contents = parseSceneText(text);
		var label = sceneData.label;

		counter.inc(label);
		if (counter.get(label) > 1)
			warnings.push("Scene label " + label + " is duplicated");

        return {
            label: label,
            title: sceneData.text,
            contents: contents,
            mappings: mappings,
        }
    });
	scenes.warnings = warnings;
	return scenes;
}

var tokenPattern = buildPattern();

function parseSceneText(text) {
    var startIndex = -1;
    var startChar;

    var tokens = [];

	//var pattern = buildPattern();
    while (true) {
        var startIndex = text.search(tokenPattern);
        var match = tokenPattern.exec(text);

        if (startIndex < 0)
            break;

        subtext = match[1];
        var tokenObject = Token.parse(subtext);
        //var tokenObject = subtext;
        var endIndex = startIndex + subtext.length;
        var preText = text.slice(0, startIndex);

        if (preText != "")
            tokens.push(preText);
        tokens.push(tokenObject);

        text = text.slice(endIndex, text.length);
    }
    if (text)
        tokens.push(text);

    return tokens;
}

function groupWith(lines, startIndex, nextfn) {
    var indices = getIndices(lines, startIndex, nextfn);
    indices.push(lines.length-1);

    return _.map(groupByTwos(indices), function(pair) {
        return lines.slice(pair.fst, pair.snd);
    });
}

function parseLink(line) {
	var line = trimMarker(line);

	var colonIndex = line.indexOf(":");
	if (colonIndex < 0)
		return null;;
	var key = line.slice(0, colonIndex).trim();
	var val = line.slice(colonIndex+1, line.length).trim();
	return {key: key, val: val};
}

function getMappings(lines) {
    var indices = getIndices(lines, 0, nextLinkIndex);

    return _.reduce(indices, function(result, i) {
        var line = trimMarker(lines[i]);
		var link = parseLink(line);
		if (link)
			result[link.key] = link.val;
		return result;

		//var colonIndex = line.indexOf(":");
        //if (colonIndex < 0)
        //    return result;
        //var src = line.slice(0, colonIndex).trim();
        //var dest = line.slice(colonIndex+1, line.length).trim();

		//result[m.val] = dest;
        return result;
    }, {});
}

function beginningPairs() {
    return _.map(_.values(TPAIRS), function(t) {
        return _.keys(t)[0];
    });
}

function getSceneText(lines, index) {
    index = index || 0;
    var end = nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.LINK);
    });
    if (end < 0)
        end = lines.length;
    return lines.slice(index, end).join("\n");
}

//var RX_LABELED = /^(\(\w+\)) (.*)$/;
var RX_LABELED = /^\(([^\s]+)\) (.*)$/;

function parseMarkedData(s) {
    var match = trimMarker(s).match(RX_LABELED);

    var data = {};
    if (match) {
        data = {
            label: match[1],
            text: match[2],
        }
    } else {
        data = { label: s, title: s}
    }

    return data;
}

function groupByTwos(coll) {
    var groups = [];
    for (var i = 0; i < coll.length - 1; i++) {
        groups.push({
            fst: coll[i],
            snd: coll[i+1],
        });
    }
    return groups;
}

function getSceneIndices(lines, startIndex) {
    return getIndices(lines, startIndex, nextSceneIndex);
}

function getChapterIndices(lines, startIndex) {
    return getIndices(lines, startIndex, nextChapterIndex);
}

function getIndices(lines, startIndex, pred) {
    var i = startIndex = startIndex || 0;

    var indices = [];
    while (true) {
        i = pred(i, lines);
        if (i < 0)
            break;
        indices.push(i);
    }

    return indices;
}

function findSynopsis(titleIndex, lines) {
    var i = nextMarker(titleIndex, lines);
    return lines.slice(titleIndex+1, i).join("\n").trim();
}

// TODO: Rewrite
function getTitleIndex(index, lines) {
    for (var i = index; i < lines.length; i++) {
        if (isStoryTitle(lines[i]))
            return i;
    }
    return -1;
}

function isStoryTitle(s) {
    // TODO: Rewrite to use marker
    return s.match(/^\* .+/);
}

function nextLinkIndex(index, lines) {
    return nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.LINK);
    });
}

function nextSceneIndex(index, lines) {
    return nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.SCENE);
    });
}

function nextChapterIndex(index, lines) {
    return nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.CHAPTER);
    });
}

function nextLineMarkers(index, lines) {
    var next = nextIndex(index, lines, function(line) {
		return isMarkedWith(line, MARKER.FIGURES) ||
			isMarkedWith(line, MARKER.DEFAULTS);
    });
	return next;
}

function nextFigureIndex(index, lines) {
    var next = nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.FIGURES);
    });
	return next;
}

function nextMarker(startIndex, lines) {
    var i =  nextIndex(startIndex, lines, isMarked);
	if (i < 0)
		return lines.length;
	return i;
}

function nextIndex(index, lines, pred) {
    for (var i = index+1; i < lines.length; i++) {
        if (pred(lines[i]))
            return i;
    }
    return -1;
}

function isMarked(s) {
    return _.some(_.values(MARKER), function(marker) {
        return isMarkedWith(s, marker);
    });
}

function isMarkedWith(s, marker) {
	return startsWith(s, marker+" ") ||
		s === marker;
}

function trimMarker(s) {
    if (!s) return "";
    return _.reduce(_.values(MARKER), function(result, marker) {
        marker = escapeRegExp(marker);
        return result.replace(new RegExp("^"+marker+" "), "");
    }, s);
}

// Based on an MDN page
// https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
function escapeRegExp(string){
  return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}

function concat() {
    return _.toArray(arguments).join("");
}

function wrap(name, text) {
    var pair = TPAIRS[name];
    if (pair) {
        return concat(pair[0], text, pair[1]);
    }
    return text;
}

function Token() { }

Token.create = function(name, text) {
    var self = { name: name, text: text };
    var fn = Token.processors[name];
    if (fn) {
        fn(self, text);
    }
    return self;
}

Token.processors = {

    LINK: splitLabel,
    CHAP: splitLabel,

    INPUT:  function(self, text) {
        var split = text.split(":");

        if (split.length == 2) {
            self.label = split[0];
            split = split[1].split(",").map(function(x) {
                return x.trim();
            });

			self.text = split[0];
			self.buttonVal = "ok";
            if (split.length >= 2) {
                self.buttonVal = split[1];
            }
        }
    },

    IMG: function (self, text) {
        var split = text.split(":");
        if (split.length == 2) {
            self.label = split[0];
            self.text = split[1];
        } else {
            self.label = text;
            self.text = '';
        }
    },
}

Token.serialize = function(t) {
    return wrap(t.name, t.text);
}

Token.parse = function(text) {
    var keys = _.keys(TPAIRS);
    for (var i = 0; i < keys.length; i++) {
        var name = keys[i];
        var wrapper = TPAIRS[name];
        var opening = wrapper[0];
        var closing = wrapper[1];

        if (startsWith(text, opening)) {
            //return new Token(name,
            //text.slice(opening.length, text.length-closing.length));
            return Token.create(name,
                text.slice(opening.length, text.length-closing.length));
        }
    }
    return text;
}

function splitLabel(self, text) {
    var split = text.split(":");
    if (split.length == 2) {
        self.label = split[0];
        text = split[1];
    }
    self.text = text;
}

function listTokenNames() {
    return _.keys(TPAIRS);
}

function buildPattern() {
    var patterns = [];
    var esc = escapeRegExp;
    _.each(_.values(TPAIRS), function(pair) {
		//patterns.push([esc(pair[0]), ".+?", esc(pair[1])].join(""));
		var open = esc(pair[0]);
		var close = esc(pair[1]);
		patterns.push([
			open,
			".+?",
			close,
		].join(""));
    });
    return new RegExp("("+patterns.join("|")+")");
}

function randSelect(coll) {
    return coll[Math.floor(Math.random()*coll.length)];
}

function randomString() {
    var s = Math.random().toString(36);
    return s.slice(2, 3+Math.random()*s.length);
}

function intercalate(xs, e) {
    if (xs.length == 1)
        return xs;

    if (typeof e !== "function")
        e = _.constant(e);

    var ys = [];
    ys.push(xs[0]);
    for (var i = 1; i < xs.length; i++) {
        ys.push(e());
        ys.push(xs[i]);
    }
    return ys;
}

function startsWith(s, prefix) {
    var i = 0;
    while (s[i] && s[i] == prefix[i])
        i++;
    return prefix[i] == null;
}

root || (root = {});
root.parseScript = parseScript;
root.updateFigures = updateFigures;

})(this);

<commit_msg>Fix scene text parsing<commit_after>(function(root) {

// TODO: Define the following
// - nextSection
// - sectionType

// Ugly (yet minimal) hack to make it work on node and on the browser
try {require} catch(e) {require = function(){}};
function requireAs(libpath, varname) {
    if (!root[varname])
        return require(libpath);
	return root[varname];
}

var _ = requireAs("underscore", "_");

var MARKER = {
    LINK:       "#",
    TITLE:      "*",
    CHAPTER:    "**",
    SCENE:      "***",
    SCRIPT_END: "************",
	DEFAULTS: "---[defaults]---",
	FIGURES:    "---[figures]---",
}

var TPAIRS = {
    LINK:   ['[' ,  ']'],
    CHAP:   ['{' ,  '}'],
    IMG:    ['<' ,  '>'],
    INPUT:  ['--', '--'],
    VAR:    ['++', '++'],
};

function parseScript(scripts) {
    var lines = scripts.split('\n');
    var index = 0;

    var titleIndex = getTitleIndex(index, lines);
    if (titleIndex < 0) {
        return {
            storyTitle: "",
            synopsis: "",
            chapters: [],
            error: "Story title not found"
        };
    }

    return {
        storyTitle: trimMarker(lines[titleIndex]).trim(),
        synopsis: findSynopsis(titleIndex, lines).trim(),
        chapters: parseChapters(lines),
        defaultBindings: parseBindings(lines, titleIndex),
		mappings: getMappings(lines),
		figures: parseFigures(lines),
    }
}

function makeLink(key, val) {
	return MARKER.LINK+" "+key+": "+val;
}

function updateFigures(figures, script) {
	figures = _.clone(figures);
	var lines = script.split("\n");
	var i = nextFigureIndex(0, lines);
	var end = nextLineMarkers(i+1, lines);
	if (end < 0) end = lines.length;

	if (i < 0) {
		lines.push(MARKER.FIGURES);
		i = end;
	}

	while(i < end) {
		var j = nextLinkIndex(i, lines);
		if (j < 0)
			break;
		i = j;
		var m = parseLink(lines[i]);
		var val = figures[m.key];
		if (val) {
			lines[i] = makeLink(m.key, val);
			delete figures[m.key];
		} else if (val === null){
			lines.splice(i, 1); // delete
			i--;
			delete figures[m.key];
		}
	}

	var links = _.map(figures, function(val, key) {
		return makeLink(key, val);
	});
	lines.splice.apply(lines, [i+1, -1].concat(links));
	return lines.join("\n");
}

function parseFigures(lines) {
	var  i = 0;
	while (i < lines.length) {
		var line = lines[i];
		if (isMarkedWith(line, MARKER.FIGURES))
			break;
		i++;
	}
	var end = nextFigureIndex(i, lines);
	if (end < 0) end = lines.length;
    lines = lines.slice(i, end);
    return getMappings(lines) || {};
}

function parseBindings(lines, index) {
    var i = nextChapterIndex(index, lines);
    if (i < 0) {
        i = lines.length;
    }
    lines = lines.slice(index+1, i);
    return getMappings(lines) || {};
}

function parseChapters(lines, index) {
    lines.push(MARKER.SCRIPT_END);
    var grouped = groupWith(lines, index, nextChapterIndex);

	var warnings = [];
	var counter = new Counter();
    var chapters =  _.map(grouped, function(lines) {
        var chapterData = parseMarkedData(lines[0]);
        var scenes = [];
        if (lines.length >= 2) {
            var scenes = parseScenes(lines, 0);
        }
		var label = chapterData.label;

		counter.inc(label);
		if (counter.get(label) > 1)
			warnings.push("Chapter label " + label + " is duplicated");

        return {
            label: chapterData.label,
            title: chapterData.text,
            scenes: scenes,
        }
        // - combine each mappings of each scene
        // - scene.label -> scene.label
        // - create a tree from the mappings
        // - where the root is the first scene
        // - give each path a unique label
    });
	chapters.warnings = warnings;
	return chapters;
}

function Counter() {
	this.count = 0;
	this.store = {};
}
Counter.prototype.get = function(key) {
	return this.store[key] || 0;
}
Counter.prototype.inc = function(key) {
	var store = this.store;
	if (!store[key])
		store[key] = 0;
	store[key]++;
}

function parseScenes(lines, index) {
    lines.push(MARKER.SCRIPT_END);
    var grouped = groupWith(lines, index, nextSceneIndex);

	var warnings = [];
	var counter = new Counter();
    var scenes = _.map(grouped, function(lines) {

        var sceneData = parseMarkedData(lines[0]);
        var text = getSceneText(lines, 1).trim();
        var mappings = getMappings(lines);
        var contents = parseSceneText(text);
		var label = sceneData.label;

		counter.inc(label);
		if (counter.get(label) > 1)
			warnings.push("Scene label " + label + " is duplicated");

        return {
            label: label,
            title: sceneData.text,
            contents: contents,
            mappings: mappings,
        }
    });
	scenes.warnings = warnings;
	return scenes;
}

var tokenPattern = buildPattern();

function parseSceneText(text) {
    var startIndex = -1;
    var startChar;

    var tokens = [];

	//var pattern = buildPattern();
    while (true) {
        var startIndex = text.search(tokenPattern);
        var match = tokenPattern.exec(text);

        if (startIndex < 0)
            break;

        subtext = match[1];
        var tokenObject = Token.parse(subtext);
        //var tokenObject = subtext;
        var endIndex = startIndex + subtext.length;
        var preText = text.slice(0, startIndex);

        if (preText != "")
            tokens.push(preText);
        tokens.push(tokenObject);

        text = text.slice(endIndex, text.length);
    }
    if (text)
        tokens.push(text);

    return tokens;
}

function groupWith(lines, startIndex, nextfn) {
    var indices = getIndices(lines, startIndex, nextfn);
    indices.push(lines.length-1);

    return _.map(groupByTwos(indices), function(pair) {
        return lines.slice(pair.fst, pair.snd);
    });
}

function parseLink(line) {
	var line = trimMarker(line);

	var colonIndex = line.indexOf(":");
	if (colonIndex < 0)
		return null;;
	var key = line.slice(0, colonIndex).trim();
	var val = line.slice(colonIndex+1, line.length).trim();
	return {key: key, val: val};
}

function getMappings(lines) {
    var indices = getIndices(lines, 0, nextLinkIndex);

    return _.reduce(indices, function(result, i) {
        var line = trimMarker(lines[i]);
		var link = parseLink(line);
		if (link)
			result[link.key] = link.val;
		return result;

		//var colonIndex = line.indexOf(":");
        //if (colonIndex < 0)
        //    return result;
        //var src = line.slice(0, colonIndex).trim();
        //var dest = line.slice(colonIndex+1, line.length).trim();

		//result[m.val] = dest;
        return result;
    }, {});
}

function beginningPairs() {
    return _.map(_.values(TPAIRS), function(t) {
        return _.keys(t)[0];
    });
}

function getSceneText(lines, index) {
    index = index || 0;
	var end = nextFigureIndex(index, lines);
    if (end < 0)
        end = nextLinkIndex(index, lines);
	if (end < 0)
		end = lines.length;
    return lines.slice(index, end).join("\n");
}

//var RX_LABELED = /^(\(\w+\)) (.*)$/;
var RX_LABELED = /^\(([^\s]+)\) (.*)$/;

function parseMarkedData(s) {
    var match = trimMarker(s).match(RX_LABELED);

    var data = {};
    if (match) {
        data = {
            label: match[1],
            text: match[2],
        }
    } else {
        data = { label: s, title: s}
    }

    return data;
}

function groupByTwos(coll) {
    var groups = [];
    for (var i = 0; i < coll.length - 1; i++) {
        groups.push({
            fst: coll[i],
            snd: coll[i+1],
        });
    }
    return groups;
}

function getSceneIndices(lines, startIndex) {
    return getIndices(lines, startIndex, nextSceneIndex);
}

function getChapterIndices(lines, startIndex) {
    return getIndices(lines, startIndex, nextChapterIndex);
}

function getIndices(lines, startIndex, pred) {
    var i = startIndex = startIndex || 0;

    var indices = [];
    while (true) {
        i = pred(i, lines);
        if (i < 0)
            break;
        indices.push(i);
    }

    return indices;
}

function findSynopsis(titleIndex, lines) {
    var i = nextMarker(titleIndex, lines);
    return lines.slice(titleIndex+1, i).join("\n").trim();
}

// TODO: Rewrite
function getTitleIndex(index, lines) {
    for (var i = index; i < lines.length; i++) {
        if (isStoryTitle(lines[i]))
            return i;
    }
    return -1;
}

function isStoryTitle(s) {
    // TODO: Rewrite to use marker
    return s.match(/^\* .+/);
}

function nextLinkIndex(index, lines) {
    return nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.LINK);
    });
}

function nextSceneIndex(index, lines) {
    return nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.SCENE);
    });
}

function nextChapterIndex(index, lines) {
    return nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.CHAPTER);
    });
}

function nextLineMarkers(index, lines) {
    var next = nextIndex(index, lines, function(line) {
		return isMarkedWith(line, MARKER.FIGURES) ||
			isMarkedWith(line, MARKER.DEFAULTS);
    });
	return next;
}

function nextFigureIndex(index, lines) {
    var next = nextIndex(index, lines, function(line) {
        return isMarkedWith(line, MARKER.FIGURES);
    });
	return next;
}

function nextMarker(startIndex, lines) {
    var i =  nextIndex(startIndex, lines, isMarked);
	if (i < 0)
		return lines.length;
	return i;
}

function nextIndex(index, lines, pred) {
    for (var i = index+1; i < lines.length; i++) {
        if (pred(lines[i]))
            return i;
    }
    return -1;
}

function isMarked(s) {
    return _.some(_.values(MARKER), function(marker) {
        return isMarkedWith(s, marker);
    });
}

function isMarkedWith(s, marker) {
	return startsWith(s, marker+" ") ||
		s === marker;
}

function trimMarker(s) {
    if (!s) return "";
    return _.reduce(_.values(MARKER), function(result, marker) {
        marker = escapeRegExp(marker);
        return result.replace(new RegExp("^"+marker+" "), "");
    }, s);
}

// Based on an MDN page
// https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
function escapeRegExp(string){
  return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}

function concat() {
    return _.toArray(arguments).join("");
}

function wrap(name, text) {
    var pair = TPAIRS[name];
    if (pair) {
        return concat(pair[0], text, pair[1]);
    }
    return text;
}

function Token() { }

Token.create = function(name, text) {
    var self = { name: name, text: text };
    var fn = Token.processors[name];
    if (fn) {
        fn(self, text);
    }
    return self;
}

Token.processors = {

    LINK: splitLabel,
    CHAP: splitLabel,

    INPUT:  function(self, text) {
        var split = text.split(":");
		self.buttonVal = "ok";
        if (split.length == 2) {
            self.label = split[0];
            split = split[1].split(",").map(function(x) {
                return x.trim();
            });

			self.text = split[0];
            if (split.length >= 2) {
                self.buttonVal = split[1];
            }
        }
    },

    IMG: function (self, text) {
        var split = text.split(":");
        if (split.length == 2) {
            self.label = split[0];
            self.text = split[1];
        } else {
            self.label = text;
            self.text = '';
        }
    },
}

Token.serialize = function(t) {
    return wrap(t.name, t.text);
}

Token.parse = function(text) {
    var keys = _.keys(TPAIRS);
    for (var i = 0; i < keys.length; i++) {
        var name = keys[i];
        var wrapper = TPAIRS[name];
        var opening = wrapper[0];
        var closing = wrapper[1];

        if (startsWith(text, opening)) {
            //return new Token(name,
            //text.slice(opening.length, text.length-closing.length));
            return Token.create(name,
                text.slice(opening.length, text.length-closing.length));
        }
    }
    return text;
}

function splitLabel(self, text) {
    var split = text.split(":");
    if (split.length == 2) {
        self.label = split[0];
        text = split[1];
    }
    self.text = text;
}

function listTokenNames() {
    return _.keys(TPAIRS);
}

function buildPattern() {
    var patterns = [];
    var esc = escapeRegExp;
    _.each(_.values(TPAIRS), function(pair) {
		//patterns.push([esc(pair[0]), ".+?", esc(pair[1])].join(""));
		var open = esc(pair[0]);
		var close = esc(pair[1]);
		patterns.push([
			open,
			".+?",
			close,
		].join(""));
    });
    return new RegExp("("+patterns.join("|")+")");
}

function randSelect(coll) {
    return coll[Math.floor(Math.random()*coll.length)];
}

function randomString() {
    var s = Math.random().toString(36);
    return s.slice(2, 3+Math.random()*s.length);
}

function intercalate(xs, e) {
    if (xs.length == 1)
        return xs;

    if (typeof e !== "function")
        e = _.constant(e);

    var ys = [];
    ys.push(xs[0]);
    for (var i = 1; i < xs.length; i++) {
        ys.push(e());
        ys.push(xs[i]);
    }
    return ys;
}

function startsWith(s, prefix) {
    var i = 0;
    while (s[i] && s[i] == prefix[i])
        i++;
    return prefix[i] == null;
}

root || (root = {});
root.parseScript = parseScript;
root.updateFigures = updateFigures;

})(this);

<|endoftext|>


<commit_before>/*jshint node:true*/

'use strict';

var fs = require('fs');
var glob = require('glob');
var async = require('async');
var path  = require('path');
var utils = require('amd-utils');
var optimizer = require('../optimizer');

module.exports = {
    id          : 'dejavu-optimizer',
    author      : 'Indigo United',
    name        : 'dejavu optimizer',
    description : 'Optimize dejavu usages.',
    options: {
        files: {
            description: 'Which files should be optimized. Accepts an object in which keys are the source files and values the destination. Source values support minimatch.'
        },
        closure: {
            description: 'Optimize using closures. This has better performance for nodejs, chrome and safari but worst for Firefox and IE',
            'default': false
        },
        glob: {
            description: 'The options to pass to glob (check https://npmjs.org/package/glob for details).',
            'default': null
        }
    },
    tasks: [
        {
            task: function (opt, ctx, next) {
                opt.glob = opt.glob || {};
                var optimizerOpts = { closure: opt.closure },
                    sources = Object.keys(opt.files),
                    error;

                // Cycle through each source
                // Note that series is used to avoid conflicts between each pattern
                async.forEachSeries(sources, function (pattern, next) {
                    var dsts = utils.lang.isArray(opt.files[pattern]) ? opt.files[pattern] : [opt.files[pattern]];
                    dsts = utils.array.unique(dsts.map(function (dst) { return path.normalize(dst); }));
                    pattern = path.normalize(pattern);

                    // Expand the files
                    opt.glob.mark = true;
                    glob(pattern, opt.glob, function (err, matches) {
                        if (err) {
                            return next(err);
                        }

                        var files = matches.filter(function (match) {
                            return !utils.string.endsWith(match, '/');
                        });

                        if (!files.length) {
                            error = new Error('ENOENT, no such file \'' + pattern + '\'');
                            error.code = 'ENOENT';
                            return next(error);
                        }

                        async.forEachLimit(files, 30, function (file, next) {
                            ctx.log.debugln('Optimizing file: ', file);

                            var contents = fs.readFile(file, function (err) {
                                if (err) {
                                    return next(err);
                                }

                                optimizer(contents, optimizerOpts, function (errors, contents) {
                                    errors.forEach(function (err) {
                                        ctx.log.warnln(err.message);
                                    });
                                    fs.writeFile(file, contents, next);
                                });
                            });
                        }, next);
                    });
                }, next);
            }
        }
    ]
};<commit_msg>Optimizer task was not using destinations.<commit_after>/*jshint node:true, latedef:false*/

'use strict';

var fs = require('fs');
var glob = require('glob');
var async = require('async');
var path  = require('path');
var utils = require('amd-utils');
var optimizer = require('../optimizer');

module.exports = {
    id          : 'dejavu-optimizer',
    author      : 'Indigo United',
    name        : 'dejavu optimizer',
    description : 'Optimize dejavu usages.',
    options: {
        files: {
            description: 'Which files should be optimized. Accepts an object in which keys are the source files and values the destination. Source values support minimatch.'
        },
        closure: {
            description: 'Optimize using closures. This has better performance for nodejs, chrome and safari but worst for Firefox and IE',
            'default': false
        },
        glob: {
            description: 'The options to pass to glob (check https://npmjs.org/package/glob for details).',
            'default': null
        }
    },
    tasks: [
        {
            task: function (opt, ctx, next) {
                opt.glob = opt.glob || {};
                var optimizerOpts = { closure: opt.closure },
                    sources = Object.keys(opt.files),
                    error;

                // Cycle through each source
                // Note that series is used to avoid conflicts between each pattern
                async.forEachSeries(sources, function (pattern, next) {
                    var dsts = utils.lang.isArray(opt.files[pattern]) ? opt.files[pattern] : [opt.files[pattern]];
                    dsts = utils.array.unique(dsts.map(function (dst) { return path.normalize(dst); }));
                    pattern = path.normalize(pattern);

                    // Expand the files
                    opt.glob.mark = true;
                    glob(pattern, opt.glob, function (err, matches) {
                        if (err) {
                            return next(err);
                        }

                        var files = matches.filter(function (match) {
                            return !utils.string.endsWith(match, '/');
                        });

                        if (!files.length) {
                            error = new Error('ENOENT, no such file \'' + pattern + '\'');
                            error.code = 'ENOENT';
                            return next(error);
                        }

                        async.forEachLimit(files, 30, function (file, next) {
                            ctx.log.debugln('Optimizing file: ', file);

                            var contents = fs.readFile(file, function (err) {
                                if (err) {
                                    return next(err);
                                }

                                optimizer(contents, optimizerOpts, function (errors, contents) {
                                    errors.forEach(function (err) {
                                        ctx.log.warnln(err.message);
                                    });

                                    var relative = relativePath(file, pattern);
                                    async.forEach(dsts, function (dst) {
                                        fs.writeFile(path.join(dst, relative), contents, next);
                                    });
                                });
                            });
                        }, next);
                    });
                }, next);
            }
        }
    ]
};

/**
 * Gets the relative path of a file relative to the pattern.
 * For instance:
 *   file = /a/b.js
 *   pattern = /a/*
 *
 * Should return b.js
 *
 * @param {String} file    The file
 * @param {String} pattern The pattern
 *
 * @return {String} The relative path
 */
function relativePath(file, pattern) {
    var length = file.length,
        x;

    pattern = path.normalize(pattern);

    for (x = 0; x < length; x += 1) {
        if (file[x] !== pattern[x]) {
            return file.substr(x);
        }
    }

    return path.basename(file);
}
<|endoftext|>


<commit_before>/**
 * This file is part of Superdesk.
 *
 * Copyright 2015 Sourcefabric z.u. and contributors.
 *
 * For the full copyright and license information, please see the
 * AUTHORS and LICENSE files distributed with this source code, or
 * at https://www.sourcefabric.org/superdesk/license
 */
(function() {

'use strict';

WorkqueueService.$inject = ['session', 'api'];
function WorkqueueService(session, api) {

    this.items = [];

    /**
     * Get all items locked by current user
     */
    this.fetch = function() {
        return session.getIdentity()
            .then(angular.bind(this, function(identity) {
                return api.query('archive', {source: {filter: {term: {lock_user: identity._id}}}})
                    .then(angular.bind(this, function(res) {
                        this.items = null;
                        this.items = res._items || [];
                        return this.items;
                    }));
            }));
    };

    /**
     * Update given item
     */
    this.updateItem = function(itemId) {
        var old = _.find(this.items, {_id: itemId});
        if (old) {
            return api.find('archive', itemId).then(function(item) {
                return angular.extend(old, item);
            });
        }
    };
}

WorkqueueCtrl.$inject = [
    '$scope',
    '$route',
    'workqueue',
    'authoringWorkspace',
    'multiEdit',
    'superdesk',
    'lock',
    '$location',
    'session',
    'authoring'
];
function WorkqueueCtrl($scope, $route, workqueue, authoringWorkspace, multiEdit, superdesk, lock, $location, session, authoring) {

    $scope.active = null;
    $scope.workqueue = workqueue;
    $scope.multiEdit = multiEdit;

    $scope.$on('$locationChangeSuccess', updateWorkqueue);
    $scope.$on('item:lock', updateWorkqueue);
    $scope.$on('item:unlock', function (_e, data) {
        var item = _.find(workqueue.items, {_id: data.item});
        if (item && session.sessionId !== data.lock_session && $scope.active !== item) {
            authoring.unlock(item, data.user, item.headline);
        }

        if (item && item.linked_in_packages) {
            _.each(item.linked_in_packages, function(item) {
                var pck = _.find(workqueue.items, {_id: item.package});
                if (pck) {
                    authoringWorkspace.edit(pck);
                }
            });
        }

        updateWorkqueue();
    });
    $scope.$on('media_archive', function(e, data) {
        workqueue.updateItem(data.item);
    });

    updateWorkqueue();

    /**
     * Update list of opened items and set one active if its id is in current route path.
     */
    function updateWorkqueue() {
        workqueue.fetch().then(function() {
            var route = $route.current || {_id: null, params: {}};
            $scope.isMultiedit = route._id === 'multiedit';
            $scope.active = null;
            if (route.params.item) {
                $scope.active = _.find(workqueue.items, {_id: route.params.item});
            }
        });
    }

    $scope.openDashboard = function() {
        superdesk.intent('author', 'dashboard');
    };

    /**
     * Closes item. If item is opened, close authoring workspace.
     * Updates multiedit items, if item is part of multiedit.
     * When closing last item that was in multiedit(no more items in multiedit), redirects to monitoring.
     */
    $scope.closeItem = function(item) {
        lock.unlock(item);
        if (authoringWorkspace.item && item._id === authoringWorkspace.item._id){
            authoringWorkspace.close(true);
        }

        multiEdit.items = _.without(multiEdit.items, _.find(multiEdit.items, {article: item._id}));
        if (multiEdit.items.length === 0){
            $scope.redirectOnCloseMulti();
        }
    };

    $scope.openMulti = function() {
        $scope.isMultiedit = true;
        updateWorkqueue();
        multiEdit.open();
    };

    /**
     * Close multiedit.
     */
    $scope.closeMulti = function() {
        multiEdit.exit();
        $scope.redirectOnCloseMulti();
    };

    /**
     * If multi edit screen is opened, redirect to monitoring.
     */
    $scope.redirectOnCloseMulti = function() {
        if (this.isMultiedit){
            this.isMultiedit = false;
            $location.url('/workspace/monitoring');
        }
    };
}

WorkqueueListDirective.$inject = ['$rootScope', 'authoringWorkspace', '$location'];
function WorkqueueListDirective($rootScope, authoringWorkspace, $location) {
    return {
        templateUrl: 'scripts/superdesk-authoring/views/opened-articles.html',
        controller: 'Workqueue',
        scope: {},
        link: function(scope) {
            scope.edit = function(item, event) {
                if (!event.ctrlKey) {
                    scope.active = item;
                    authoringWorkspace.edit(item);
                    scope.redirectOnCloseMulti();

                    event.preventDefault();
                }
            };

            scope.link = function(item) {
                if (item) {
                    return $rootScope.link('authoring', item);
                }
            };
        }
    };
}

function ArticleDashboardDirective() {
    return {
        templateUrl: 'scripts/superdesk-authoring/views/dashboard-articles.html',
        controller: 'Workqueue'
    };
}

angular.module('superdesk.authoring.workqueue', [
    'superdesk.activity',
    'superdesk.notification',
    'superdesk.authoring.multiedit'
])
    .service('workqueue', WorkqueueService)
    .controller('Workqueue', WorkqueueCtrl)
    .directive('sdWorkqueue', WorkqueueListDirective)
    .directive('sdDashboardArticles', ArticleDashboardDirective);
})();
<commit_msg>fix(workqueue): SD-3775 Update workqueue headline on item save<commit_after>/**
 * This file is part of Superdesk.
 *
 * Copyright 2015 Sourcefabric z.u. and contributors.
 *
 * For the full copyright and license information, please see the
 * AUTHORS and LICENSE files distributed with this source code, or
 * at https://www.sourcefabric.org/superdesk/license
 */
(function() {

'use strict';

WorkqueueService.$inject = ['session', 'api'];
function WorkqueueService(session, api) {

    this.items = [];

    /**
     * Get all items locked by current user
     */
    this.fetch = function() {
        return session.getIdentity()
            .then(angular.bind(this, function(identity) {
                return api.query('archive', {source: {filter: {term: {lock_user: identity._id}}}})
                    .then(angular.bind(this, function(res) {
                        this.items = null;
                        this.items = res._items || [];
                        return this.items;
                    }));
            }));
    };

    /**
     * Update given item
     */
    this.updateItem = function(itemId) {
        var old = _.find(this.items, {_id: itemId});
        if (old) {
            return api.find('archive', itemId).then(function(item) {
                return angular.extend(old, item);
            });
        }
    };
}

WorkqueueCtrl.$inject = [
    '$scope',
    '$route',
    'workqueue',
    'authoringWorkspace',
    'multiEdit',
    'superdesk',
    'lock',
    '$location',
    'session',
    'authoring'
];
function WorkqueueCtrl($scope, $route, workqueue, authoringWorkspace, multiEdit, superdesk, lock, $location, session, authoring) {

    $scope.active = null;
    $scope.workqueue = workqueue;
    $scope.multiEdit = multiEdit;

    $scope.$on('$locationChangeSuccess', updateWorkqueue);
    $scope.$on('content:update', updateWorkqueue);
    $scope.$on('item:lock', updateWorkqueue);
    $scope.$on('item:unlock', function (_e, data) {
        var item = _.find(workqueue.items, {_id: data.item});
        if (item && session.sessionId !== data.lock_session && $scope.active !== item) {
            authoring.unlock(item, data.user, item.headline);
        }

        if (item && item.linked_in_packages) {
            _.each(item.linked_in_packages, function(item) {
                var pck = _.find(workqueue.items, {_id: item.package});
                if (pck) {
                    authoringWorkspace.edit(pck);
                }
            });
        }

        updateWorkqueue();
    });
    $scope.$on('media_archive', function(e, data) {
        workqueue.updateItem(data.item);
    });

    updateWorkqueue();

    /**
     * Update list of opened items and set one active if its id is in current route path.
     */
    function updateWorkqueue() {
        workqueue.fetch().then(function() {
            var route = $route.current || {_id: null, params: {}};
            $scope.isMultiedit = route._id === 'multiedit';
            $scope.active = null;
            if (route.params.item) {
                $scope.active = _.find(workqueue.items, {_id: route.params.item});
            }
        });
    }

    $scope.openDashboard = function() {
        superdesk.intent('author', 'dashboard');
    };

    /**
     * Closes item. If item is opened, close authoring workspace.
     * Updates multiedit items, if item is part of multiedit.
     * When closing last item that was in multiedit(no more items in multiedit), redirects to monitoring.
     */
    $scope.closeItem = function(item) {
        lock.unlock(item);
        if (authoringWorkspace.item && item._id === authoringWorkspace.item._id){
            authoringWorkspace.close(true);
        }

        multiEdit.items = _.without(multiEdit.items, _.find(multiEdit.items, {article: item._id}));
        if (multiEdit.items.length === 0){
            $scope.redirectOnCloseMulti();
        }
    };

    $scope.openMulti = function() {
        $scope.isMultiedit = true;
        updateWorkqueue();
        multiEdit.open();
    };

    /**
     * Close multiedit.
     */
    $scope.closeMulti = function() {
        multiEdit.exit();
        $scope.redirectOnCloseMulti();
    };

    /**
     * If multi edit screen is opened, redirect to monitoring.
     */
    $scope.redirectOnCloseMulti = function() {
        if (this.isMultiedit){
            this.isMultiedit = false;
            $location.url('/workspace/monitoring');
        }
    };
}

WorkqueueListDirective.$inject = ['$rootScope', 'authoringWorkspace', '$location'];
function WorkqueueListDirective($rootScope, authoringWorkspace, $location) {
    return {
        templateUrl: 'scripts/superdesk-authoring/views/opened-articles.html',
        controller: 'Workqueue',
        scope: {},
        link: function(scope) {
            scope.edit = function(item, event) {
                if (!event.ctrlKey) {
                    scope.active = item;
                    authoringWorkspace.edit(item);
                    scope.redirectOnCloseMulti();

                    event.preventDefault();
                }
            };

            scope.link = function(item) {
                if (item) {
                    return $rootScope.link('authoring', item);
                }
            };
        }
    };
}

function ArticleDashboardDirective() {
    return {
        templateUrl: 'scripts/superdesk-authoring/views/dashboard-articles.html',
        controller: 'Workqueue'
    };
}

angular.module('superdesk.authoring.workqueue', [
    'superdesk.activity',
    'superdesk.notification',
    'superdesk.authoring.multiedit'
])
    .service('workqueue', WorkqueueService)
    .controller('Workqueue', WorkqueueCtrl)
    .directive('sdWorkqueue', WorkqueueListDirective)
    .directive('sdDashboardArticles', ArticleDashboardDirective);
})();
<|endoftext|>


<commit_before>import Mixin from '@ember/object/mixin';
import { get, computed } from '@ember/object';
import { reads } from '@ember/object/computed';
import Fuse from 'npm:fuse.js';

/**
  Searchable mixin

  Simple search filtering behavior for a list of objects.

  Properties to override:
    - searchTerm: the string to use as a query
    - searchProps: the props on each object to search
    -- exactMatchSearchProps: the props for exact search when props are different per search type
    -- regexSearchProps: the props for regex search when props are different per search type
    -- fuzzySearchProps: the props for fuzzy search when props are different per search type
    - exactMatchEnabled: (true) disable to not use the exact match search type
    - fuzzySearchEnabled: (false) enable to use the fuzzy search type
    - regexEnabled: (true) disable to disable the regex search type
    - listToSearch: the list of objects to search

  Properties provided:
    - listSearched: a subset of listToSearch of items that meet the search criteria
*/
export default Mixin.create({
  searchTerm: '',
  listToSearch: computed(() => []),

  searchProps: null,
  exactMatchSearchProps: reads('searchProps'),
  regexSearchProps: reads('searchProps'),
  fuzzySearchProps: reads('searchProps'),

  // Three search modes
  exactMatchEnabled: true,
  fuzzySearchEnabled: false,
  regexEnabled: true,

  fuse: computed('listToSearch.[]', 'fuzzySearchProps.[]', function() {
    return new Fuse(this.get('listToSearch'), {
      shouldSort: true,
      threshold: 0.4,
      location: 0,
      distance: 100,
      tokenize: true,
      matchAllTokens: true,
      maxPatternLength: 32,
      minMatchCharLength: 1,
      keys: this.get('fuzzySearchProps') || [],
      getFn(item, key) {
        return get(item, key);
      },
    });
  }),

  listSearched: computed(
    'searchTerm',
    'listToSearch.[]',
    'exactMatchEnabled',
    'fuzzySearchEnabled',
    'regexEnabled',
    'exactMatchSearchProps.[]',
    'fuzzySearchProps.[]',
    'regexSearchProps.[]',
    function() {
      const searchTerm = this.get('searchTerm').trim();
      if (searchTerm && searchTerm.length) {
        const results = [];
        if (this.get('exactMatchEnabled')) {
          results.push(
            ...exactMatchSearch(
              searchTerm,
              this.get('listToSearch'),
              this.get('exactMatchSearchProps')
            )
          );
        }
        if (this.get('fuzzySearchEnabled')) {
          results.push(...this.get('fuse').search(searchTerm));
        }
        if (this.get('regexEnabled')) {
          results.push(
            ...regexSearch(searchTerm, this.get('listToSearch'), this.get('regexSearchProps'))
          );
        }
        return results.uniq();
      }
      return this.get('listToSearch');
    }
  ),
});

function exactMatchSearch(term, list, keys) {
  if (term.length) {
    return list.filter(item => keys.some(key => get(item, key) === term));
  }
}

function regexSearch(term, list, keys) {
  if (term.length) {
    try {
      const regex = new RegExp(term, 'i');
      // Test the value of each key for each object against the regex
      // All that match are returned.
      return list.filter(item => keys.some(key => regex.test(get(item, key))));
    } catch (e) {
      // Swallow the error; most likely due to an eager search of an incomplete regex
    }
    return [];
  }
}
<commit_msg>Clean up computed property by inverting early return<commit_after>import Mixin from '@ember/object/mixin';
import { get, computed } from '@ember/object';
import { reads } from '@ember/object/computed';
import Fuse from 'npm:fuse.js';

/**
  Searchable mixin

  Simple search filtering behavior for a list of objects.

  Properties to override:
    - searchTerm: the string to use as a query
    - searchProps: the props on each object to search
    -- exactMatchSearchProps: the props for exact search when props are different per search type
    -- regexSearchProps: the props for regex search when props are different per search type
    -- fuzzySearchProps: the props for fuzzy search when props are different per search type
    - exactMatchEnabled: (true) disable to not use the exact match search type
    - fuzzySearchEnabled: (false) enable to use the fuzzy search type
    - regexEnabled: (true) disable to disable the regex search type
    - listToSearch: the list of objects to search

  Properties provided:
    - listSearched: a subset of listToSearch of items that meet the search criteria
*/
export default Mixin.create({
  searchTerm: '',
  listToSearch: computed(() => []),

  searchProps: null,
  exactMatchSearchProps: reads('searchProps'),
  regexSearchProps: reads('searchProps'),
  fuzzySearchProps: reads('searchProps'),

  // Three search modes
  exactMatchEnabled: true,
  fuzzySearchEnabled: false,
  regexEnabled: true,

  fuse: computed('listToSearch.[]', 'fuzzySearchProps.[]', function() {
    return new Fuse(this.get('listToSearch'), {
      shouldSort: true,
      threshold: 0.4,
      location: 0,
      distance: 100,
      tokenize: true,
      matchAllTokens: true,
      maxPatternLength: 32,
      minMatchCharLength: 1,
      keys: this.get('fuzzySearchProps') || [],
      getFn(item, key) {
        return get(item, key);
      },
    });
  }),

  listSearched: computed(
    'searchTerm',
    'listToSearch.[]',
    'exactMatchEnabled',
    'fuzzySearchEnabled',
    'regexEnabled',
    'exactMatchSearchProps.[]',
    'fuzzySearchProps.[]',
    'regexSearchProps.[]',
    function() {
      const searchTerm = this.get('searchTerm').trim();

      if (!searchTerm || !searchTerm.length) {
        return this.get('listToSearch');
      }

      const results = [];

      if (this.get('exactMatchEnabled')) {
        results.push(
          ...exactMatchSearch(
            searchTerm,
            this.get('listToSearch'),
            this.get('exactMatchSearchProps')
          )
        );
      }

      if (this.get('fuzzySearchEnabled')) {
        results.push(...this.get('fuse').search(searchTerm));
      }

      if (this.get('regexEnabled')) {
        results.push(
          ...regexSearch(searchTerm, this.get('listToSearch'), this.get('regexSearchProps'))
        );
      }

      return results.uniq();
    }
  ),
});

function exactMatchSearch(term, list, keys) {
  if (term.length) {
    return list.filter(item => keys.some(key => get(item, key) === term));
  }
}

function regexSearch(term, list, keys) {
  if (term.length) {
    try {
      const regex = new RegExp(term, 'i');
      // Test the value of each key for each object against the regex
      // All that match are returned.
      return list.filter(item => keys.some(key => regex.test(get(item, key))));
    } catch (e) {
      // Swallow the error; most likely due to an eager search of an incomplete regex
    }
    return [];
  }
}
<|endoftext|>


<commit_before>import SPELLS from 'common/SPELLS';
import Analyzer from 'Parser/Core/Analyzer';
import Combatants from 'Parser/Core/Modules/Combatants';

const DRUID_HOTS = [
  SPELLS.REJUVENATION.id,
  SPELLS.WILD_GROWTH.id,
  SPELLS.REJUVENATION_GERMINATION.id,
  SPELLS.CULTIVATION.id,
  SPELLS.CENARION_WARD.id,
  SPELLS.LIFEBLOOM_HOT_HEAL.id,
  // Make sure to check that event.tick is true, because regrowth uses the same id for the heal and the HoT part
  SPELLS.REGROWTH.id,
  SPELLS.SPRING_BLOSSOMS.id,
  SPELLS.DREAMER.id,
];

// attributions
const DEFAULT = 0; // indicates the regular application method of the HoT, whatever that might be
const POTA = 1; // Power of the Archdruid (Rejuv or Regrowth)
const TEARSTONE = 2; // Tearstone of Elune (Rejuv)
const T194P = 3; // Tier 19 4pc (Rejuv)
const T214P = 4; // Tier 21 4pc (Dreamer, used to distinguish Dreamer procs that would not have happened but for the 4pc)

const debug = false;

/*
 * Backend module for tracking attribution of HoTs, e.g. what applied them / applied parts of them / boosted them
 */
class Rejuvenation extends Analyzer {
  static dependencies = {
    combatants: Combatants,
  };

  // {
  //   [playerId]: {
  //      [hotId]: { applied, attributions },
  //   },
  // }
  hots = {};

  on_byPlayer_heal(event) {
    // TODO
  }

  on_byPlayer_cast(event) {
    // TODO
  }

  on_byPlayer_applybuff(event) {
    const spellId = event.ability.guid;
    if (!DRUID_HOTS.includes(spellId)) {
      return;
    }

    const targetId = event.targetID;
    if (!targetId) {
      debug && console.log(`${event.ability.name} cast on target without ID @${this.owner.formatTimestamp(event.timestamp)}... HoT will not be tracked.`);
      return;
    }

    if(!this.hots[targetId]) {
      this.hots[targetId] = {};
    }

    this.hots[targetId][spellId] = {
      applied: event.timestamp,
      attributions: this._getAttributions(spellId, event.timestamp),
      // TODO need more fields?
    };
  }

  _getAttributions(spellId, timestamp) {
    if (spellId === SPELLS.REJUVENATION.id || SPELLS.REJUVENATION_GERMINATION.id) {

    } else {
      // ...
    }
  }

}

export default Rejuvenation;
<commit_msg>Filled out more of the outline...<commit_after>import SPELLS from 'common/SPELLS';
import Analyzer from 'Parser/Core/Analyzer';
import Combatants from 'Parser/Core/Modules/Combatants';

const DRUID_HOTS = [
  SPELLS.REJUVENATION.id,
  SPELLS.WILD_GROWTH.id,
  SPELLS.REJUVENATION_GERMINATION.id,
  SPELLS.CULTIVATION.id,
  SPELLS.CENARION_WARD.id,
  SPELLS.LIFEBLOOM_HOT_HEAL.id,
  // Make sure to check that event.tick is true, because regrowth uses the same id for the heal and the HoT part
  SPELLS.REGROWTH.id,
  SPELLS.SPRING_BLOSSOMS.id,
  SPELLS.DREAMER.id,
];

const debug = false;

/*
 * Backend module for tracking attribution of HoTs, e.g. what applied them / applied parts of them / boosted them
 */
class Rejuvenation extends Analyzer {
  static dependencies = {
    combatants: Combatants,
  };

  potaRejuv = { healing: 0 };
  potaRegrowth = { healing: 0 };
  tearstone = { healing: 0 };
  t194p = { healing: 0 };
  t214p = { healing: 0 }; // used to distinguish Dreamer procs that would not have happened but for the 4pc

  // {
  //   [playerId]: {
  //      [hotId]: { applied, attributions },
  //   },
  // }
  hots = {};

  on_byPlayer_heal(event) {
    const spellId = event.spellId;
    if(!event.tick || DRUID_HOTS.includes(spellId)) {
      return;
    }

    const targetId = event.targetId;
    if (!targetId) {
      debug && console.log(`${event.ability.name} healed target without ID @${this.owner.formatTimestamp(event.timestamp)}... no attribution possible.`);
      return;
    }

    const hot = this.hots[targetId][spellId];
    if (!hot) {
      console.warn(`${event.ability.name} healed target ID ${targetId} @${this.owner.formatTimestamp(event.timestamp)} but that player isn't recorded as having that HoT...`);
    }

    const healing = event.amount + (event.absorbed || 0);
    hot.attributions.forEach(att => att.healing += healing);
    // TODO handle extensions
    hot.boosts.forEach(att => att.healing += calculateEffectiveHealing(event, att.boost));
  }

  on_byPlayer_applybuff(event) {
    this._applyBuff(event);
  }

  on_byPlayer_refreshbuff(event) {
    this._removeBuff(event);
    this._applyBuff(event);
  }

  on_byPlayer_removebuff(event) {
    this._removeBuff(event);
  }

  _applyBuff(event) {
    const spellId = event.ability.guid;
    if (!DRUID_HOTS.includes(spellId)) {
      return;
    }

    const targetId = event.targetID;
    if (!targetId) {
      debug && console.log(`${event.ability.name} cast on target without ID @${this.owner.formatTimestamp(event.timestamp)}... HoT will not be tracked.`);
      return;
    }

    if(!this.hots[targetId]) {
      this.hots[targetId] = {};
    }

    const newHot = {
      start: event.timestamp,
      attributions: [], // new generated HoT
      extensions: [], // duration extensions to existing HoT
      boosts: [], // strength boost to existing HoT
      // TODO need more fields (like expected end)?
    };

    newHot.attributions.concat(this._getAttributions(spellId, timestamp));
    this.hots[targetId][spellId] = newHot;
  }

  _removeBuff(event) {
    // TODO implement
  }

  _getAttributions(spellId, timestamp) {
    if (spellId === SPELLS.REJUVENATION.id || SPELLS.REJUVENATION_GERMINATION.id) {

    } else {
      // ...
    }
  }

}

export default Rejuvenation;
<|endoftext|>


<commit_before>'use strict';

/* Controllers */

angular.module('myApp.controllers', ['ngUpload', 'chieffancypants.loadingBar', 'ngAnimate'])
	.controller("UsersCtrl", function ($scope,$rootScope, $location, usersService, cfpLoadingBar){
		//Executes when the controller is created
		$scope.getUsers = function(){
			usersService.getUsers().then(
				function (data) {
				$rootScope.is_logged = window.sessionStorage.getItem('is_logged');
				$rootScope.logged_in_user = JSON.parse(window.sessionStorage.getItem("logged_in_user"));
				if ($rootScope.logged_in_user){
					$rootScope.logged_in_user.pic = JSON.parse(window.sessionStorage.getItem("logged_in_user_pic"));
				}

					if ($rootScope.is_logged == "true")
						{
							$scope.users = data;
						}
					else{
						$location.path('/login');
					}

				}
			);
		}
		$scope.getUsers();
		cfpLoadingBar.complete();
	})
.controller("UsersRegisterCtrl", function ($scope,$rootScope, $location, $timeout, usersService, cfpLoadingBar, onAlert){

		$scope.addNewUser = function(user){
			usersService.addNewUser(user).then(function(user) {
				cfpLoadingBar.start();
				$location.path('/login');
				cfpLoadingBar.complete();
			});
		}
		$scope.passwordmatch = function(){
			$scope.alerts = onAlert.alerts;
			var check = $scope.user.password == $scope.user.password2;
			if(check){
				console.log("Password matches");
				document.getElementById("register").disabled = false;
				onAlert.successEvent("password matches");
			}else{
				console.log("password not matches");
				document.getElementById("register").disabled = true;
				onAlert.errorEvent("password not matches");
			}
		}

		$scope.cancel = function(){
			$location.path('/login');
		   }

		$scope.emailmatch = function(email){
			$scope.alerts = onAlert.alerts;
			usersService.chkemailid(email).then(function(user) {
				if(!user.success){
					document.getElementById("register").disabled = true;
					onAlert.errorEvent("Email already exist! Please try with another email");
				}else{
					// alert("email-id is available");
					document.getElementById("register").disabled = false;
				}
			});
		}

    	// fake the initial load so first time users can see it right away:
	    cfpLoadingBar.start();
	    $timeout(function() {
		    cfpLoadingBar.complete();
		    }, 1000);
	})

	.controller("UserEditCtrl", ['$scope','$location', '$routeParams','usersService', 'genders', function($scope, $location, $routeParams, usersService, genders
	){
		//Executes when the controller is created
		var userId = $routeParams.userId;
		// $scope.genders = [{value:'Male', text:'Male'}, {value:'Female', text:'Female'}];
		$scope.genders = genders.gender;
		console.log("In edit controller");
		var user = {id: userId};
		usersService.fetchUser(user).then(function(user) {
			var original = user;
			$scope.user = original;
		});
		$scope.updateUser = function(user){
			user.put().then(function() {
				console.log("success put");
				$location.path('/users');
			});
		}
	}])

	.controller("UserViewCtrl", ['$scope','$location', '$routeParams','usersService','newUsers', function($scope, $location, $routeParams, usersService, newUsers
){
		//Executes when the controller is created
		var userId = $routeParams.userId;
		var user = {id: userId};
		usersService.fetchUser(user).then(function(user) {
			var original = user;
			$scope.user = original;
		});
	}])

  .controller("UserDeleteCtrl", ['$scope','$location', '$routeParams','usersService', function($scope, $location, $routeParams, usersService
){
	  //Executes when the controller is created
	  console.log("In delete controller");
	  var userId = $routeParams.userId;
	  var user = {id: userId};
	  usersService.removeUser(user).then(function(user) {
		  var original = user;
		  original.remove().then(function() {
			  $location.path('/users');
		  })
      });
}])
.controller('uploadResume', function ($scope, usersService) {
      $scope.startUploading = function() {
        console.log('uploading....')
      };
      $scope.uploadComplete = function (content) {
        console.log("In uploadResume controller");
        if (content) console.log(content);
          $scope.response = content; // Presumed content is a json string!
          $scope.response.style = {
              color: $scope.response.color,
              "font-weight": "bold"
          };

          // Clear form (reason for using the 'ng-model' directive on the input elements)
          $scope.fullname = '';
          $scope.gender = '';
          $scope.color = '';
          // Look for way to clear the input[type=file] element
        }
      })

.controller('LoginCtrl', function($scope, $rootScope, $location, usersService, cfpLoadingBar, $timeout, Facebook, FbService, newUsers, onAlert){
    // And some fancy flags to display messages upon user status change
	if (window.sessionStorage.getItem("is_logged") == true){
				$location.path('/users');
	}
	// Here, usually you should watch for when Facebook is ready and loaded
	  $scope.$watch(function() {
	    return Facebook.isReady(); // This is for convenience, to notify if Facebook is loaded and ready to go.
	  }, function(newVal) {
	    $scope.facebookReady = true; // You might want to use this to disable/show/hide buttons and else
	  });

	$scope.logIn = function(user){
		usersService.chkLogin(user).then(function(user) {
			if ( (($scope.user.email) == (user[0].email)) && (($scope.user.password) == (user[0].password)) ){
				$rootScope.is_logged = true;
				window.sessionStorage.setItem("is_logged", true);
				cfpLoadingBar.start();
				$location.path('/users');
			}else{
				alert("Email or Password is incorrect.");
				$location.path('/login');
			}

		});
	}

    /**
     * IntentLogin
     **/
    $scope.intentLogin = function() {
      Facebook.getLoginStatus(function(response) {
        if (response.status == 'connected') {
        	$scope.is_logged_in();
        }
        else
          $scope.fblogin();
      });
    };

    $scope.is_logged_in = function() {
   	 	$scope.logged = true;
        $rootScope.is_logged = true;
        window.sessionStorage.setItem("is_logged", true);
        $scope.me();
    }

    /**
     * Login
     */
     $scope.fblogin = function() {
       Facebook.login(function(response) {
        if (response.status == 'connected') {
        	$scope.is_logged_in();
        }

      }, { scope: 'email' });
     };

     /**
      * me
      */
      $scope.me = function() {
    	  FbService.me().then(function(response){
    		  console.log(response);
    		  $timeout(function() {
                $rootScope.logged_in_user = response;
                window.sessionStorage.setItem("logged_in_user", JSON.stringify($rootScope.logged_in_user));
                $scope.my_pic();
                $scope.checkEmail();
              });
    	  });
      };
      $scope.checkEmail = function(){
    	  var fb_email = $rootScope.logged_in_user.email;
    	  usersService.chkemailid(fb_email).then(function(user) {
    		  if(user.success){
				console.log("New user");
				$scope.newUser = newUsers.newUser($rootScope.logged_in_user);
				$scope.addUser($scope.newUser)
    		  }else{
    			  console.log("Existing user");
    		  }
    	  });
		};
	  $scope.addUser = function(user){
			usersService.addNewUser(user).then(function(user) {
				console.log("Added FB user to DB");
			});
	  }

      $scope.my_pic = function() {
    	  FbService.my_pic().then(function(response){
    		  /**
               * Using $scope.$apply since this happens outside angular framework.
               */
    		  $timeout(function() {
    			  $rootScope.logged_in_user.pic = response;
    			  window.sessionStorage.setItem("logged_in_user_pic", JSON.stringify($rootScope.logged_in_user.pic));
              });
    	  });
          $location.path('/users');
        };

    /**
     * Taking approach of Events :D
     */
    $scope.$on('Facebook:statusChange', function(ev, data) {
      console.log('Status: ', data);
      if (data.status == 'connected') {
        $scope.$apply(function() {
          $scope.salutation = true;
          $scope.byebye     = false;
        });
      } else {
        $scope.$apply(function() {
          $scope.salutation = false;
          $scope.byebye     = true;

          // Dismiss byebye message after two seconds
          $timeout(function() {
            $scope.byebye = false;
          }, 2000)
        });
      }

    });
})

.controller('LogoutCtrl', function($scope, $rootScope,$location, $timeout, Facebook){
	$scope.logout = function() {
	  $rootScope.is_logged = false;
	  window.sessionStorage.setItem("is_logged", false);
      Facebook.logout(function() {
    	  $timeout(function() {
        	$scope.logged = false;
          $rootScope.logged_in_user = {};
          window.sessionStorage.setItem("logged_in_user", JSON.stringify($rootScope.logged_in_user));

        });
      });
    }
	$scope.logout();
});
<commit_msg>committing for code for logout functionality working correct after change<commit_after>'use strict';

/* Controllers */

angular.module('myApp.controllers', ['ngUpload', 'chieffancypants.loadingBar', 'ngAnimate'])
	.controller("UsersCtrl", function ($scope,$rootScope, $location, usersService, cfpLoadingBar){
		//Executes when the controller is created
		$scope.getUsers = function(){
			usersService.getUsers().then(
				function (data) {
				$rootScope.is_logged = window.sessionStorage.getItem('is_logged');
				$rootScope.logged_in_user = JSON.parse(window.sessionStorage.getItem("logged_in_user"));
				if ($rootScope.logged_in_user){
					$rootScope.logged_in_user.pic = JSON.parse(window.sessionStorage.getItem("logged_in_user_pic"));
				}

					if ($rootScope.is_logged == "true")
						{
							$scope.users = data;
						}
					else{
						$location.path('/login');
					}

				}
			);
		}
		$scope.getUsers();
		cfpLoadingBar.complete();
	})
.controller("UsersRegisterCtrl", function ($scope,$rootScope, $location, $timeout, usersService, cfpLoadingBar, onAlert){

		$scope.addNewUser = function(user){
			usersService.addNewUser(user).then(function(user) {
				cfpLoadingBar.start();
				$location.path('/login');
				cfpLoadingBar.complete();
			});
		}
		$scope.passwordmatch = function(){
			$scope.alerts = onAlert.alerts;
			var check = $scope.user.password == $scope.user.password2;
			if(check){
				console.log("Password matches");
				document.getElementById("register").disabled = false;
				onAlert.successEvent("password matches");
			}else{
				console.log("password not matches");
				document.getElementById("register").disabled = true;
				onAlert.errorEvent("password not matches");
			}
		}

		$scope.cancel = function(){
			$location.path('/login');
		   }

		$scope.emailmatch = function(email){
			$scope.alerts = onAlert.alerts;
			usersService.chkemailid(email).then(function(user) {
				if(!user.success){
					document.getElementById("register").disabled = true;
					onAlert.errorEvent("Email already exist! Please try with another email");
				}else{
					// alert("email-id is available");
					document.getElementById("register").disabled = false;
				}
			});
		}

    	// fake the initial load so first time users can see it right away:
	    cfpLoadingBar.start();
	    $timeout(function() {
		    cfpLoadingBar.complete();
		    }, 1000);
	})

	.controller("UserEditCtrl", ['$scope','$location', '$routeParams','usersService', 'genders', function($scope, $location, $routeParams, usersService, genders
	){
		//Executes when the controller is created
		var userId = $routeParams.userId;
		// $scope.genders = [{value:'Male', text:'Male'}, {value:'Female', text:'Female'}];
		$scope.genders = genders.gender;
		console.log("In edit controller");
		var user = {id: userId};
		usersService.fetchUser(user).then(function(user) {
			var original = user;
			$scope.user = original;
		});
		$scope.updateUser = function(user){
			user.put().then(function() {
				console.log("success put");
				$location.path('/users');
			});
		}
	}])

	.controller("UserViewCtrl", ['$scope','$location', '$routeParams','usersService','newUsers', function($scope, $location, $routeParams, usersService, newUsers
){
		//Executes when the controller is created
		var userId = $routeParams.userId;
		var user = {id: userId};
		usersService.fetchUser(user).then(function(user) {
			var original = user;
			$scope.user = original;
		});
	}])

  .controller("UserDeleteCtrl", ['$scope','$location', '$routeParams','usersService', function($scope, $location, $routeParams, usersService
){
	  //Executes when the controller is created
	  console.log("In delete controller");
	  var userId = $routeParams.userId;
	  var user = {id: userId};
	  usersService.removeUser(user).then(function(user) {
		  var original = user;
		  original.remove().then(function() {
			  $location.path('/users');
		  })
      });
}])
.controller('uploadResume', function ($scope, usersService) {
      $scope.startUploading = function() {
        console.log('uploading....')
      };
      $scope.uploadComplete = function (content) {
        console.log("In uploadResume controller");
        if (content) console.log(content);
          $scope.response = content; // Presumed content is a json string!
          $scope.response.style = {
              color: $scope.response.color,
              "font-weight": "bold"
          };

          // Clear form (reason for using the 'ng-model' directive on the input elements)
          $scope.fullname = '';
          $scope.gender = '';
          $scope.color = '';
          // Look for way to clear the input[type=file] element
        }
      })

.controller('LoginCtrl', function($scope, $rootScope, $location, usersService, cfpLoadingBar, $timeout, Facebook, FbService, newUsers, onAlert){
    // And some fancy flags to display messages upon user status change
	if (window.sessionStorage.getItem("is_logged")=="true"){
				$location.path('/users');
	}
	// Here, usually you should watch for when Facebook is ready and loaded
	  $scope.$watch(function() {
	    return Facebook.isReady(); // This is for convenience, to notify if Facebook is loaded and ready to go.
	  }, function(newVal) {
	    $scope.facebookReady = true; // You might want to use this to disable/show/hide buttons and else
	  });

	$scope.logIn = function(user){
		usersService.chkLogin(user).then(function(user) {
			if ( (($scope.user.email) == (user[0].email)) && (($scope.user.password) == (user[0].password)) ){
				$rootScope.is_logged = true;
				window.sessionStorage.setItem("is_logged", true);
				cfpLoadingBar.start();
				$location.path('/users');
			}else{
				alert("Email or Password is incorrect.");
				$location.path('/login');
			}

		});
	}

    /**
     * IntentLogin
     **/
    $scope.intentLogin = function() {
      Facebook.getLoginStatus(function(response) {
        if (response.status == 'connected') {
        	$scope.is_logged_in();
        }
        else
          $scope.fblogin();
      });
    };

    $scope.is_logged_in = function() {
   	 	$scope.logged = true;
        $rootScope.is_logged = true;
        window.sessionStorage.setItem("is_logged", true);
        $scope.me();
    }

    /**
     * Login
     */
     $scope.fblogin = function() {
       Facebook.login(function(response) {
        if (response.status == 'connected') {
        	$scope.is_logged_in();
        }

      }, { scope: 'email' });
     };

     /**
      * me
      */
      $scope.me = function() {
    	  FbService.me().then(function(response){
    		  console.log(response);
    		  $timeout(function() {
                $rootScope.logged_in_user = response;
                window.sessionStorage.setItem("logged_in_user", JSON.stringify($rootScope.logged_in_user));
                $scope.my_pic();
                $scope.checkEmail();
              });
    	  });
      };
      $scope.checkEmail = function(){
    	  var fb_email = $rootScope.logged_in_user.email;
    	  usersService.chkemailid(fb_email).then(function(user) {
    		  if(user.success){
				console.log("New user");
				$scope.newUser = newUsers.newUser($rootScope.logged_in_user);
				$scope.addUser($scope.newUser)
    		  }else{
    			  console.log("Existing user");
    		  }
    	  });
		};
	  $scope.addUser = function(user){
			usersService.addNewUser(user).then(function(user) {
				console.log("Added FB user to DB");
			});
	  }

      $scope.my_pic = function() {
    	  FbService.my_pic().then(function(response){
    		  /**
               * Using $scope.$apply since this happens outside angular framework.
               */
    		  $timeout(function() {
    			  $rootScope.logged_in_user.pic = response;
    			  window.sessionStorage.setItem("logged_in_user_pic", JSON.stringify($rootScope.logged_in_user.pic));
              });
    	  });
          $location.path('/users');
        };

    /**
     * Taking approach of Events :D
     */
    $scope.$on('Facebook:statusChange', function(ev, data) {
      console.log('Status: ', data);
      if (data.status == 'connected') {
        $scope.$apply(function() {
          $scope.salutation = true;
          $scope.byebye     = false;
        });
      } else {
        $scope.$apply(function() {
          $scope.salutation = false;
          $scope.byebye     = true;

          // Dismiss byebye message after two seconds
          $timeout(function() {
            $scope.byebye = false;
          }, 2000)
        });
      }

    });
})

.controller('LogoutCtrl', function($scope, $rootScope,$location, $timeout, Facebook){
	$scope.logout = function() {
	  $rootScope.is_logged = false;
	  window.sessionStorage.setItem("is_logged", false);
      Facebook.logout(function() {
    	  $timeout(function() {
        	$scope.logged = false;
          $rootScope.logged_in_user = {};
          window.sessionStorage.setItem("logged_in_user", JSON.stringify($rootScope.logged_in_user));

        });
      });
    }
	$scope.logout();
});
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Button, Input, Select, Card, Alert, Spin } from 'antd'
import { autobind } from 'core-decorators';
import crossRequest from 'cross-request';
import { withRouter } from 'react-router';
import URL from 'url';

import {
} from '../../../actions/group.js'

import './InterfaceTest.scss'

const { TextArea } = Input;
const InputGroup = Input.Group;
const Option = Select.Option;

@connect(
  state => ({
    reqParams: state.addInterface.reqParams,
    method: state.addInterface.method,
    url: state.addInterface.url,
    seqGroup: state.addInterface.seqGroup,
    interfaceName: state.addInterface.interfaceName,
    interfaceProject: state.addInterface.project
  }),
  {
  }
)
@withRouter
export default class InterfaceTest extends Component {

  static propTypes = {
    reqParams: PropTypes.string,
    method: PropTypes.string,
    url: PropTypes.string,
    interfaceName: PropTypes.string,
    seqGroup: PropTypes.array,
    match: PropTypes.object,
    interfaceProject: PropTypes.object
  }

  state = {
    res: '',
    method: 'GET',
    domains: [],
    pathname: '',
    query: {},
    params: {},
    paramsNotJson: false,
    headers: {},
    currDomain: ''
  }

  constructor(props) {
    super(props)
  }

  componentWillMount() {
    this.getInterfaceState()
  }

  componentWillReceiveProps(nextProps) {
    this.getInterfaceState(nextProps)
  }

  @autobind
  getInterfaceState(nextProps) {
    const props = nextProps || this.props;
    const { method, url, seqGroup, interfaceProject } = props;
    const { prd_host, basepath, protocol, env } = interfaceProject;
    const pathname = (basepath + url).replace(/\/+/g, '/');

    const domains = {prd: protocol + '://' + prd_host};
    env.forEach(item => {
      domains[item.name] = item.domain;
    })

    const query = {};
    let params = {};
    let reqParams = this.props.reqParams ? this.props.reqParams : '{}';
    let paramsNotJson = false;
    try {
      reqParams = JSON.parse(reqParams)
      paramsNotJson = false;
    } catch (e) {
      paramsNotJson = true;
    }
    if (method === 'GET') {
      Object.keys(reqParams).forEach(key => {
        const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams[key]) : reqParams[key].toString();
        query[key] = value;
      })
    } else if (method === 'POST') {
      params = reqParams;
    }

    const headers = {}
    seqGroup.forEach((headerItem) => {
      if (headerItem.name) {
        headers[headerItem.name] = headerItem.value;
      }
    })

    this.setState({
      method,
      domains,
      pathname,
      query,
      params,
      paramsNotJson,
      headers,
      currDomain: domains.prd,
      loading: false
    });
  }

  @autobind
  testInterface() {
    const { headers, params, currDomain, method, pathname, query } = this.state;
    const urlObj = URL.parse(currDomain);

    const href = URL.format({
      protocol: urlObj.protocol || 'http',
      host: urlObj.host,
      pathname,
      query
    });

    this.setState({ loading: true })

    crossRequest({
      url: href,
      method,
      headers,
      data: params,
      success: (res, header) => {
        console.log(header)
        try {
          res = JSON.parse(res);
        } catch (e) {
          null;
        }
        this.setState({res})
        this.setState({ loading: false })
      },
      error: (err, header) => {
        console.log(header)
        this.setState({res: err || '请求失败'})
        this.setState({ loading: false })
      }
    })
  }

  @autobind
  changeDomain(value) {
    this.setState({ currDomain: value });
  }

  @autobind
  selectDomain(value) {
    this.setState({ currDomain: value });
  }

  @autobind
  changeHeader(e, key) {
    const headers = JSON.parse(JSON.stringify(this.state.headers));
    headers[key] = e.target.value;
    this.setState({ headers });
  }

  @autobind
  changeQuery(e, key) {
    const query = JSON.parse(JSON.stringify(this.state.query));
    query[key] = e.target.value;

    this.setState({ query });
  }

  @autobind
  changeParams(e, key) {
    const params = JSON.parse(JSON.stringify(this.state.params));
    params[key] = e.target.value;
    this.setState({ params });
  }

  @autobind
  changeMethod(value) {
    this.setState({ method: value });
  }

  @autobind
  changePath(e) {
    const path = e.target.value;
    const urlObj = URL.parse(path, true);
    this.setState({
      query: urlObj.query,
      pathname: urlObj.pathname
    })
  }

  hasCrossRequestPlugin() {
    const dom = document.getElementById('y-request');
    return dom.getAttribute('key') === 'yapi';
  }

  render () {

    const { interfaceName } = this.props;
    const { method, domains, pathname, query, headers, params, paramsNotJson, currDomain } = this.state;
    const hasPlugin = this.hasCrossRequestPlugin();
    const search = decodeURIComponent(URL.format({query}));

    return (

          { hasPlugin ? '' :
          <Alert
            message={

                温馨提示：当前正在使用接口测试服务，请安装我们为您免费提供的

                  测试增强插件 [点击获取]！

            }
            type="warning"
          />
          }

{interfaceName}

        {/* url */}

            <InputGroup compact style={{display: 'inline-block', width: 680, border: 0, background: '#fff', marginBottom: -4}}>
              <Input value="Method" disabled style={{display: 'inline-block', width: 80, border: 0, background: '#fff'}} />
              <Input value="Domain" disabled style={{display: 'inline-block', width: 300, border: 0, background: '#fff'}} />
              <Input value="Basepath + Url + [Query]" disabled style={{display: 'inline-block', width: 300, border: 0, background: '#fff'}} />
            </InputGroup>
            <InputGroup compact style={{display: 'inline-block', width: 680}}>
              <Select value={method} style={{display: 'inline-block', width: 80}} onChange={this.changeMethod} >
                <Option value="GET">GET</Option>
                <Option value="POST">POST</Option>
              </Select>
              <Select value={currDomain} mode="combobox" filterOption={() => true} style={{display: 'inline-block', width: 300}} onChange={this.changeDomain} onSelect={this.selectDomain}>
                {
                  Object.keys(domains).map((key, index) => (<Option value={domains[key]} key={index}>{key + '：' + domains[key]}</Option>))
                }
              </Select>
              <Input value={pathname + search} onChange={this.changePath} spellCheck="false" style={{display: 'inline-block', width: 300}} />
            </InputGroup>
            <Button
              onClick={this.testInterface}
              type="primary"
              style={{marginLeft: 10}}
              loading={this.state.loading}
            >发送</Button>
            （请求测试真实接口）

          <Card title="Query" noHovering style={{marginTop: 10}} className={Object.keys(query).length ? '' : 'hidden'}>

              {
                Object.keys(query).map((key, index) => {
                  const value = typeof query[key] === 'object' ? JSON.stringify(query[key]) : query[key].toString();
                  return (

                      <Input disabled value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={value} onChange={e => this.changeQuery(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />

                  )
                })
              }

          </Card>
          <Card title="HEADERS" noHovering style={{marginTop: 10}} className={Object.keys(headers).length ? '' : 'hidden'}>

              {
                Object.keys(headers).map((key, index) => {
                  return (

                      <Input disabled value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={headers[key]} onChange={e => this.changeHeader(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />

                  )
                })
              }

          </Card>
          <Card title="Body" noHovering style={{marginTop: 10}} className={Object.keys(params).length ? '' : 'hidden'}>

              { paramsNotJson ?
                <TextArea
                  value={params}
                  style={{margin: 10}}
                  autosize={{ minRows: 2, maxRows: 6 }}
                ></TextArea> :
                Object.keys(params).map((key, index) => {
                  const value = typeof params[key] === 'object' ? JSON.stringify(params[key]) : params[key].toString();
                  return (

                      <Input disabled value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={value} onChange={e => this.changeParams(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />

                  )
                })
              }

          </Card>

        <Card title="返回结果" noHovering style={{marginTop: 10}}>
          <Spin spinning={this.state.loading}>

                <TextArea
                  value={typeof this.state.res === 'object' ? JSON.stringify(this.state.res, null, 2) : this.state.res.toString()}
                  autosize={{ minRows: 2, maxRows: 6 }}
                ></TextArea>

          </Spin>
        </Card>

    )
  }
}
<commit_msg>opti: remove disabled<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Button, Input, Select, Card, Alert, Spin } from 'antd'
import { autobind } from 'core-decorators';
import crossRequest from 'cross-request';
import { withRouter } from 'react-router';
import URL from 'url';

import {
} from '../../../actions/group.js'

import './InterfaceTest.scss'

const { TextArea } = Input;
const InputGroup = Input.Group;
const Option = Select.Option;

@connect(
  state => ({
    reqParams: state.addInterface.reqParams,
    method: state.addInterface.method,
    url: state.addInterface.url,
    seqGroup: state.addInterface.seqGroup,
    interfaceName: state.addInterface.interfaceName,
    interfaceProject: state.addInterface.project
  }),
  {
  }
)
@withRouter
export default class InterfaceTest extends Component {

  static propTypes = {
    reqParams: PropTypes.string,
    method: PropTypes.string,
    url: PropTypes.string,
    interfaceName: PropTypes.string,
    seqGroup: PropTypes.array,
    match: PropTypes.object,
    interfaceProject: PropTypes.object
  }

  state = {
    res: '',
    method: 'GET',
    domains: [],
    pathname: '',
    query: {},
    params: {},
    paramsNotJson: false,
    headers: {},
    currDomain: ''
  }

  constructor(props) {
    super(props)
  }

  componentWillMount() {
    this.getInterfaceState()
  }

  componentWillReceiveProps(nextProps) {
    this.getInterfaceState(nextProps)
  }

  @autobind
  getInterfaceState(nextProps) {
    const props = nextProps || this.props;
    const { method, url, seqGroup, interfaceProject } = props;
    const { prd_host, basepath, protocol, env } = interfaceProject;
    const pathname = (basepath + url).replace(/\/+/g, '/');

    const domains = {prd: protocol + '://' + prd_host};
    env.forEach(item => {
      domains[item.name] = item.domain;
    })

    const query = {};
    let params = {};
    let reqParams = this.props.reqParams ? this.props.reqParams : '{}';
    let paramsNotJson = false;
    try {
      reqParams = JSON.parse(reqParams)
      paramsNotJson = false;
    } catch (e) {
      paramsNotJson = true;
    }
    if (method === 'GET') {
      Object.keys(reqParams).forEach(key => {
        const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams[key]) : reqParams[key].toString();
        query[key] = value;
      })
    } else if (method === 'POST') {
      params = reqParams;
    }

    const headers = {}
    seqGroup.forEach((headerItem) => {
      if (headerItem.name) {
        headers[headerItem.name] = headerItem.value;
      }
    })

    this.setState({
      method,
      domains,
      pathname,
      query,
      params,
      paramsNotJson,
      headers,
      currDomain: domains.prd,
      loading: false
    });
  }

  @autobind
  testInterface() {
    const { headers, params, currDomain, method, pathname, query } = this.state;
    const urlObj = URL.parse(currDomain);

    const href = URL.format({
      protocol: urlObj.protocol || 'http',
      host: urlObj.host,
      pathname,
      query
    });

    this.setState({ loading: true })

    crossRequest({
      url: href,
      method,
      headers,
      data: params,
      success: (res, header) => {
        console.log(header)
        try {
          res = JSON.parse(res);
        } catch (e) {
          null;
        }
        this.setState({res})
        this.setState({ loading: false })
      },
      error: (err, header) => {
        console.log(header)
        this.setState({res: err || '请求失败'})
        this.setState({ loading: false })
      }
    })
  }

  @autobind
  changeDomain(value) {
    this.setState({ currDomain: value });
  }

  @autobind
  selectDomain(value) {
    this.setState({ currDomain: value });
  }

  @autobind
  changeHeader(e, key) {
    const headers = JSON.parse(JSON.stringify(this.state.headers));
    headers[key] = e.target.value;
    this.setState({ headers });
  }

  @autobind
  changeQuery(e, key) {
    const query = JSON.parse(JSON.stringify(this.state.query));
    query[key] = e.target.value;

    this.setState({ query });
  }

  @autobind
  changeParams(e, key) {
    const params = JSON.parse(JSON.stringify(this.state.params));
    params[key] = e.target.value;
    this.setState({ params });
  }

  @autobind
  changeMethod(value) {
    this.setState({ method: value });
  }

  @autobind
  changePath(e) {
    const path = e.target.value;
    const urlObj = URL.parse(path, true);
    this.setState({
      query: urlObj.query,
      pathname: urlObj.pathname
    })
  }

  hasCrossRequestPlugin() {
    const dom = document.getElementById('y-request');
    return dom.getAttribute('key') === 'yapi';
  }

  render () {

    const { interfaceName } = this.props;
    const { method, domains, pathname, query, headers, params, paramsNotJson, currDomain } = this.state;
    const hasPlugin = this.hasCrossRequestPlugin();
    const search = decodeURIComponent(URL.format({query}));

    return (

          { hasPlugin ? '' :
          <Alert
            message={

                温馨提示：当前正在使用接口测试服务，请安装我们为您免费提供的

                  测试增强插件 [点击获取]！

            }
            type="warning"
          />
          }

{interfaceName}

        {/* url */}

            <InputGroup compact style={{display: 'inline-block', width: 680, border: 0, background: '#fff', marginBottom: -4}}>
              <Input value="Method" style={{display: 'inline-block', width: 80, border: 0, background: '#fff'}} />
              <Input value="Domain" style={{display: 'inline-block', width: 300, border: 0, background: '#fff'}} />
              <Input value="Basepath + Url + [Query]" style={{display: 'inline-block', width: 300, border: 0, background: '#fff'}} />
            </InputGroup>
            <InputGroup compact style={{display: 'inline-block', width: 680}}>
              <Select value={method} style={{display: 'inline-block', width: 80}} onChange={this.changeMethod} >
                <Option value="GET">GET</Option>
                <Option value="POST">POST</Option>
              </Select>
              <Select value={currDomain} mode="combobox" filterOption={() => true} style={{display: 'inline-block', width: 300}} onChange={this.changeDomain} onSelect={this.selectDomain}>
                {
                  Object.keys(domains).map((key, index) => (<Option value={domains[key]} key={index}>{key + '：' + domains[key]}</Option>))
                }
              </Select>
              <Input value={pathname + search} onChange={this.changePath} spellCheck="false" style={{display: 'inline-block', width: 300}} />
            </InputGroup>
            <Button
              onClick={this.testInterface}
              type="primary"
              style={{marginLeft: 10}}
              loading={this.state.loading}
            >发送</Button>
            （请求测试真实接口）

          <Card title="Query" noHovering style={{marginTop: 10}} className={Object.keys(query).length ? '' : 'hidden'}>

              {
                Object.keys(query).map((key, index) => {
                  const value = typeof query[key] === 'object' ? JSON.stringify(query[key]) : query[key].toString();
                  return (

                      <Input value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={value} onChange={e => this.changeQuery(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />

                  )
                })
              }

          </Card>
          <Card title="HEADERS" noHovering style={{marginTop: 10}} className={Object.keys(headers).length ? '' : 'hidden'}>

              {
                Object.keys(headers).map((key, index) => {
                  return (

                      <Input value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={headers[key]} onChange={e => this.changeHeader(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />

                  )
                })
              }

          </Card>
          <Card title="Body" noHovering style={{marginTop: 10}} className={Object.keys(params).length ? '' : 'hidden'}>

              { paramsNotJson ?
                <TextArea
                  value={params}
                  style={{margin: 10}}
                  autosize={{ minRows: 2, maxRows: 6 }}
                ></TextArea> :
                Object.keys(params).map((key, index) => {
                  const value = typeof params[key] === 'object' ? JSON.stringify(params[key]) : params[key].toString();
                  return (

                      <Input value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
                      <Input value={value} onChange={e => this.changeParams(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />

                  )
                })
              }

          </Card>

        <Card title="返回结果" noHovering style={{marginTop: 10}}>
          <Spin spinning={this.state.loading}>

                <TextArea
                  value={typeof this.state.res === 'object' ? JSON.stringify(this.state.res, null, 2) : this.state.res.toString()}
                  autosize={{ minRows: 2, maxRows: 6 }}
                ></TextArea>

          </Spin>
        </Card>

    )
  }
}
<|endoftext|>