<commit_before>//some regex borrowed from pksunkara/inflect
//https://github.com/pksunkara/inflect/blob/master/lib/defaults.js

inflect = (function() {
    var irregulars = [
        ['child', 'children'],
        ['person', 'people'],
        ['man', 'men'],
        ['leaf', 'leaves'],
        ['database', 'databases'],
        ['quiz', 'quizzes'],
        ['child', 'children'],
        ['stomach', 'stomachs'],
        ['sex', 'sexes'],
        ['move', 'moves'],
        ['shoe', 'shoes'],
        ["goose", "geese"],
        ["phenomenon", "phenomena"],
        ['barracks', 'barracks'],
        ['deer', 'deer'],
        ['syllabus', 'syllabi'],
        ['index', 'indices'],
        ['appendix', 'appendices'],
        ['criterion', 'criteria'],
        ['i', 'we'],
        ['person', 'people'],
        ['man', 'men'],
        ['child', 'children'],
        ['move', 'moves'],
        ['she', 'they'],
        ['he', 'they'],
        ['myself', 'ourselves'],
        ['yourself', 'yourselves'],
        ['himself', 'themselves'],
        ['herself', 'themselves'],
        ['themself', 'themselves'],
        ['mine', 'ours'],
        ['hers', 'theirs'],
        ['his', 'theirs'],
        ['its', 'theirs'],
        ['theirs', 'theirs'],
        ['sex', 'sexes'],
        ['photo', 'photos'],
        ['video', 'videos'],
        ['narrative', 'narratives'],
        ['rodeo', 'rodeos'],
        ['gas', 'gases'],
        ['epoch', 'epochs'],
        ['zero', 'zeros'],
        ['avocado', 'avocados'],
        ['halo', 'halos'],
        ['tornado', 'tornados'],
        ['tuxedo', 'tuxedos'],
        ['sombrero', 'sombreros'],
    ]

    var uncountables = {
        "aircraft": 1,
        "bass": 1,
        "bison": 1,
        "fowl": 1,
        "halibut": 1,
        "moose": 1,
        "salmon": 1,
        "spacecraft": 1,
        "tuna": 1,
        "trout": 1,
        "advice": 1,
        "help": 1,
        "information": 1,
        "knowledge": 1,
        "trouble": 1,
        "work": 1,
        "enjoyment": 1,
        "fun": 1,
        "recreation": 1,
        "relaxation": 1,
        "meat": 1,
        "rice": 1,
        "bread": 1,
        "cake": 1,
        "coffee": 1,
        "ice": 1,
        "water": 1,
        "oil": 1,
        "grass": 1,
        "hair": 1,
        "fruit": 1,
        "wildlife": 1,
        "equipment": 1,
        "machinery": 1,
        "furniture": 1,
        "mail": 1,
        "luggage": 1,
        "jewelry": 1,
        "clothing": 1,
        "money": 1,
        "mathematics": 1,
        "economics": 1,
        "physics": 1,
        "civics": 1,
        "ethics": 1,
        "mumps": 1,
        "measles": 1,
        "news": 1,
        "tennis": 1,
        "baggage": 1,
        "currency": 1,
        "travel": 1,
        "soap": 1,
        "toothpaste": 1,
        "food": 1,
        "sugar": 1,
        "butter": 1,
        "flour": 1,
        "progress": 1,
        "research": 1,
        "leather": 1,
        "wool": 1,
        "wood": 1,
        "coal": 1,
        "weather": 1,
        "homework": 1,
        "cotton": 1,
        "silk": 1,
        "patience": 1,
        "impatience": 1,
        "talent": 1,
        "energy": 1,
        "experience": 1,
        "vinegar": 1,
        "polish": 1,
        "air": 1,
        "alcohol": 1,
        "anger": 1,
        "art": 1,
        "beef": 1,
        "blood": 1,
        "cash": 1,
        "chaos": 1,
        "cheese": 1,
        "chewing": 1,
        "conduct": 1,
        "confusion": 1,
        "courage": 1,
        "damage": 1,
        "education": 1,
        "electricity": 1,
        "entertainment": 1,
        "fiction": 1,
        "forgiveness": 1,
        "gold": 1,
        "gossip": 1,
        "ground": 1,
        "happiness": 1,
        "history": 1,
        "honey": 1,
        "hope": 1,
        "hospitality": 1,
        "importance": 1,
        "jam": 1,
        "justice": 1,
        "laughter": 1,
        "leisure": 1,
        "lightning": 1,
        "literature": 1,
        "love": 1,
        "luck": 1,
        "melancholy": 1,
        "milk": 1,
        "mist": 1,
        "music": 1,
        "noise": 1,
        "oxygen": 1,
        "paper": 1,
        "pay": 1,
        "peace": 1,
        "peanut": 1,
        "pepper": 1,
        "petrol": 1,
        "plastic": 1,
        "pork": 1,
        "power": 1,
        "pressure": 1,
        "rain": 1,
        "recognition": 1,
        "sadness": 1,
        "safety": 1,
        "salt": 1,
        "sand": 1,
        "scenery": 1,
        "shopping": 1,
        "silver": 1,
        "snow": 1,
        "softness": 1,
        "space": 1,
        "speed": 1,
        "steam": 1,
        "sunshine": 1,
        "tea": 1,
        "thunder": 1,
        "time": 1,
        "traffic": 1,
        "trousers": 1,
        "violence": 1,
        "warmth": 1,
        "washing": 1,
        "wind": 1,
        "wine": 1,
        "steel": 1,
        "soccer": 1,
        "hockey": 1,
        "golf": 1,
        "fish": 1,
        "gum": 1,
        "liquid": 1,
        "series": 1,
        "sheep": 1,
        "species": 1,
    }

    var pluralize_rules = [{
        reg: /(ax|test)is$/i,
        repl: '$1es'
    }, {
        reg: /(octop|vir|radi|nucle|fung|cact|stimul)us$/i,
        repl: '$1i'
    }, {
        reg: /(octop|vir)i$/i,
        repl: '$1i'
    }, {
        reg: /([rl])f$/i,
        repl: '$1ves'
    }, {
        reg: /(alias|status)$/i,
        repl: '$1es'
    }, {
        reg: /(bu)s$/i,
        repl: '$1ses'
    }, {
        reg: /(al|ad|at|er|et|ed|ad)o$/i,
        repl: '$1oes'
    }, {
        reg: /([ti])um$/i,
        repl: '$1a'
    }, {
        reg: /([ti])a$/i,
        repl: '$1a'
    }, {
        reg: /sis$/i,
        repl: 'ses'
    }, {
        reg: /(?:([^f])fe|([lr])f)$/i,
        repl: '$1ves'
    }, {
        reg: /(hive)$/i,
        repl: '$1s'
    }, {
        reg: /([^aeiouy]|qu)y$/i,
        repl: '$1ies'
    }, {
        reg: /(x|ch|ss|sh|s|z)$/i,
        repl: '$1es'
    }, {
        reg: /(matr|vert|ind|cort)(ix|ex)$/i,
        repl: '$1ices'
    }, {
        reg: /([m|l])ouse$/i,
        repl: '$1ice'
    }, {
        reg: /([m|l])ice$/i,
        repl: '$1ice'
    }, {
        reg: /^(ox)$/i,
        repl: '$1en'
    }, {
        reg: /^(oxen)$/i,
        repl: '$1'
    }, {
        reg: /(quiz)$/i,
        repl: '$1zes'
    }, {
        reg: /(antenn|formul|nebul|vertebr|vit)a$/i,
        repl: '$1ae'
    }, {
        reg: /(sis)$/i,
        repl: 'ses'
    }, {
        reg: /^(?!talis|.*hu)(.*)man$/i,
        repl: '$1men'
    }, {
        reg: /(.*)/i,
        repl: '$1s'
    }]

    var pluralize = function(str) {
        var low = str.toLowerCase()
        //uncountable
        if (uncountables[low]) {
            return str
        }
        //irregular
        var found = irregulars.filter(function(r) {
            return r[0] == low
        })
        if (found[0]) {
            if (low == str) {
                return found[0][1]
            } else {
                return found[0][1].charAt(0).toUpperCase() + string.slice(1)
            }
        }
        //inflect first word of preposition-phrase
        if (str.match(/([a-z]*) (of|in|by|for) [a-z]/)) {
            var first = str.match(/^([a-z]*) (of|in|by|for) [a-z]/)[1]
            if (first) {
                var better_first = pluralize(first)
                return better_first + str.replace(first, '')
            }
        }
        //regular
        for (var i in pluralize_rules) {
            if (str.match(pluralize_rules[i].reg)) {
                return str.replace(pluralize_rules[i].reg, pluralize_rules[i].repl)
            }
        }
    }

    var singularize_rules = [{
        reg: /([^v])ies$/i,
        repl: '$1y'
    }, {
        reg: /ises$/i,
        repl: 'isis'
    }, {
        reg: /ives$/i,
        repl: 'ife'
    }, {
        reg: /(antenn|formul|nebul|vertebr|vit)ae$/i,
        repl: '$1a'
    }, {
        reg: /(octop|vir|radi|nucle|fung|cact|stimul)(i)$/i,
        repl: '$1us'
    }, {
        reg: /(buffal|tomat|tornad)(oes)$/i,
        repl: '$1o'
    }, {
        reg: /((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i,
        repl: '$1sis'
    }, {
        reg: /(vert|ind|cort)(ices)$/i,
        repl: '$1ex'
    }, {
        reg: /(matr|append)(ices)$/i,
        repl: '$1ix'
    }, {
        reg: /(x|ch|ss|sh|s|z|o)es$/i,
        repl: '$1'
    }, {
        reg: /men$/i,
        repl: 'man'
    }, {
        reg: /(n)ews$/i,
        repl: '$1ews'
    }, {
        reg: /([ti])a$/i,
        repl: '$1um'
    }, {
        reg: /([^f])ves$/i,
        repl: '$1fe'
    }, {
        reg: /([lr])ves$/i,
        repl: '$1f'
    }, {
        reg: /([^aeiouy]|qu)ies$/i,
        repl: '$1y'
    }, {
        reg: /(s)eries$/i,
        repl: '$1eries'
    }, {
        reg: /(m)ovies$/i,
        repl: '$1ovie'
    }, {
        reg: /([m|l])ice$/i,
        repl: '$1ouse'
    }, {
        reg: /(cris|ax|test)es$/i,
        repl: '$1is'
    }, {
        reg: /(alias|status)es$/i,
        repl: '$1'
    }, {
        reg: /s$/i,
        repl: ''
    }]

    var singularize = function(str) {
        var low = str.toLowerCase()
        //uncountable
        if (uncountables[low]) {
            return str
        }
        //irregular
        var found = irregulars.filter(function(r) {
            return r[1] == low
        })
        if (found[0]) {
            if (low == str) {
                return found[0][0]
            } else {
                return found[0][0].charAt(0).toUpperCase() + string.slice(1)
            }
        }
        //inflect first word of preposition-phrase
        if (str.match(/([a-z]*) (of|in|by|for) [a-z]/)) {
            var first = str.match(/^([a-z]*) (of|in|by|for) [a-z]/)[1]
            if (first) {
                var better_first = singularize(first)
                return better_first + str.replace(first, '')
            }
        }
        //regular
        for (var i in singularize_rules) {
            if (str.match(singularize_rules[i].reg)) {
                return str.replace(singularize_rules[i].reg, singularize_rules[i].repl)
            }
        }
        return str
    }

    var is_plural = function(str) {
        for (var i = 0; i < irregulars.length; i++) {
            if (irregulars[i][1] == str) {
                return true
            }
            if (irregulars[i][0] == str) {
                return false
            }
        }
        if (str.match(/s$/)) {
            return true
        }
    }

    var inflect = function(str) {
        if (is_plural(str)) {
            return {
                plural: str,
                singular: singularize(str),
            }
        } else {
            return {
                singular: str,
                plural: pluralize(str)
            }
        }
    }

    var methods = {
        inflect: inflect,
        is_plural: is_plural,
        singularize: singularize,
        pluralize: pluralize
    }
    if (typeof module !== "undefined" && module.exports) {
        module.exports = methods;
    }
    return methods;
})();

// console.log(inflect.pluralize('twin'))
// console.log(inflect.pluralize('phantom of the opera'))
// console.log(inflect.pluralize('mayor of chicago'))
// console.log(inflect.pluralize('boy in the mall'))
// console.log(inflect.pluralize('maple leaf'))
// console.log(inflect.singularize('leaves'))
// console.log(inflect.singularize('mayors of toronto'))

/*

bus
kiss
        console.log(nlp.noun('crisis').pluralize() == 'crises')
        console.log(nlp.noun('analysis').pluralize() == 'analyses')
        console.log(nlp.noun('neurosis').pluralize() == 'neuroses')
*/<commit_msg>add celcious etc to uncountables<commit_after>//some regex borrowed from pksunkara/inflect
//https://github.com/pksunkara/inflect/blob/master/lib/defaults.js

inflect = (function() {
    var irregulars = [
        ['child', 'children'],
        ['person', 'people'],
        ['man', 'men'],
        ['leaf', 'leaves'],
        ['database', 'databases'],
        ['quiz', 'quizzes'],
        ['child', 'children'],
        ['stomach', 'stomachs'],
        ['sex', 'sexes'],
        ['move', 'moves'],
        ['shoe', 'shoes'],
        ["goose", "geese"],
        ["phenomenon", "phenomena"],
        ['barracks', 'barracks'],
        ['deer', 'deer'],
        ['syllabus', 'syllabi'],
        ['index', 'indices'],
        ['appendix', 'appendices'],
        ['criterion', 'criteria'],
        ['i', 'we'],
        ['person', 'people'],
        ['man', 'men'],
        ['child', 'children'],
        ['move', 'moves'],
        ['she', 'they'],
        ['he', 'they'],
        ['myself', 'ourselves'],
        ['yourself', 'yourselves'],
        ['himself', 'themselves'],
        ['herself', 'themselves'],
        ['themself', 'themselves'],
        ['mine', 'ours'],
        ['hers', 'theirs'],
        ['his', 'theirs'],
        ['its', 'theirs'],
        ['theirs', 'theirs'],
        ['sex', 'sexes'],
        ['photo', 'photos'],
        ['video', 'videos'],
        ['narrative', 'narratives'],
        ['rodeo', 'rodeos'],
        ['gas', 'gases'],
        ['epoch', 'epochs'],
        ['zero', 'zeros'],
        ['avocado', 'avocados'],
        ['halo', 'halos'],
        ['tornado', 'tornados'],
        ['tuxedo', 'tuxedos'],
        ['sombrero', 'sombreros'],
    ]

    var uncountables = {
        "aircraft": 1,
        "bass": 1,
        "bison": 1,
        "fowl": 1,
        "halibut": 1,
        "moose": 1,
        "salmon": 1,
        "spacecraft": 1,
        "tuna": 1,
        "trout": 1,
        "advice": 1,
        "help": 1,
        "information": 1,
        "knowledge": 1,
        "trouble": 1,
        "work": 1,
        "enjoyment": 1,
        "fun": 1,
        "recreation": 1,
        "relaxation": 1,
        "meat": 1,
        "rice": 1,
        "bread": 1,
        "cake": 1,
        "coffee": 1,
        "ice": 1,
        "water": 1,
        "oil": 1,
        "grass": 1,
        "hair": 1,
        "fruit": 1,
        "wildlife": 1,
        "equipment": 1,
        "machinery": 1,
        "furniture": 1,
        "mail": 1,
        "luggage": 1,
        "jewelry": 1,
        "clothing": 1,
        "money": 1,
        "mathematics": 1,
        "economics": 1,
        "physics": 1,
        "civics": 1,
        "ethics": 1,
        "mumps": 1,
        "measles": 1,
        "news": 1,
        "tennis": 1,
        "baggage": 1,
        "currency": 1,
        "travel": 1,
        "soap": 1,
        "toothpaste": 1,
        "food": 1,
        "sugar": 1,
        "butter": 1,
        "flour": 1,
        "progress": 1,
        "research": 1,
        "leather": 1,
        "wool": 1,
        "wood": 1,
        "coal": 1,
        "weather": 1,
        "homework": 1,
        "cotton": 1,
        "silk": 1,
        "patience": 1,
        "impatience": 1,
        "talent": 1,
        "energy": 1,
        "experience": 1,
        "vinegar": 1,
        "polish": 1,
        "air": 1,
        "alcohol": 1,
        "anger": 1,
        "art": 1,
        "beef": 1,
        "blood": 1,
        "cash": 1,
        "chaos": 1,
        "cheese": 1,
        "chewing": 1,
        "conduct": 1,
        "confusion": 1,
        "courage": 1,
        "damage": 1,
        "education": 1,
        "electricity": 1,
        "entertainment": 1,
        "fiction": 1,
        "forgiveness": 1,
        "gold": 1,
        "gossip": 1,
        "ground": 1,
        "happiness": 1,
        "history": 1,
        "honey": 1,
        "hope": 1,
        "hospitality": 1,
        "importance": 1,
        "jam": 1,
        "justice": 1,
        "laughter": 1,
        "leisure": 1,
        "lightning": 1,
        "literature": 1,
        "love": 1,
        "luck": 1,
        "melancholy": 1,
        "milk": 1,
        "mist": 1,
        "music": 1,
        "noise": 1,
        "oxygen": 1,
        "paper": 1,
        "pay": 1,
        "peace": 1,
        "peanut": 1,
        "pepper": 1,
        "petrol": 1,
        "plastic": 1,
        "pork": 1,
        "power": 1,
        "pressure": 1,
        "rain": 1,
        "recognition": 1,
        "sadness": 1,
        "safety": 1,
        "salt": 1,
        "sand": 1,
        "scenery": 1,
        "shopping": 1,
        "silver": 1,
        "snow": 1,
        "softness": 1,
        "space": 1,
        "speed": 1,
        "steam": 1,
        "sunshine": 1,
        "tea": 1,
        "thunder": 1,
        "time": 1,
        "traffic": 1,
        "trousers": 1,
        "violence": 1,
        "warmth": 1,
        "washing": 1,
        "wind": 1,
        "wine": 1,
        "steel": 1,
        "soccer": 1,
        "hockey": 1,
        "golf": 1,
        "fish": 1,
        "gum": 1,
        "liquid": 1,
        "series": 1,
        "sheep": 1,
        "species": 1,
        "fahrenheit": 1,
        "celcius": 1,
        "kelvin": 1,
        "hertz": 1,
    }

    var pluralize_rules = [{
        reg: /(ax|test)is$/i,
        repl: '$1es'
    }, {
        reg: /(octop|vir|radi|nucle|fung|cact|stimul)us$/i,
        repl: '$1i'
    }, {
        reg: /(octop|vir)i$/i,
        repl: '$1i'
    }, {
        reg: /([rl])f$/i,
        repl: '$1ves'
    }, {
        reg: /(alias|status)$/i,
        repl: '$1es'
    }, {
        reg: /(bu)s$/i,
        repl: '$1ses'
    }, {
        reg: /(al|ad|at|er|et|ed|ad)o$/i,
        repl: '$1oes'
    }, {
        reg: /([ti])um$/i,
        repl: '$1a'
    }, {
        reg: /([ti])a$/i,
        repl: '$1a'
    }, {
        reg: /sis$/i,
        repl: 'ses'
    }, {
        reg: /(?:([^f])fe|([lr])f)$/i,
        repl: '$1ves'
    }, {
        reg: /(hive)$/i,
        repl: '$1s'
    }, {
        reg: /([^aeiouy]|qu)y$/i,
        repl: '$1ies'
    }, {
        reg: /(x|ch|ss|sh|s|z)$/i,
        repl: '$1es'
    }, {
        reg: /(matr|vert|ind|cort)(ix|ex)$/i,
        repl: '$1ices'
    }, {
        reg: /([m|l])ouse$/i,
        repl: '$1ice'
    }, {
        reg: /([m|l])ice$/i,
        repl: '$1ice'
    }, {
        reg: /^(ox)$/i,
        repl: '$1en'
    }, {
        reg: /^(oxen)$/i,
        repl: '$1'
    }, {
        reg: /(quiz)$/i,
        repl: '$1zes'
    }, {
        reg: /(antenn|formul|nebul|vertebr|vit)a$/i,
        repl: '$1ae'
    }, {
        reg: /(sis)$/i,
        repl: 'ses'
    }, {
        reg: /^(?!talis|.*hu)(.*)man$/i,
        repl: '$1men'
    }, {
        reg: /(.*)/i,
        repl: '$1s'
    }]

    var pluralize = function(str) {
        var low = str.toLowerCase()
        //uncountable
        if (uncountables[low]) {
            return str
        }
        //irregular
        var found = irregulars.filter(function(r) {
            return r[0] == low
        })
        if (found[0]) {
            if (low == str) {
                return found[0][1]
            } else {
                return found[0][1].charAt(0).toUpperCase() + string.slice(1)
            }
        }
        //inflect first word of preposition-phrase
        if (str.match(/([a-z]*) (of|in|by|for) [a-z]/)) {
            var first = str.match(/^([a-z]*) (of|in|by|for) [a-z]/)[1]
            if (first) {
                var better_first = pluralize(first)
                return better_first + str.replace(first, '')
            }
        }
        //regular
        for (var i in pluralize_rules) {
            if (str.match(pluralize_rules[i].reg)) {
                return str.replace(pluralize_rules[i].reg, pluralize_rules[i].repl)
            }
        }
    }

    var singularize_rules = [{
        reg: /([^v])ies$/i,
        repl: '$1y'
    }, {
        reg: /ises$/i,
        repl: 'isis'
    }, {
        reg: /ives$/i,
        repl: 'ife'
    }, {
        reg: /(antenn|formul|nebul|vertebr|vit)ae$/i,
        repl: '$1a'
    }, {
        reg: /(octop|vir|radi|nucle|fung|cact|stimul)(i)$/i,
        repl: '$1us'
    }, {
        reg: /(buffal|tomat|tornad)(oes)$/i,
        repl: '$1o'
    }, {
        reg: /((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i,
        repl: '$1sis'
    }, {
        reg: /(vert|ind|cort)(ices)$/i,
        repl: '$1ex'
    }, {
        reg: /(matr|append)(ices)$/i,
        repl: '$1ix'
    }, {
        reg: /(x|ch|ss|sh|s|z|o)es$/i,
        repl: '$1'
    }, {
        reg: /men$/i,
        repl: 'man'
    }, {
        reg: /(n)ews$/i,
        repl: '$1ews'
    }, {
        reg: /([ti])a$/i,
        repl: '$1um'
    }, {
        reg: /([^f])ves$/i,
        repl: '$1fe'
    }, {
        reg: /([lr])ves$/i,
        repl: '$1f'
    }, {
        reg: /([^aeiouy]|qu)ies$/i,
        repl: '$1y'
    }, {
        reg: /(s)eries$/i,
        repl: '$1eries'
    }, {
        reg: /(m)ovies$/i,
        repl: '$1ovie'
    }, {
        reg: /([m|l])ice$/i,
        repl: '$1ouse'
    }, {
        reg: /(cris|ax|test)es$/i,
        repl: '$1is'
    }, {
        reg: /(alias|status)es$/i,
        repl: '$1'
    }, {
        reg: /s$/i,
        repl: ''
    }]

    var singularize = function(str) {
        var low = str.toLowerCase()
        //uncountable
        if (uncountables[low]) {
            return str
        }
        //irregular
        var found = irregulars.filter(function(r) {
            return r[1] == low
        })
        if (found[0]) {
            if (low == str) {
                return found[0][0]
            } else {
                return found[0][0].charAt(0).toUpperCase() + string.slice(1)
            }
        }
        //inflect first word of preposition-phrase
        if (str.match(/([a-z]*) (of|in|by|for) [a-z]/)) {
            var first = str.match(/^([a-z]*) (of|in|by|for) [a-z]/)[1]
            if (first) {
                var better_first = singularize(first)
                return better_first + str.replace(first, '')
            }
        }
        //regular
        for (var i in singularize_rules) {
            if (str.match(singularize_rules[i].reg)) {
                return str.replace(singularize_rules[i].reg, singularize_rules[i].repl)
            }
        }
        return str
    }

    var is_plural = function(str) {
        for (var i = 0; i < irregulars.length; i++) {
            if (irregulars[i][1] == str) {
                return true
            }
            if (irregulars[i][0] == str) {
                return false
            }
        }
        if (str.match(/s$/)) {
            return true
        }
    }

    var inflect = function(str) {
        if (is_plural(str)) {
            return {
                plural: str,
                singular: singularize(str),
            }
        } else {
            return {
                singular: str,
                plural: pluralize(str)
            }
        }
    }

    var methods = {
        inflect: inflect,
        is_plural: is_plural,
        singularize: singularize,
        pluralize: pluralize
    }
    if (typeof module !== "undefined" && module.exports) {
        module.exports = methods;
    }
    return methods;
})();

// console.log(inflect.pluralize('twin'))
// console.log(inflect.pluralize('phantom of the opera'))
// console.log(inflect.pluralize('mayor of chicago'))
// console.log(inflect.pluralize('boy in the mall'))
// console.log(inflect.pluralize('maple leaf'))
// console.log(inflect.singularize('leaves'))
// console.log(inflect.singularize('mayors of toronto'))

/*

bus
kiss
        console.log(nlp.noun('crisis').pluralize() == 'crises')
        console.log(nlp.noun('analysis').pluralize() == 'analyses')
        console.log(nlp.noun('neurosis').pluralize() == 'neuroses')
*/<|endoftext|>


<commit_before>"use strict";

var $ = require('jquery'),
    Marionette = require('../../shim/backbone.marionette'),
    App = require('../app'),
    models = require('./models'),
    coreModels = require('../core/models'),
    coreViews = require('../core/views'),
    chart = require('../core/chart'),
    windowTmpl = require('./templates/window.html'),
    messageTmpl = require('./templates/message.html'),
    detailsTmpl = require('./templates/details.html'),
    tableTmpl = require('./templates/table.html'),
    tableRowTmpl = require('./templates/tableRow.html'),
    tabPanelTmpl = require('./templates/tabPanel.html'),
    tabContentTmpl = require('./templates/tabContent.html'),
    barChartTmpl = require('../core/templates/barChart.html');

var AnalyzeWindow = Marionette.LayoutView.extend({
    id: 'analyze-output-wrapper',
    template: windowTmpl,

    regions: {
        headerRegion: '#analyze-header-region',
        detailsRegion: {
            el: '#analyze-details-region'
        }
    },

    initialize: function() {
        this.listenTo(this, 'animateIn', function() {
            $('#analyze-output-wrapper .bar-chart').trigger('bar-chart:refresh');
        });
    },

    onShow: function() {
        this.showHeaderRegion();
        this.showAnalyzingMessage();

        var self = this;

        if (!this.model.get('result')) {
            var taskHelper = {
                pollSuccess: function() {
                    self.showDetailsRegion();
                },

                pollFailure: function() {
                    self.showErrorMessage();
                },

                startFailure: function() {
                    self.showErrorMessage();
                }
            };
            this.model.start(taskHelper);
        } else {
            this.lock = $.Deferred();
            this.lock.done(function() {
                self.showDetailsRegion();
            });
        }
    },

    showHeaderRegion: function() {
        this.headerRegion.show(new coreViews.AreaOfInterestView({
            App: App,
            model: new coreModels.AreaOfInterestModel({
                shape: this.model.get('area_of_interest'),
                place: App.map.get('areaOfInterestName'),
                can_go_back: true,
                next_label: 'Model',
                url: 'project'
            })
        }));
    },

    showAnalyzingMessage: function() {
        var messageModel = new models.AnalyzeMessageModel();
        messageModel.setAnalyzing();
        this.detailsRegion.show(new MessageView({
            model: messageModel
        }));
    },

    showErrorMessage: function() {
        var messageModel = new models.AnalyzeMessageModel();
        messageModel.setError();
        this.detailsRegion.show(new MessageView({
            model: messageModel
        }));
    },

    showDetailsRegion: function() {
        var results = JSON.parse(this.model.get('result'));

        if (!this.isDestroyed) {
            this.detailsRegion.show(new DetailsView({
                collection: new models.LayerCollection(results)
            }));
        }
    },

    transitionInCss: {
        height: '0%'
    },

    animateIn: function() {
        var self = this;

        this.$el.animate({ height: '55%' }, 200, function() {
            self.trigger('animateIn');
            App.map.setHalfSize(true);
        });
        if (this.lock !== undefined) {
            this.lock.resolve();
        }
    },

    animateOut: function() {
        var self = this;

        App.map.setFullSize(true);
        this.$el.animate({ height: '0%' }, 200, function() {
            self.trigger('animateOut');
        });
    }
});

var MessageView = Marionette.ItemView.extend({
    template: messageTmpl,
    className: 'analyze-message-region'
});

var DetailsView = Marionette.LayoutView.extend({
    template: detailsTmpl,
    regions: {
        panelsRegion: '.tab-panels-region',
        contentRegion: '.tab-contents-region'
    },

    onShow: function() {
        this.panelsRegion.show(new TabPanelsView({
            collection: this.collection
        }));

        this.contentRegion.show(new TabContentsView({
            collection: this.collection
        }));
    }
});

var TabPanelView = Marionette.ItemView.extend({
    tagName: 'li',
    template: tabPanelTmpl,
    attributes: {
        role: 'presentation'
    }
});

var TabPanelsView = Marionette.CollectionView.extend({
    tagName: 'ul',
    className: 'nav nav-tabs',
    attributes: {
        role: 'tablist'
    },
    childView: TabPanelView,

    events: {
        'shown.bs.tab li a ': 'triggerBarChartRefresh'
    },

    triggerBarChartRefresh: function() {
        $('#analyze-output-wrapper .bar-chart').trigger('bar-chart:refresh');
    },

    onRender: function() {
        this.$el.find('li:first').addClass('active');
    }
});

var TabContentView = Marionette.LayoutView.extend({
    className: 'tab-pane',
    id: function() {
        return this.model.get('name');
    },
    template: tabContentTmpl,
    attributes: {
        role: 'tabpanel'
    },
    regions: {
        tableRegion: '.analyze-table-region',
        chartRegion: '.analyze-chart-region'
    },
    onShow: function() {
        var dataCollection = new coreModels.DataCollection(
            this.model.get('categories')
        );

        this.tableRegion.show(new TableView({
            collection: dataCollection
        }));

        this.chartRegion.show(new ChartView({
            model: this.model,
            collection: dataCollection
        }));
    }
});

var TabContentsView = Marionette.CollectionView.extend({
    className: 'tab-content',
    childView: TabContentView,
    onRender: function() {
        this.$el.find('.tab-pane:first').addClass('active');
    }
});

var TableRowView = Marionette.ItemView.extend({
    tagName: 'tr',
    template: tableRowTmpl
});

var TableView = Marionette.CompositeView.extend({
    childView: TableRowView,
    childViewContainer: 'tbody',
    template: tableTmpl
});

var ChartView = Marionette.ItemView.extend({
    template: barChartTmpl,
    id: function() {
        return 'chart-' + this.model.get('name');
    },
    className: 'chart-container',

    onAttach: function() {
        this.addChart();
    },

    addChart: function() {
        var selector = '#' + this.id() + ' .bar-chart',
            chartData = this.collection.map(function(model) {
                return model.attributes;
            }),
            chartOptions = {
                isPercentage: true,
                depAxisLabel: 'Coverage'
            },
            depVars = ['coverage'],
            indVar = 'type';

        if (this.model.get('name') === 'land') {
            chartOptions.useHorizBars = true;
        }
        chart.makeBarChart(selector, chartData, indVar, depVars, chartOptions);
    }
});

module.exports = {
    AnalyzeWindow: AnalyzeWindow,
    DetailsView: DetailsView
};
<commit_msg>Ensure AOI is sent to job runner.<commit_after>"use strict";

var $ = require('jquery'),
    Marionette = require('../../shim/backbone.marionette'),
    App = require('../app'),
    models = require('./models'),
    coreModels = require('../core/models'),
    coreViews = require('../core/views'),
    chart = require('../core/chart'),
    windowTmpl = require('./templates/window.html'),
    messageTmpl = require('./templates/message.html'),
    detailsTmpl = require('./templates/details.html'),
    tableTmpl = require('./templates/table.html'),
    tableRowTmpl = require('./templates/tableRow.html'),
    tabPanelTmpl = require('./templates/tabPanel.html'),
    tabContentTmpl = require('./templates/tabContent.html'),
    barChartTmpl = require('../core/templates/barChart.html');

var AnalyzeWindow = Marionette.LayoutView.extend({
    id: 'analyze-output-wrapper',
    template: windowTmpl,

    regions: {
        headerRegion: '#analyze-header-region',
        detailsRegion: {
            el: '#analyze-details-region'
        }
    },

    initialize: function() {
        this.listenTo(this, 'animateIn', function() {
            $('#analyze-output-wrapper .bar-chart').trigger('bar-chart:refresh');
        });
    },

    onShow: function() {
        this.showHeaderRegion();
        this.showAnalyzingMessage();

        var self = this;

        if (!this.model.get('result')) {
            var aoi = JSON.stringify(this.model.get('area_of_interest')),
                taskHelper = {
                    pollSuccess: function() {
                        self.showDetailsRegion();
                    },

                    pollFailure: function() {
                        self.showErrorMessage();
                    },

                    startFailure: function() {
                        self.showErrorMessage();
                    },

                    postData: {'area_of_interest': aoi}
                };
            this.model.start(taskHelper);
        } else {
            this.lock = $.Deferred();
            this.lock.done(function() {
                self.showDetailsRegion();
            });
        }
    },

    showHeaderRegion: function() {
        this.headerRegion.show(new coreViews.AreaOfInterestView({
            App: App,
            model: new coreModels.AreaOfInterestModel({
                shape: this.model.get('area_of_interest'),
                place: App.map.get('areaOfInterestName'),
                can_go_back: true,
                next_label: 'Model',
                url: 'project'
            })
        }));
    },

    showAnalyzingMessage: function() {
        var messageModel = new models.AnalyzeMessageModel();
        messageModel.setAnalyzing();
        this.detailsRegion.show(new MessageView({
            model: messageModel
        }));
    },

    showErrorMessage: function() {
        var messageModel = new models.AnalyzeMessageModel();
        messageModel.setError();
        this.detailsRegion.show(new MessageView({
            model: messageModel
        }));
    },

    showDetailsRegion: function() {
        var results = JSON.parse(this.model.get('result'));

        if (!this.isDestroyed) {
            this.detailsRegion.show(new DetailsView({
                collection: new models.LayerCollection(results)
            }));
        }
    },

    transitionInCss: {
        height: '0%'
    },

    animateIn: function() {
        var self = this;

        this.$el.animate({ height: '55%' }, 200, function() {
            self.trigger('animateIn');
            App.map.setHalfSize(true);
        });
        if (this.lock !== undefined) {
            this.lock.resolve();
        }
    },

    animateOut: function() {
        var self = this;

        App.map.setFullSize(true);
        this.$el.animate({ height: '0%' }, 200, function() {
            self.trigger('animateOut');
        });
    }
});

var MessageView = Marionette.ItemView.extend({
    template: messageTmpl,
    className: 'analyze-message-region'
});

var DetailsView = Marionette.LayoutView.extend({
    template: detailsTmpl,
    regions: {
        panelsRegion: '.tab-panels-region',
        contentRegion: '.tab-contents-region'
    },

    onShow: function() {
        this.panelsRegion.show(new TabPanelsView({
            collection: this.collection
        }));

        this.contentRegion.show(new TabContentsView({
            collection: this.collection
        }));
    }
});

var TabPanelView = Marionette.ItemView.extend({
    tagName: 'li',
    template: tabPanelTmpl,
    attributes: {
        role: 'presentation'
    }
});

var TabPanelsView = Marionette.CollectionView.extend({
    tagName: 'ul',
    className: 'nav nav-tabs',
    attributes: {
        role: 'tablist'
    },
    childView: TabPanelView,

    events: {
        'shown.bs.tab li a ': 'triggerBarChartRefresh'
    },

    triggerBarChartRefresh: function() {
        $('#analyze-output-wrapper .bar-chart').trigger('bar-chart:refresh');
    },

    onRender: function() {
        this.$el.find('li:first').addClass('active');
    }
});

var TabContentView = Marionette.LayoutView.extend({
    className: 'tab-pane',
    id: function() {
        return this.model.get('name');
    },
    template: tabContentTmpl,
    attributes: {
        role: 'tabpanel'
    },
    regions: {
        tableRegion: '.analyze-table-region',
        chartRegion: '.analyze-chart-region'
    },
    onShow: function() {
        var dataCollection = new coreModels.DataCollection(
            this.model.get('categories')
        );

        this.tableRegion.show(new TableView({
            collection: dataCollection
        }));

        this.chartRegion.show(new ChartView({
            model: this.model,
            collection: dataCollection
        }));
    }
});

var TabContentsView = Marionette.CollectionView.extend({
    className: 'tab-content',
    childView: TabContentView,
    onRender: function() {
        this.$el.find('.tab-pane:first').addClass('active');
    }
});

var TableRowView = Marionette.ItemView.extend({
    tagName: 'tr',
    template: tableRowTmpl
});

var TableView = Marionette.CompositeView.extend({
    childView: TableRowView,
    childViewContainer: 'tbody',
    template: tableTmpl
});

var ChartView = Marionette.ItemView.extend({
    template: barChartTmpl,
    id: function() {
        return 'chart-' + this.model.get('name');
    },
    className: 'chart-container',

    onAttach: function() {
        this.addChart();
    },

    addChart: function() {
        var selector = '#' + this.id() + ' .bar-chart',
            chartData = this.collection.map(function(model) {
                return model.attributes;
            }),
            chartOptions = {
                isPercentage: true,
                depAxisLabel: 'Coverage'
            },
            depVars = ['coverage'],
            indVar = 'type';

        if (this.model.get('name') === 'land') {
            chartOptions.useHorizBars = true;
        }
        chart.makeBarChart(selector, chartData, indVar, depVars, chartOptions);
    }
});

module.exports = {
    AnalyzeWindow: AnalyzeWindow,
    DetailsView: DetailsView
};
<|endoftext|>


<commit_before>/**
 * @file BriefPagination component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import PaginationSize from '../_PaginationSize';
import IconButton from '../IconButton';

import Valid from '../_vendors/Valid';
import TableCalculation from '../_vendors/TableCalculation';
import ComponentUtil from '../_vendors/ComponentUtil';

class BriefPagination extends Component {

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            page: props.page,
            pageSize: props.pageSize
        };

    }

    handlePageChange = page => {
        this.setState({
            page
        }, () => {
            const {onPageChange} = this.props;
            onPageChange && onPageChange(page);
        });
    };

    handlePageSizeChange = pageSize => {

        const {pageSizeValueField} = this.props,
            originPageSizeValue = TableCalculation.getPageSizeValue(this.state.pageSize, pageSizeValueField),
            pageSizeValue = TableCalculation.getPageSizeValue(pageSize, pageSizeValueField);

        if (pageSizeValue && originPageSizeValue !== pageSizeValue) {

            const {onPageSizeChange} = this.props;
            onPageSizeChange && onPageSizeChange(pageSize);

            const totalPage = Math.ceil(this.props.total / pageSizeValue),
                page = Valid.range(this.state.page, 0, totalPage - 1);
            if (page !== this.state.page) {
                const {onPageChange} = this.props;
                onPageChange && onPageChange(page);
            }

        }

    };

    static getDerivedStateFromProps(props, state) {
        return {
            prevProps: props,
            page: ComponentUtil.getDerivedState(props, state, 'page'),
            pageSize: ComponentUtil.getDerivedState(props, state, 'pageSize')
        };
    }

    render() {

        const {
                total, page, pageSize, pageSizes, pageSizeRightIconCls, pageSizeValueField, pageSizeDisplayField,
                selectedCount, countVisible, pageSizeVisible, prevIconCls, nextIconCls,
                countRenderer, parentEl
            } = this.props,

            totalPage = Math.ceil(total / pageSize),

            startNumber = Valid.range(page * pageSize + 1, 0, total),
            stopNumber = Valid.range((page + 1) * pageSize, 0, total);

        return (

                    {
                        countVisible ?

                                {`Selected: ${selectedCount}`}

                            :
                            null
                    }

                        {
                            countRenderer ?
                                countRenderer(total, page, totalPage, pageSize, pageSizes)
                                :
                                `Total: ${total}`
                        }

                    {
                        pageSizeVisible ?
                            <PaginationSize pageSize={pageSize}
                                            pageSizes={pageSizes}
                                            valueField={pageSizeValueField}
                                            displayField={pageSizeDisplayField}
                                            parentEl={parentEl}
                                            rightIconCls={pageSizeRightIconCls}
                                            onPageSizeChange={this.handlePageSizeChange}/>
                            :
                            null
                    }

                        {`${startNumber}-${stopNumber} of ${total}`}

                    <IconButton iconCls={prevIconCls}
                                disabled={page <= 0}
                                onClick={() => this.handlePageChange(page - 1)}/>

                    <IconButton iconCls={nextIconCls}
                                disabled={page >= totalPage - 1}
                                onClick={() => this.handlePageChange(page + 1)}/>

        );

    }
}

BriefPagination.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The total of data.
     */
    total: PropTypes.number,

    /**
     * The valid page.
     */
    page: PropTypes.number,

    /**
     * The number of per page.
     */
    pageSize: PropTypes.number,

    /**
     * The array of pageSize.
     */
    pageSizes: PropTypes.array,

    /**
     * The value field name of PageSize. (default: "value")
     */
    pageSizeValueField: PropTypes.string,

    /**
     * The display field name of PageSize. (default: "text")
     */
    pageSizeDisplayField: PropTypes.string,

    /**
     * The total count of selected.
     */
    selectedCount: PropTypes.number,

    /**
     * If true,the selectedCount will show.
     */
    countVisible: PropTypes.bool,

    /**
     * If false, the pageSize choice box will not show.
     */
    pageSizeVisible: PropTypes.bool,

    /**
     * Use this property to set page size right icon.
     */
    pageSizeRightIconCls: PropTypes.string,

    /**
     * Use this property to set prev button icon.
     */
    prevIconCls: PropTypes.string,

    /**
     * Use this property to set next button icon.
     */
    nextIconCls: PropTypes.string,

    countRenderer: PropTypes.func,

    parentEl: PropTypes.object,

    onPageChange: PropTypes.func,
    onPageSizeChange: PropTypes.func

};

BriefPagination.defaultProps = {

    total: 0,
    page: 0,
    pageSize: 10,
    pageSizes: [5, 10, 15, 20],
    pageSizeValueField: 'value',
    pageSizeDisplayField: 'text',
    selectedCount: 0,

    countVisible: false,
    pageSizeVisible: true,

    pageSizeRightIconCls: 'fas fa-angle-down',
    prevIconCls: 'fas fa-angle-left',
    nextIconCls: 'fas fa-angle-right'

};

export default BriefPagination;
<commit_msg>update BriefPagination<commit_after>/**
 * @file BriefPagination component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import PaginationSize from '../_PaginationSize';
import IconButton from '../IconButton';

import Valid from '../_vendors/Valid';
import TableCalculation from '../_vendors/TableCalculation';
import ComponentUtil from '../_vendors/ComponentUtil';

class BriefPagination extends Component {

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            page: props.page,
            pageSize: props.pageSize
        };

    }

    handlePageChange = page => {
        this.setState({
            page
        }, () => {
            const {onPageChange} = this.props;
            onPageChange && onPageChange(page);
        });
    };

    handlePageSizeChange = pageSize => {

        const {pageSizeValueField} = this.props,
            originPageSizeValue = TableCalculation.getPageSizeValue(this.state.pageSize, pageSizeValueField),
            pageSizeValue = TableCalculation.getPageSizeValue(pageSize, pageSizeValueField);

        if (pageSizeValue && originPageSizeValue !== pageSizeValue) {

            const {onPageSizeChange} = this.props;
            onPageSizeChange && onPageSizeChange(pageSize);

            const totalPage = Math.ceil(this.props.total / pageSizeValue),
                page = Valid.range(this.state.page, 0, totalPage - 1);
            if (page !== this.state.page) {
                const {onPageChange} = this.props;
                onPageChange && onPageChange(page);
            }

        }

    };

    static getDerivedStateFromProps(props, state) {
        return {
            prevProps: props,
            page: ComponentUtil.getDerivedState(props, state, 'page'),
            pageSize: ComponentUtil.getDerivedState(props, state, 'pageSize')
        };
    }

    render() {

        const {
                total, page, pageSize, pageSizes, pageSizeRightIconCls, pageSizeValueField, pageSizeDisplayField,
                totalVisible, selectedCount, selectionVisible, pageSizeVisible, prevIconCls, nextIconCls,
                totalRenderer, parentEl
            } = this.props,

            totalPage = Math.ceil(total / pageSize),

            startNumber = Valid.range(page * pageSize + 1, 0, total),
            stopNumber = Valid.range((page + 1) * pageSize, 0, total);

        return (

                    {
                        selectionVisible ?

                                {`Selected: ${selectedCount}`}

                            :
                            null
                    }

                    {
                        totalVisible ?

                                {
                                    totalRenderer ?
                                        totalRenderer(total, page, totalPage, pageSize, pageSizes)
                                        :
                                        `Total: ${total}`
                                }

                            :
                            null
                    }

                    {
                        pageSizeVisible ?
                            <PaginationSize pageSize={pageSize}
                                            pageSizes={pageSizes}
                                            valueField={pageSizeValueField}
                                            displayField={pageSizeDisplayField}
                                            parentEl={parentEl}
                                            rightIconCls={pageSizeRightIconCls}
                                            onPageSizeChange={this.handlePageSizeChange}/>
                            :
                            null
                    }

                        {`${startNumber}-${stopNumber} of ${total}`}

                    <IconButton iconCls={prevIconCls}
                                disabled={page <= 0}
                                onClick={() => this.handlePageChange(page - 1)}/>

                    <IconButton iconCls={nextIconCls}
                                disabled={page >= totalPage - 1}
                                onClick={() => this.handlePageChange(page + 1)}/>

        );

    }
}

BriefPagination.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The total of data.
     */
    total: PropTypes.number,

    /**
     * The valid page.
     */
    page: PropTypes.number,

    /**
     * The number of per page.
     */
    pageSize: PropTypes.number,

    /**
     * The array of pageSize.
     */
    pageSizes: PropTypes.array,

    /**
     * The value field name of PageSize. (default: "value")
     */
    pageSizeValueField: PropTypes.string,

    /**
     * The display field name of PageSize. (default: "text")
     */
    pageSizeDisplayField: PropTypes.string,

    /**
     * The total count of selected.
     */
    selectedCount: PropTypes.number,

    /**
     * If true, the selectedCount will show.
     */
    selectionVisible: PropTypes.bool,

    /**
     * If true, the total will show.
     */
    totalVisible: PropTypes.bool,

    /**
     * If false, the pageSize choice box will not show.
     */
    pageSizeVisible: PropTypes.bool,

    /**
     * Use this property to set page size right icon.
     */
    pageSizeRightIconCls: PropTypes.string,

    /**
     * Use this property to set prev button icon.
     */
    prevIconCls: PropTypes.string,

    /**
     * Use this property to set next button icon.
     */
    nextIconCls: PropTypes.string,

    totalRenderer: PropTypes.func,

    parentEl: PropTypes.object,

    onPageChange: PropTypes.func,
    onPageSizeChange: PropTypes.func

};

BriefPagination.defaultProps = {

    total: 0,
    page: 0,
    pageSize: 10,
    pageSizes: [5, 10, 15, 20],
    pageSizeValueField: 'value',
    pageSizeDisplayField: 'text',
    selectedCount: 0,

    selectionVisible: false,
    totalVisible: true,
    pageSizeVisible: true,

    pageSizeRightIconCls: 'fas fa-angle-down',
    prevIconCls: 'fas fa-angle-left',
    nextIconCls: 'fas fa-angle-right'

};

export default BriefPagination;
<|endoftext|>


<commit_before>/*global module:false*/
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    // Metadata.
    pkg: grunt.file.readJSON('bower.json'),
    banner: '/*!\n' +
      ' * <%= pkg.title || pkg.name %> - v<%= pkg.version %>\n' +
      ' * Date: <%= grunt.template.today("yyyy-mm-dd") %>\n' +
      ' * License: <%= pkg.license %>\n' +
      ' * Authors: <%= pkg.authors.join() %>\n' +
      ' * Homepage: <%= pkg.homepage %>\n' +
      ' */\n',
    // Task configuration.
    concat: {
      options: {
        banner: '<%= banner %>',
        stripBanners: true
      },
      dist: {
        src: ['src/<%= pkg.name %>.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },
    uglify: {
      options: {
        banner: '<%= banner %>'
      },
      dist: {
        src: '<%= concat.dist.dest %>',
        dest: 'dist/<%= pkg.name %>.min.js'
      }
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        unused: true,
        boss: true,
        eqnull: true,
        browser: true,
        globals: {
          d3: true
        }
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      src_test: {
        src: ['src/**/*.js', 'test/**/*.js', '!test/lib/**/*.js']
      }
    },
    mocha: {
      src: ['test/index.html'],
      options: {
        run: true,
        reporter: 'Spec'
      }
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      src_test: {
        files: '<%= jshint.src_test.src %>',
        tasks: ['jshint:src_test', 'test']
      }
    }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-mocha');

  // Default task.
  grunt.registerTask('default', ['jshint', 'test', 'concat', 'uglify']);
  grunt.registerTask('test', ['mocha']);

};
<commit_msg>create sourcemap<commit_after>/*global module:false*/
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    // Metadata.
    pkg: grunt.file.readJSON('bower.json'),
    banner: '/*!\n' +
      ' * <%= pkg.title || pkg.name %> - v<%= pkg.version %>\n' +
      ' * Date: <%= grunt.template.today("yyyy-mm-dd") %>\n' +
      ' * License: <%= pkg.license %>\n' +
      ' * Authors: <%= pkg.authors.join() %>\n' +
      ' * Homepage: <%= pkg.homepage %>\n' +
      ' */\n',
    // Task configuration.
    concat: {
      options: {
        banner: '<%= banner %>',
        stripBanners: true
      },
      dist: {
        src: ['src/<%= pkg.name %>.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },
    uglify: {
      options: {
        banner: '<%= banner %>',
        sourceMap: true
      },
      dist: {
        src: '<%= concat.dist.dest %>',
        dest: 'dist/<%= pkg.name %>.min.js'
      }
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        unused: true,
        boss: true,
        eqnull: true,
        browser: true,
        globals: {
          d3: true
        }
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      src_test: {
        src: ['src/**/*.js', 'test/**/*.js', '!test/lib/**/*.js']
      }
    },
    mocha: {
      src: ['test/index.html'],
      options: {
        run: true,
        reporter: 'Spec'
      }
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      src_test: {
        files: '<%= jshint.src_test.src %>',
        tasks: ['jshint:src_test', 'test']
      }
    }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-mocha');

  // Default task.
  grunt.registerTask('default', ['jshint', 'test', 'concat', 'uglify']);
  grunt.registerTask('test', ['mocha']);

};
<|endoftext|>


<commit_before>import { Component, render, h } from 'preact';
import { findHints } from './findHints';
import { settings } from './settings';
import { mouseEvent, isTextEditable } from 'lib/dom';
import { style } from './style';

export let showHints;
export let hideHints;
export let advanceOnKey;

class HintRenderer extends Component {
  constructor () {
    super();
    this.state = {
      hints: [],
      filteredHints: [],
      inputKeys: []
    };
  }
  componentDidMount () {
    showHints = () => {
      const hints = findHints();
      const hintStrings = generateHintStrings(settings.hintCharacters, hints.length);
      const labeledHints = hints.map((hint, i) => Object.assign(hint, { hintString: hintStrings[i] }));
      this.setState({
        hints: labeledHints,
        filteredHints: labeledHints,
        inputKeys: ''
      });
    };
    advanceOnKey = (key) => {
      const hints = this.state.hints;
      const inputKeys = this.state.inputKeys + key;
      const filteredHints = this.state.hints
        .filter((hint) => hint.hintString.startsWith(inputKeys));
      if (filteredHints.length === 1 && inputKeys === filteredHints[0].hintString) {
        return activateHint(filteredHints[0]);
      }
      this.setState({
        hints,
        filteredHints,
        inputKeys
      });
      return 'HINTS';
    };
    hideHints = () => {
      this.setState({
        hints: [],
        filteredHints: [],
        inputKeys: ''
      });
    };
  }
  render () {
    return (

        {this.state.filteredHints.map((hint) => (
          <Hint
            hintString={hint.hintString}
            left={hint.rect.left + window.scrollX}
            top={hint.rect.top + window.scrollY}
            seen={this.state.inputKeys.length} />
        ))}

    );
  }
}

const Hint = ({ hintString, left, top, seen }) => (

    { hintString.split('').map((char, i) => (
      = seen ? 1 : 0.5}}>{char}
    ))}

);

function activateHint (hint) {
  if (isTextEditable(hint.element)) {
    hint.element.focus();
    return 'TEXT';
  }
  mouseEvent('click', hint.element);
  return 'COMMAND';
}

function generateHintStrings (characters, count) {
  const hints = [''];
  let offset = 0;
  while (hints.length - offset < count || hints.length === 1) {
    const hint = hints[offset++];
    for (const c of characters) {
      hints.push(hint + c);
    }
  }
  return hints.slice(offset, offset + count);
}

const hintContainer = document.createElement('div');
// Ideally, should use shadow dom, but only chrome supports it (3/2017)
// fallback is to reset styles on all child hints (see styles.js)
if (SAKA_PLATFORM === 'chrome') {
  document.documentElement.appendChild(hintContainer);
  const shadow = hintContainer.attachShadow({mode: 'open'});
  shadow.innerHTML = `<style>${style}</style>`;
  render(<HintRenderer />, shadow);
} else {
  hintContainer.innerHTML = `<style>${style}</style>`;
  document.documentElement.appendChild(hintContainer);
  render(<HintRenderer />, hintContainer);
}
<commit_msg>fixed hints mode so it exits if there are 0 hints left<commit_after>import { Component, render, h } from 'preact';
import { findHints } from './findHints';
import { settings } from './settings';
import { mouseEvent, isTextEditable } from 'lib/dom';
import { style } from './style';

export let showHints;
export let hideHints;
export let advanceOnKey;

class HintRenderer extends Component {
  constructor () {
    super();
    this.state = {
      hints: [],
      filteredHints: [],
      inputKeys: []
    };
  }
  componentDidMount () {
    showHints = () => {
      const hints = findHints();
      const hintStrings = generateHintStrings(settings.hintCharacters, hints.length);
      const labeledHints = hints.map((hint, i) => Object.assign(hint, { hintString: hintStrings[i] }));
      this.setState({
        hints: labeledHints,
        filteredHints: labeledHints,
        inputKeys: ''
      });
    };
    advanceOnKey = (key) => {
      const hints = this.state.hints;
      const inputKeys = this.state.inputKeys + key;
      const filteredHints = this.state.hints
        .filter((hint) => hint.hintString.startsWith(inputKeys));
      if (filteredHints.length === 1 && inputKeys === filteredHints[0].hintString) {
        return activateHint(filteredHints[0]);
      } else if (filteredHints.length === 0) {
        return 'COMMAND';
      }
      this.setState({
        hints,
        filteredHints,
        inputKeys
      });
      return 'HINTS';
    };
    hideHints = () => {
      this.setState({
        hints: [],
        filteredHints: [],
        inputKeys: ''
      });
    };
  }
  render () {
    return (

        {this.state.filteredHints.map((hint) => (
          <Hint
            hintString={hint.hintString}
            left={hint.rect.left + window.scrollX}
            top={hint.rect.top + window.scrollY}
            seen={this.state.inputKeys.length} />
        ))}

    );
  }
}

const Hint = ({ hintString, left, top, seen }) => (

    { hintString.split('').map((char, i) => (
      = seen ? 1 : 0.5}}>{char}
    ))}

);

function activateHint (hint) {
  if (isTextEditable(hint.element)) {
    hint.element.focus();
    return 'TEXT';
  }
  mouseEvent('click', hint.element);
  return 'COMMAND';
}

function generateHintStrings (characters, count) {
  const hints = [''];
  let offset = 0;
  while (hints.length - offset < count || hints.length === 1) {
    const hint = hints[offset++];
    for (const c of characters) {
      hints.push(hint + c);
    }
  }
  return hints.slice(offset, offset + count);
}

const hintContainer = document.createElement('div');
// Ideally, should use shadow dom, but only chrome supports it (3/2017)
// fallback is to reset styles on all child hints (see styles.js)
if (SAKA_PLATFORM === 'chrome') {
  document.documentElement.appendChild(hintContainer);
  const shadow = hintContainer.attachShadow({mode: 'open'});
  shadow.innerHTML = `<style>${style}</style>`;
  render(<HintRenderer />, shadow);
} else {
  hintContainer.innerHTML = `<style>${style}</style>`;
  document.documentElement.appendChild(hintContainer);
  render(<HintRenderer />, hintContainer);
}
<|endoftext|>


<commit_before>function showhideCategories(hdr,on) {
  var table = hdr.parentNode.parentNode.parentNode,
      newDisplay = on ? '' : 'none',
      nameList = new Array(), id;
  for (var i = 1; i < table.rows.length; i++) {
    if (on || table.rows[i].cells.length == 1)
      table.rows[i].style.display = newDisplay;
     else {
      // Hide duplicate rows for a plugin:version when not viewing by-category
      id = table.rows[i].cells[1].getAttribute('data-id');
      if (nameList[id] == 1) table.rows[i].style.display = 'none';
      nameList[id] = 1;
    }
  }
}
function showhideCategory(col) {
  var row = col.parentNode.nextSibling;
  var newDisplay = row && row.style.display == 'none' ? '' : 'none';
  for (; row && row.cells.length > 1; row = row.nextSibling)
    row.style.display = newDisplay;
}

Behaviour.specify("#filter-box", '_table', 0, function(e) {
      function applyFilter() {
          var filter = e.value.toLowerCase();
          ["TR.plugin","TR.plugin-category"].each(function(clz) {
            var encountered = {};
            var items = document.getElementsBySelector(clz);
            for (var i=0; i<items.length; i++) {
                var visible = (filter=="" || items[i].innerHTML.toLowerCase().indexOf(filter)>=0);
                var name = items[i].cells && items[i].cells.length > 1
                        ? items[i].cells[1].getAttribute('data-id')
                        : items[i].getAttribute("name");
                if (visible && name != null) {
                    console.log(name);
                    if (encountered[name]) {
                        visible = false;
                    }
                    encountered[name] = true;
                }
                items[i].style.display = (visible ? "" : "none");
            }
          });

          layoutUpdateCallback.call();
      }

      e.onkeyup = applyFilter;
});

/**
 * Code for handling the enable/disable behavior based on plugin
 * dependencies and dependants.
 */
(function(){
    function selectAll(selector, element) {
        if (element) {
            return $(element).select(selector);
        } else {
            return Element.select(undefined, selector);
        }
    }
    function select(selector, element) {
        var elementsBySelector = selectAll(selector, element);
        if (elementsBySelector.length > 0) {
            return  elementsBySelector[0];
        } else {
            return undefined;
        }
    }

    /**
     * Wait for document onload.
     */
    Element.observe(window, "load", function() {
        var pluginsTable = select('#plugins');
        var pluginTRs = selectAll('.plugin', pluginsTable);

        if (!pluginTRs) {
            return;
        }

        var pluginI18n = select('.plugins.i18n');
        function i18n(messageId) {
            return pluginI18n.getAttribute('data-' + messageId);
        }

        // Create a map of the plugin rows, making it easy to index them.
        var plugins = {};
        for (var i = 0; i < pluginTRs.length; i++) {
            var pluginTR = pluginTRs[i];
            var pluginId = pluginTR.getAttribute('data-plugin-id');

            plugins[pluginId] = pluginTR;
        }

        function getPluginTR(pluginId) {
            return plugins[pluginId];
        }
        function getPluginName(pluginId) {
            var pluginTR = getPluginTR(pluginId);
            if (pluginTR) {
                return pluginTR.getAttribute('data-plugin-name');
            } else {
                return pluginId;
            }
        }

        function processSpanSet(spans) {
            var ids = [];
            for (var i = 0; i < spans.length; i++) {
                var span = spans[i];
                var pluginId = span.getAttribute('data-plugin-id');
                var pluginName = getPluginName(pluginId);

                span.update(pluginName);
                ids.push(pluginId);
            }
            return ids;
        }

        function markAllDependantsDisabled(pluginTR) {
            var jenkinsPluginMetadata = pluginTR.jenkinsPluginMetadata;
            var dependantIds = jenkinsPluginMetadata.dependantIds;

            if (dependantIds) {
                // If the only dependant is jenkins-core (it's a bundle plugin), then lets
                // treat it like all its dependants are disabled. We're really only interested in
                // dependant plugins in this case.
                // Note: This does not cover "implied" dependencies ala detached plugins. See https://goo.gl/lQHrUh
                if (dependantIds.length === 1 && dependantIds[0] === 'jenkins-core') {
                    pluginTR.addClassName('all-dependants-disabled');
                    return;
                }

                for (var i = 0; i < dependantIds.length; i++) {
                    var dependantId = dependantIds[i];

                    if (dependantId === 'jenkins-core') {
                        // Jenkins core is always enabled. So, make sure it's not possible to disable/uninstall
                        // any plugins that it "depends" on. (we sill have bundled plugins)
                        pluginTR.removeClassName('all-dependants-disabled');
                        return;
                    }

                    // The dependant is a plugin....
                    var dependantPluginTr = getPluginTR(dependantId);
                    if (dependantPluginTr && dependantPluginTr.jenkinsPluginMetadata.enableInput.checked) {
                        // One of the plugins that depend on this plugin, is marked as enabled.
                        pluginTR.removeClassName('all-dependants-disabled');
                        return;
                    }
                }
            }

            pluginTR.addClassName('all-dependants-disabled');
        }

        function markHasDisabledDependencies(pluginTR) {
            var jenkinsPluginMetadata = pluginTR.jenkinsPluginMetadata;
            var dependencyIds = jenkinsPluginMetadata.dependencyIds;

            if (dependencyIds) {
                for (var i = 0; i < dependencyIds.length; i++) {
                    var dependencyPluginTr = getPluginTR(dependencyIds[i]);
                    if (dependencyPluginTr && !dependencyPluginTr.jenkinsPluginMetadata.enableInput.checked) {
                        // One of the plugins that this plugin depend on, is marked as disabled.
                        pluginTR.addClassName('has-disabled-dependency');
                        return;
                    }
                }
            }

            pluginTR.removeClassName('has-disabled-dependency');
        }

        function setEnableWidgetStates() {
            for (var i = 0; i < pluginTRs.length; i++) {
                var pluginMetadata = pluginTRs[i].jenkinsPluginMetadata;
                if (pluginTRs[i].hasClassName('has-dependants-but-disabled')) {
                    if (pluginMetadata.enableInput.checked) {
                            pluginTRs[i].removeClassName('has-dependants-but-disabled');
                        }
                }
                markAllDependantsDisabled(pluginTRs[i]);
                markHasDisabledDependencies(pluginTRs[i]);
            }
        }

        function addDependencyInfoRow(pluginTR, infoTR) {
            infoTR.addClassName('plugin-dependency-info');
            pluginTR.insert({
                after: infoTR
            });
        }
        function removeDependencyInfoRow(pluginTR) {
            var nextRows = pluginTR.nextSiblings();
            if (nextRows && nextRows.length > 0) {
                var nextRow = nextRows[0];
                if (nextRow.hasClassName('plugin-dependency-info')) {
                    nextRow.remove();
                }
            }
        }

        function populateEnableDisableInfo(pluginTR, infoContainer) {
            var pluginMetadata = pluginTR.jenkinsPluginMetadata;

            // Remove all existing class info
            infoContainer.removeAttribute('class');
            infoContainer.addClassName('enable-state-info');

            if (pluginTR.hasClassName('has-disabled-dependency')) {
                var dependenciesDiv = pluginMetadata.dependenciesDiv;
                var dependencySpans = pluginMetadata.dependencies;

                infoContainer.update('
' + i18n('cannot-enable') + '

' + i18n('disabled-dependencies') + '.
');

                // Go through each dependency  element. Show the spans where the dependency is
                // disabled. Hide the others.
                for (var i = 0; i < dependencySpans.length; i++) {
                    var dependencySpan = dependencySpans[i];
                    var pluginId = dependencySpan.getAttribute('data-plugin-id');
                    var depPluginTR = getPluginTR(pluginId);
                    var depPluginMetadata = depPluginTR.jenkinsPluginMetadata;
                    if (depPluginMetadata.enableInput.checked) {
                        // It's enabled ... hide the span
                        dependencySpan.setStyle({display: 'none'});
                    } else {
                        // It's disabled ... show the span
                        dependencySpan.setStyle({display: 'inline-block'});
                    }
                }

                dependenciesDiv.setStyle({display: 'inherit'});
                infoContainer.appendChild(dependenciesDiv);

                return true;
            } if (pluginTR.hasClassName('has-dependants')) {
                if (!pluginTR.hasClassName('all-dependants-disabled')) {
                    var dependantIds = pluginMetadata.dependantIds;

                    // If the only dependant is jenkins-core (it's a bundle plugin), then lets
                    // treat it like all its dependants are disabled. We're really only interested in
                    // dependant plugins in this case.
                    // Note: This does not cover "implied" dependencies ala detached plugins. See https://goo.gl/lQHrUh
                    if (dependantIds.length === 1 && dependantIds[0] === 'jenkins-core') {
                        pluginTR.addClassName('all-dependants-disabled');
                        return false;
                    }

                    var dependantsDiv = pluginMetadata.dependantsDiv;
                    var dependantSpans = pluginMetadata.dependants;

                    infoContainer.update('
' + i18n('cannot-disable') + '

' + i18n('enabled-dependants') + '.
');

                    // Go through each dependant  element. Show the spans where the dependant is
                    // enabled. Hide the others.
                    for (var i = 0; i < dependantSpans.length; i++) {
                        var dependantSpan = dependantSpans[i];
                        var dependantId = dependantSpan.getAttribute('data-plugin-id');

                        if (dependantId === 'jenkins-core') {
                            // show the span
                            dependantSpan.setStyle({display: 'inline-block'});
                        } else {
                            var depPluginTR = getPluginTR(dependantId);
                            var depPluginMetadata = depPluginTR.jenkinsPluginMetadata;
                            if (depPluginMetadata.enableInput.checked) {
                                // It's enabled ... show the span
                                dependantSpan.setStyle({display: 'inline-block'});
                            } else {
                                // It's disabled ... hide the span
                                dependantSpan.setStyle({display: 'none'});
                            }
                        }
                    }

                    dependantsDiv.setStyle({display: 'inherit'});
                    infoContainer.appendChild(dependantsDiv);

                    return true;
                }
            }

            return false;
        }

        function populateUninstallInfo(pluginTR, infoContainer) {
            // Remove all existing class info
            infoContainer.removeAttribute('class');
            infoContainer.addClassName('uninstall-state-info');

            if (pluginTR.hasClassName('has-dependants')) {
                var pluginMetadata = pluginTR.jenkinsPluginMetadata;
                var dependantsDiv = pluginMetadata.dependantsDiv;
                var dependantSpans = pluginMetadata.dependants;

                infoContainer.update('
' + i18n('cannot-uninstall') + '

' + i18n('installed-dependants') + '.
');

                // Go through each dependant  element. Show them all.
                for (var i = 0; i < dependantSpans.length; i++) {
                    var dependantSpan = dependantSpans[i];
                    dependantSpan.setStyle({display: 'inline-block'});
                }

                dependantsDiv.setStyle({display: 'inherit'});
                infoContainer.appendChild(dependantsDiv);

                return true;
            }

            return false;
        }

        function initPluginRowHandling(pluginTR) {
            var enableInput = select('.enable input', pluginTR);
            var dependenciesDiv = select('.dependency-list', pluginTR);
            var dependantsDiv = select('.dependant-list', pluginTR);
            var enableTD = select('td.enable', pluginTR);
            var uninstallTD = select('td.uninstall', pluginTR);

            pluginTR.jenkinsPluginMetadata = {
                enableInput: enableInput,
                dependenciesDiv: dependenciesDiv,
                dependantsDiv: dependantsDiv
            };

            if (dependenciesDiv) {
                pluginTR.jenkinsPluginMetadata.dependencies = selectAll('span', dependenciesDiv);
                pluginTR.jenkinsPluginMetadata.dependencyIds = processSpanSet(pluginTR.jenkinsPluginMetadata.dependencies);
            }
            if (dependantsDiv) {
                pluginTR.jenkinsPluginMetadata.dependants = selectAll('span', dependantsDiv);
                pluginTR.jenkinsPluginMetadata.dependantIds = processSpanSet(pluginTR.jenkinsPluginMetadata.dependants);
            }

            // Setup event handlers...

            // Toggling of the enable/disable checkbox requires a check and possible
            // change of visibility on the same checkbox on other plugins.
            Element.observe(enableInput, 'click', function() {
                setEnableWidgetStates();
            });

            //
            var infoTR = document.createElement("tr");
            var infoTD = document.createElement("td");
            var infoDiv = document.createElement("div");
            infoTR.appendChild(infoTD)
            infoTD.appendChild(infoDiv)
            infoTD.setAttribute('colspan', '6'); // This is the cell that all info will be added to.
            infoDiv.setStyle({display: 'inherit'});

            // We don't want the info row to appear immediately. We wait for e.g. 1 second and if the mouse
            // is still in there (hasn't left the cell) then we show. The following code is for clearing the
            // show timeout where the mouse has left before the timeout has fired.
            var showInfoTimeout = undefined;
            function clearShowInfoTimeout() {
                if (showInfoTimeout) {
                    clearTimeout(showInfoTimeout);
                }
                showInfoTimeout = undefined;
            }

            // Handle mouse in/out of the enable/disable cell (left most cell).
            Element.observe(enableTD, 'mouseenter', function() {
                showInfoTimeout = setTimeout(function() {
                    showInfoTimeout = undefined;
                    infoDiv.update('');
                    if (populateEnableDisableInfo(pluginTR, infoDiv)) {
                        addDependencyInfoRow(pluginTR, infoTR);
                    }
                }, 1000);
            });
            Element.observe(enableTD, 'mouseleave', function() {
                clearShowInfoTimeout();
                removeDependencyInfoRow(pluginTR);
            });

            // Handle mouse in/out of the uninstall cell (right most cell).
            Element.observe(uninstallTD, 'mouseenter', function() {
                showInfoTimeout = setTimeout(function() {
                    showInfoTimeout = undefined;
                    infoDiv.update('');
                    if (populateUninstallInfo(pluginTR, infoDiv)) {
                        addDependencyInfoRow(pluginTR, infoTR);
                    }
                }, 1000);
            });
            Element.observe(uninstallTD, 'mouseleave', function() {
                clearShowInfoTimeout();
                removeDependencyInfoRow(pluginTR);
            });
        }

        for (var i = 0; i < pluginTRs.length; i++) {
            initPluginRowHandling(pluginTRs[i]);
        }

        setEnableWidgetStates();
    });
}());
<commit_msg>Remove left-over debugging statement<commit_after>function showhideCategories(hdr,on) {
  var table = hdr.parentNode.parentNode.parentNode,
      newDisplay = on ? '' : 'none',
      nameList = new Array(), id;
  for (var i = 1; i < table.rows.length; i++) {
    if (on || table.rows[i].cells.length == 1)
      table.rows[i].style.display = newDisplay;
     else {
      // Hide duplicate rows for a plugin:version when not viewing by-category
      id = table.rows[i].cells[1].getAttribute('data-id');
      if (nameList[id] == 1) table.rows[i].style.display = 'none';
      nameList[id] = 1;
    }
  }
}
function showhideCategory(col) {
  var row = col.parentNode.nextSibling;
  var newDisplay = row && row.style.display == 'none' ? '' : 'none';
  for (; row && row.cells.length > 1; row = row.nextSibling)
    row.style.display = newDisplay;
}

Behaviour.specify("#filter-box", '_table', 0, function(e) {
      function applyFilter() {
          var filter = e.value.toLowerCase();
          ["TR.plugin","TR.plugin-category"].each(function(clz) {
            var encountered = {};
            var items = document.getElementsBySelector(clz);
            for (var i=0; i<items.length; i++) {
                var visible = (filter=="" || items[i].innerHTML.toLowerCase().indexOf(filter)>=0);
                var name = items[i].cells && items[i].cells.length > 1
                        ? items[i].cells[1].getAttribute('data-id')
                        : items[i].getAttribute("name");
                if (visible && name != null) {
                    if (encountered[name]) {
                        visible = false;
                    }
                    encountered[name] = true;
                }
                items[i].style.display = (visible ? "" : "none");
            }
          });

          layoutUpdateCallback.call();
      }

      e.onkeyup = applyFilter;
});

/**
 * Code for handling the enable/disable behavior based on plugin
 * dependencies and dependants.
 */
(function(){
    function selectAll(selector, element) {
        if (element) {
            return $(element).select(selector);
        } else {
            return Element.select(undefined, selector);
        }
    }
    function select(selector, element) {
        var elementsBySelector = selectAll(selector, element);
        if (elementsBySelector.length > 0) {
            return  elementsBySelector[0];
        } else {
            return undefined;
        }
    }

    /**
     * Wait for document onload.
     */
    Element.observe(window, "load", function() {
        var pluginsTable = select('#plugins');
        var pluginTRs = selectAll('.plugin', pluginsTable);

        if (!pluginTRs) {
            return;
        }

        var pluginI18n = select('.plugins.i18n');
        function i18n(messageId) {
            return pluginI18n.getAttribute('data-' + messageId);
        }

        // Create a map of the plugin rows, making it easy to index them.
        var plugins = {};
        for (var i = 0; i < pluginTRs.length; i++) {
            var pluginTR = pluginTRs[i];
            var pluginId = pluginTR.getAttribute('data-plugin-id');

            plugins[pluginId] = pluginTR;
        }

        function getPluginTR(pluginId) {
            return plugins[pluginId];
        }
        function getPluginName(pluginId) {
            var pluginTR = getPluginTR(pluginId);
            if (pluginTR) {
                return pluginTR.getAttribute('data-plugin-name');
            } else {
                return pluginId;
            }
        }

        function processSpanSet(spans) {
            var ids = [];
            for (var i = 0; i < spans.length; i++) {
                var span = spans[i];
                var pluginId = span.getAttribute('data-plugin-id');
                var pluginName = getPluginName(pluginId);

                span.update(pluginName);
                ids.push(pluginId);
            }
            return ids;
        }

        function markAllDependantsDisabled(pluginTR) {
            var jenkinsPluginMetadata = pluginTR.jenkinsPluginMetadata;
            var dependantIds = jenkinsPluginMetadata.dependantIds;

            if (dependantIds) {
                // If the only dependant is jenkins-core (it's a bundle plugin), then lets
                // treat it like all its dependants are disabled. We're really only interested in
                // dependant plugins in this case.
                // Note: This does not cover "implied" dependencies ala detached plugins. See https://goo.gl/lQHrUh
                if (dependantIds.length === 1 && dependantIds[0] === 'jenkins-core') {
                    pluginTR.addClassName('all-dependants-disabled');
                    return;
                }

                for (var i = 0; i < dependantIds.length; i++) {
                    var dependantId = dependantIds[i];

                    if (dependantId === 'jenkins-core') {
                        // Jenkins core is always enabled. So, make sure it's not possible to disable/uninstall
                        // any plugins that it "depends" on. (we sill have bundled plugins)
                        pluginTR.removeClassName('all-dependants-disabled');
                        return;
                    }

                    // The dependant is a plugin....
                    var dependantPluginTr = getPluginTR(dependantId);
                    if (dependantPluginTr && dependantPluginTr.jenkinsPluginMetadata.enableInput.checked) {
                        // One of the plugins that depend on this plugin, is marked as enabled.
                        pluginTR.removeClassName('all-dependants-disabled');
                        return;
                    }
                }
            }

            pluginTR.addClassName('all-dependants-disabled');
        }

        function markHasDisabledDependencies(pluginTR) {
            var jenkinsPluginMetadata = pluginTR.jenkinsPluginMetadata;
            var dependencyIds = jenkinsPluginMetadata.dependencyIds;

            if (dependencyIds) {
                for (var i = 0; i < dependencyIds.length; i++) {
                    var dependencyPluginTr = getPluginTR(dependencyIds[i]);
                    if (dependencyPluginTr && !dependencyPluginTr.jenkinsPluginMetadata.enableInput.checked) {
                        // One of the plugins that this plugin depend on, is marked as disabled.
                        pluginTR.addClassName('has-disabled-dependency');
                        return;
                    }
                }
            }

            pluginTR.removeClassName('has-disabled-dependency');
        }

        function setEnableWidgetStates() {
            for (var i = 0; i < pluginTRs.length; i++) {
                var pluginMetadata = pluginTRs[i].jenkinsPluginMetadata;
                if (pluginTRs[i].hasClassName('has-dependants-but-disabled')) {
                    if (pluginMetadata.enableInput.checked) {
                            pluginTRs[i].removeClassName('has-dependants-but-disabled');
                        }
                }
                markAllDependantsDisabled(pluginTRs[i]);
                markHasDisabledDependencies(pluginTRs[i]);
            }
        }

        function addDependencyInfoRow(pluginTR, infoTR) {
            infoTR.addClassName('plugin-dependency-info');
            pluginTR.insert({
                after: infoTR
            });
        }
        function removeDependencyInfoRow(pluginTR) {
            var nextRows = pluginTR.nextSiblings();
            if (nextRows && nextRows.length > 0) {
                var nextRow = nextRows[0];
                if (nextRow.hasClassName('plugin-dependency-info')) {
                    nextRow.remove();
                }
            }
        }

        function populateEnableDisableInfo(pluginTR, infoContainer) {
            var pluginMetadata = pluginTR.jenkinsPluginMetadata;

            // Remove all existing class info
            infoContainer.removeAttribute('class');
            infoContainer.addClassName('enable-state-info');

            if (pluginTR.hasClassName('has-disabled-dependency')) {
                var dependenciesDiv = pluginMetadata.dependenciesDiv;
                var dependencySpans = pluginMetadata.dependencies;

                infoContainer.update('
' + i18n('cannot-enable') + '

' + i18n('disabled-dependencies') + '.
');

                // Go through each dependency  element. Show the spans where the dependency is
                // disabled. Hide the others.
                for (var i = 0; i < dependencySpans.length; i++) {
                    var dependencySpan = dependencySpans[i];
                    var pluginId = dependencySpan.getAttribute('data-plugin-id');
                    var depPluginTR = getPluginTR(pluginId);
                    var depPluginMetadata = depPluginTR.jenkinsPluginMetadata;
                    if (depPluginMetadata.enableInput.checked) {
                        // It's enabled ... hide the span
                        dependencySpan.setStyle({display: 'none'});
                    } else {
                        // It's disabled ... show the span
                        dependencySpan.setStyle({display: 'inline-block'});
                    }
                }

                dependenciesDiv.setStyle({display: 'inherit'});
                infoContainer.appendChild(dependenciesDiv);

                return true;
            } if (pluginTR.hasClassName('has-dependants')) {
                if (!pluginTR.hasClassName('all-dependants-disabled')) {
                    var dependantIds = pluginMetadata.dependantIds;

                    // If the only dependant is jenkins-core (it's a bundle plugin), then lets
                    // treat it like all its dependants are disabled. We're really only interested in
                    // dependant plugins in this case.
                    // Note: This does not cover "implied" dependencies ala detached plugins. See https://goo.gl/lQHrUh
                    if (dependantIds.length === 1 && dependantIds[0] === 'jenkins-core') {
                        pluginTR.addClassName('all-dependants-disabled');
                        return false;
                    }

                    var dependantsDiv = pluginMetadata.dependantsDiv;
                    var dependantSpans = pluginMetadata.dependants;

                    infoContainer.update('
' + i18n('cannot-disable') + '

' + i18n('enabled-dependants') + '.
');

                    // Go through each dependant  element. Show the spans where the dependant is
                    // enabled. Hide the others.
                    for (var i = 0; i < dependantSpans.length; i++) {
                        var dependantSpan = dependantSpans[i];
                        var dependantId = dependantSpan.getAttribute('data-plugin-id');

                        if (dependantId === 'jenkins-core') {
                            // show the span
                            dependantSpan.setStyle({display: 'inline-block'});
                        } else {
                            var depPluginTR = getPluginTR(dependantId);
                            var depPluginMetadata = depPluginTR.jenkinsPluginMetadata;
                            if (depPluginMetadata.enableInput.checked) {
                                // It's enabled ... show the span
                                dependantSpan.setStyle({display: 'inline-block'});
                            } else {
                                // It's disabled ... hide the span
                                dependantSpan.setStyle({display: 'none'});
                            }
                        }
                    }

                    dependantsDiv.setStyle({display: 'inherit'});
                    infoContainer.appendChild(dependantsDiv);

                    return true;
                }
            }

            return false;
        }

        function populateUninstallInfo(pluginTR, infoContainer) {
            // Remove all existing class info
            infoContainer.removeAttribute('class');
            infoContainer.addClassName('uninstall-state-info');

            if (pluginTR.hasClassName('has-dependants')) {
                var pluginMetadata = pluginTR.jenkinsPluginMetadata;
                var dependantsDiv = pluginMetadata.dependantsDiv;
                var dependantSpans = pluginMetadata.dependants;

                infoContainer.update('
' + i18n('cannot-uninstall') + '

' + i18n('installed-dependants') + '.
');

                // Go through each dependant  element. Show them all.
                for (var i = 0; i < dependantSpans.length; i++) {
                    var dependantSpan = dependantSpans[i];
                    dependantSpan.setStyle({display: 'inline-block'});
                }

                dependantsDiv.setStyle({display: 'inherit'});
                infoContainer.appendChild(dependantsDiv);

                return true;
            }

            return false;
        }

        function initPluginRowHandling(pluginTR) {
            var enableInput = select('.enable input', pluginTR);
            var dependenciesDiv = select('.dependency-list', pluginTR);
            var dependantsDiv = select('.dependant-list', pluginTR);
            var enableTD = select('td.enable', pluginTR);
            var uninstallTD = select('td.uninstall', pluginTR);

            pluginTR.jenkinsPluginMetadata = {
                enableInput: enableInput,
                dependenciesDiv: dependenciesDiv,
                dependantsDiv: dependantsDiv
            };

            if (dependenciesDiv) {
                pluginTR.jenkinsPluginMetadata.dependencies = selectAll('span', dependenciesDiv);
                pluginTR.jenkinsPluginMetadata.dependencyIds = processSpanSet(pluginTR.jenkinsPluginMetadata.dependencies);
            }
            if (dependantsDiv) {
                pluginTR.jenkinsPluginMetadata.dependants = selectAll('span', dependantsDiv);
                pluginTR.jenkinsPluginMetadata.dependantIds = processSpanSet(pluginTR.jenkinsPluginMetadata.dependants);
            }

            // Setup event handlers...

            // Toggling of the enable/disable checkbox requires a check and possible
            // change of visibility on the same checkbox on other plugins.
            Element.observe(enableInput, 'click', function() {
                setEnableWidgetStates();
            });

            //
            var infoTR = document.createElement("tr");
            var infoTD = document.createElement("td");
            var infoDiv = document.createElement("div");
            infoTR.appendChild(infoTD)
            infoTD.appendChild(infoDiv)
            infoTD.setAttribute('colspan', '6'); // This is the cell that all info will be added to.
            infoDiv.setStyle({display: 'inherit'});

            // We don't want the info row to appear immediately. We wait for e.g. 1 second and if the mouse
            // is still in there (hasn't left the cell) then we show. The following code is for clearing the
            // show timeout where the mouse has left before the timeout has fired.
            var showInfoTimeout = undefined;
            function clearShowInfoTimeout() {
                if (showInfoTimeout) {
                    clearTimeout(showInfoTimeout);
                }
                showInfoTimeout = undefined;
            }

            // Handle mouse in/out of the enable/disable cell (left most cell).
            Element.observe(enableTD, 'mouseenter', function() {
                showInfoTimeout = setTimeout(function() {
                    showInfoTimeout = undefined;
                    infoDiv.update('');
                    if (populateEnableDisableInfo(pluginTR, infoDiv)) {
                        addDependencyInfoRow(pluginTR, infoTR);
                    }
                }, 1000);
            });
            Element.observe(enableTD, 'mouseleave', function() {
                clearShowInfoTimeout();
                removeDependencyInfoRow(pluginTR);
            });

            // Handle mouse in/out of the uninstall cell (right most cell).
            Element.observe(uninstallTD, 'mouseenter', function() {
                showInfoTimeout = setTimeout(function() {
                    showInfoTimeout = undefined;
                    infoDiv.update('');
                    if (populateUninstallInfo(pluginTR, infoDiv)) {
                        addDependencyInfoRow(pluginTR, infoTR);
                    }
                }, 1000);
            });
            Element.observe(uninstallTD, 'mouseleave', function() {
                clearShowInfoTimeout();
                removeDependencyInfoRow(pluginTR);
            });
        }

        for (var i = 0; i < pluginTRs.length; i++) {
            initPluginRowHandling(pluginTRs[i]);
        }

        setEnableWidgetStates();
    });
}());
<|endoftext|>