<commit_before>"use strict";

/*  ---------------------------------------------------------------------------

    A tests launcher. Runs tests for all languages and all exchanges, in
    parallel, with a humanized error reporting.

    Usage: node run-tests [--php] [--js] [--python] [--python3] [exchange] [symbol]

    --------------------------------------------------------------------------- */

const fs = require ('fs')
const log = require ('ololog')//.configure ({ indent: { pattern: '  ' }})
const ansi = require ('ansicolor').nice

/*  --------------------------------------------------------------------------- */

process.on ('uncaughtException',  e => { log.bright.red.error (e); process.exit (1) })
process.on ('unhandledRejection', e => { log.bright.red.error (e); process.exit (1) })

/*  --------------------------------------------------------------------------- */

const [,, ...args] = process.argv

const keys = {

    '--js': false,      // run JavaScript tests only
    '--php': false,     // run PHP tests only
    '--python': false,  // run Python 2 tests only
    '--python3': false, // run Python 3 tests only
}

let exchanges = []
let symbol = 'all'
let maxConcurrency = Number.MAX_VALUE // no limit

for (const arg of args) {
    if (arg.startsWith ('--'))               { keys[arg] = true }
    else if (arg.includes ('/'))             { symbol = arg }
    else if (Number.isFinite (Number (arg))) { maxConcurrency = Number (arg) }
    else                                     { exchanges.push (arg) }
}

/*  --------------------------------------------------------------------------- */

if (!exchanges.length) {

    if (!fs.existsSync ('exchanges.json')) {

        log.bright.red ('\n\tNo', 'exchanges.json'.white, 'found, please run', 'npm run build'.white, 'to generate it!\n')
        process.exit (1)
    }

    exchanges = JSON.parse (fs.readFileSync ('exchanges.json')).ids
}

/*  --------------------------------------------------------------------------- */

const sleep = s => new Promise (resolve => setTimeout (resolve, s*1000))
const timeout = (s, promise) => Promise.race ([ promise, sleep (s).then (() => { throw new Error ('timed out') }) ])

/*  --------------------------------------------------------------------------- */

const exec = (bin, ...args) =>

/*  A custom version of child_process.exec that captures both stdout and
    stderr,  not separating them into distinct buffers — so that we can show
    the same output as if it were running in a terminal.                        */

    timeout (120, new Promise (return_ => {

        const ps = require ('child_process').spawn (bin, args)

        let output = ''
        let stderr = ''
        let hasWarnings = false

        ps.stdout.on ('data', data => { output += data.toString () })
        ps.stderr.on ('data', data => { output += data.toString (); stderr += data.toString (); hasWarnings = true })

        ps.on ('exit', code => {
            return_ ({
                failed: code !== 0,
                output,
                hasOutput: output.trim ().length > 0,
                hasWarnings,
                warnings: ansi.strip (stderr).match (/^\[[^\]]+\]/g) || []
            })
        })

    })).catch (e => ({

        failed: true,
        output: e.message

    })).then (x => Object.assign (x, { hasOutput: x.output.length > 0 }))

/*  ------------------------------------------------------------------------ */

// const execWithRetry = () => {

//     // Sometimes execution (on a remote CI server) is just fails with no
//     // apparent reason, leaving an empty stdout/stderr behind. I suspect
//     // it's related to out-of-memory errors. So in that case we will re-try
//     // until it eventually finalizes.
// }

/*  ------------------------------------------------------------------------ */

let numExchangesTested = 0

/*  Tests of different languages for the same exchange should be run
    sequentially to prevent the interleaving nonces problem.
    ------------------------------------------------------------------------ */

const sequentialMap = async (input, fn) => {

    const result = []
    for (const item of input) { result.push (await fn (item)) }
    return result
}

/*  ------------------------------------------------------------------------ */

const testExchange = async (exchange) => {

    const nonce = Date.now ()

/*  Run tests for all/selected languages (in parallel)     */

    const args = [exchange, ...symbol === 'all' ? [] : symbol]
        , allTests = [

            { language: 'JavaScript', key: '--js',      exec: ['node',      'test/test.js',       ...args] },
            { language: 'Python',     key: '--python',  exec: ['python',    'test/test.py',       ...args] },
            { language: 'Python 3',   key: '--python3', exec: ['python3',   'test/test_async.py', ...args] },
            { language: 'PHP',        key: '--php',     exec: ['php', '-f', 'test/test.php',      ...args] }
        ]
        , selectedTests  = allTests.filter (t => keys[t.key])
        , scheduledTests = selectedTests.length ? selectedTests : allTests
        , completeTests  = await sequentialMap (scheduledTests, async test => Object.assign (test, await exec (...test.exec)))
        , failed         = completeTests.find (test => test.failed)
        , hasWarnings    = completeTests.find (test => test.hasWarnings)
        , warnings       = completeTests.reduce ((total, { warnings }) => total.concat (warnings), [])

/*  Print interactive log output    */

    numExchangesTested++

    const percentsDone = ((numExchangesTested / exchanges.length) * 100).toFixed (0) + '%'

    log.bright (('[' + percentsDone + ']').dim, 'Testing', exchange.cyan, (failed      ? 'FAIL'.red :
                                                                          (hasWarnings ? (warnings.length ? warnings.join (' ') : 'WARN').yellow
                                                                                : 'OK'.green)))

/*  Return collected data to main loop     */

    return {

        exchange,
        failed,
        hasWarnings,
        explain () {
            for (const { language, failed, output, hasWarnings } of completeTests) {
                if (failed || hasWarnings) {

                    if (failed) { log.bright ('\nFAILED'.bgBrightRed.white, exchange.red,    '(' + language + '):\n') }
                    else        { log.bright ('\nWARN'.yellow.inverse,      exchange.yellow, '(' + language + '):\n') }

                    log.indent (1) (output)
                }
            }
        }
    }
}

/*  ------------------------------------------------------------------------ */

function TaskPool (maxConcurrency) {

    const pending = []
        , queue   = []

    let numActive = 0

    return {

        pending,

        run (task) {

            if (numActive >= maxConcurrency) { // queue task

                return new Promise (resolve => queue.push (() => this.run (task).then (resolve)))

            } else { // execute task

                let p = task ().then (x => {
                    numActive--
                    return (queue.length && (numActive < maxConcurrency))
                                ? queue.shift () ().then (() => x)
                                : x
                })
                numActive++
                pending.push (p)
                return p
            }
        }
    }
}

/*  ------------------------------------------------------------------------ */

async function testAllExchanges () {

    const taskPool = TaskPool (maxConcurrency)
    const results = []

    for (const exchange of exchanges) {
        taskPool.run (() => testExchange (exchange).then (x => results.push (x)))
    }

    await Promise.all (taskPool.pending)

    return results
}

/*  ------------------------------------------------------------------------ */

(async function () {

    log.bright.magenta.noPretty ('Testing'.white, Object.assign (
                                                            { exchanges, symbol, keys },
                                                            maxConcurrency >= Number.MAX_VALUE ? {} : { maxConcurrency }))

    const tested    = await testAllExchanges ()
        , warnings  = tested.filter (t => !t.failed && t.hasWarnings)
        , failed    = tested.filter (t =>  t.failed)
        , succeeded = tested.filter (t => !t.failed && !t.hasWarnings)

    log.newline ()

    warnings.forEach (t => t.explain ())
    failed.forEach (t => t.explain ())

    log.newline ()

    if (failed.length)   { log.noPretty.bright.red    ('FAIL'.bgBrightRed.white, failed.map (t => t.exchange)) }
    if (warnings.length) { log.noPretty.bright.yellow ('WARN'.inverse,           warnings.map (t => t.exchange)) }

    log.newline ()

    log.bright ('All done,', [failed.length    && (failed.length    + ' failed')   .red,
                              succeeded.length && (succeeded.length + ' succeeded').green,
                              warnings.length  && (warnings.length  + ' warnings') .yellow].filter (s => s).join (', '))

    if (failed.length) {

        await sleep (10) // to fight TravisCI log truncation issue, see https://github.com/travis-ci/travis-ci/issues/8189
        process.exit (1)

    } else {
        process.exit (0)
    }

}) ();

/*  ------------------------------------------------------------------------ */
<commit_msg>Broke run-tests.js intentionally (to test how the previous travis fix works)<commit_after>"use strict";

throw new Error ('debug me')

/*  ---------------------------------------------------------------------------

    A tests launcher. Runs tests for all languages and all exchanges, in
    parallel, with a humanized error reporting.

    Usage: node run-tests [--php] [--js] [--python] [--python3] [exchange] [symbol]

    --------------------------------------------------------------------------- */

const fs = require ('fs')
const log = require ('ololog')//.configure ({ indent: { pattern: '  ' }})
const ansi = require ('ansicolor').nice

/*  --------------------------------------------------------------------------- */

process.on ('uncaughtException',  e => { log.bright.red.error (e); process.exit (1) })
process.on ('unhandledRejection', e => { log.bright.red.error (e); process.exit (1) })

/*  --------------------------------------------------------------------------- */

const [,, ...args] = process.argv

const keys = {

    '--js': false,      // run JavaScript tests only
    '--php': false,     // run PHP tests only
    '--python': false,  // run Python 2 tests only
    '--python3': false, // run Python 3 tests only
}

let exchanges = []
let symbol = 'all'
let maxConcurrency = Number.MAX_VALUE // no limit

for (const arg of args) {
    if (arg.startsWith ('--'))               { keys[arg] = true }
    else if (arg.includes ('/'))             { symbol = arg }
    else if (Number.isFinite (Number (arg))) { maxConcurrency = Number (arg) }
    else                                     { exchanges.push (arg) }
}

/*  --------------------------------------------------------------------------- */

if (!exchanges.length) {

    if (!fs.existsSync ('exchanges.json')) {

        log.bright.red ('\n\tNo', 'exchanges.json'.white, 'found, please run', 'npm run build'.white, 'to generate it!\n')
        process.exit (1)
    }

    exchanges = JSON.parse (fs.readFileSync ('exchanges.json')).ids
}

/*  --------------------------------------------------------------------------- */

const sleep = s => new Promise (resolve => setTimeout (resolve, s*1000))
const timeout = (s, promise) => Promise.race ([ promise, sleep (s).then (() => { throw new Error ('timed out') }) ])

/*  --------------------------------------------------------------------------- */

const exec = (bin, ...args) =>

/*  A custom version of child_process.exec that captures both stdout and
    stderr,  not separating them into distinct buffers — so that we can show
    the same output as if it were running in a terminal.                        */

    timeout (120, new Promise (return_ => {

        const ps = require ('child_process').spawn (bin, args)

        let output = ''
        let stderr = ''
        let hasWarnings = false

        ps.stdout.on ('data', data => { output += data.toString () })
        ps.stderr.on ('data', data => { output += data.toString (); stderr += data.toString (); hasWarnings = true })

        ps.on ('exit', code => {
            return_ ({
                failed: code !== 0,
                output,
                hasOutput: output.trim ().length > 0,
                hasWarnings,
                warnings: ansi.strip (stderr).match (/^\[[^\]]+\]/g) || []
            })
        })

    })).catch (e => ({

        failed: true,
        output: e.message

    })).then (x => Object.assign (x, { hasOutput: x.output.length > 0 }))

/*  ------------------------------------------------------------------------ */

// const execWithRetry = () => {

//     // Sometimes execution (on a remote CI server) is just fails with no
//     // apparent reason, leaving an empty stdout/stderr behind. I suspect
//     // it's related to out-of-memory errors. So in that case we will re-try
//     // until it eventually finalizes.
// }

/*  ------------------------------------------------------------------------ */

let numExchangesTested = 0

/*  Tests of different languages for the same exchange should be run
    sequentially to prevent the interleaving nonces problem.
    ------------------------------------------------------------------------ */

const sequentialMap = async (input, fn) => {

    const result = []
    for (const item of input) { result.push (await fn (item)) }
    return result
}

/*  ------------------------------------------------------------------------ */

const testExchange = async (exchange) => {

    const nonce = Date.now ()

/*  Run tests for all/selected languages (in parallel)     */

    const args = [exchange, ...symbol === 'all' ? [] : symbol]
        , allTests = [

            { language: 'JavaScript', key: '--js',      exec: ['node',      'test/test.js',       ...args] },
            { language: 'Python',     key: '--python',  exec: ['python',    'test/test.py',       ...args] },
            { language: 'Python 3',   key: '--python3', exec: ['python3',   'test/test_async.py', ...args] },
            { language: 'PHP',        key: '--php',     exec: ['php', '-f', 'test/test.php',      ...args] }
        ]
        , selectedTests  = allTests.filter (t => keys[t.key])
        , scheduledTests = selectedTests.length ? selectedTests : allTests
        , completeTests  = await sequentialMap (scheduledTests, async test => Object.assign (test, await exec (...test.exec)))
        , failed         = completeTests.find (test => test.failed)
        , hasWarnings    = completeTests.find (test => test.hasWarnings)
        , warnings       = completeTests.reduce ((total, { warnings }) => total.concat (warnings), [])

/*  Print interactive log output    */

    numExchangesTested++

    const percentsDone = ((numExchangesTested / exchanges.length) * 100).toFixed (0) + '%'

    log.bright (('[' + percentsDone + ']').dim, 'Testing', exchange.cyan, (failed      ? 'FAIL'.red :
                                                                          (hasWarnings ? (warnings.length ? warnings.join (' ') : 'WARN').yellow
                                                                                : 'OK'.green)))

/*  Return collected data to main loop     */

    return {

        exchange,
        failed,
        hasWarnings,
        explain () {
            for (const { language, failed, output, hasWarnings } of completeTests) {
                if (failed || hasWarnings) {

                    if (failed) { log.bright ('\nFAILED'.bgBrightRed.white, exchange.red,    '(' + language + '):\n') }
                    else        { log.bright ('\nWARN'.yellow.inverse,      exchange.yellow, '(' + language + '):\n') }

                    log.indent (1) (output)
                }
            }
        }
    }
}

/*  ------------------------------------------------------------------------ */

function TaskPool (maxConcurrency) {

    const pending = []
        , queue   = []

    let numActive = 0

    return {

        pending,

        run (task) {

            if (numActive >= maxConcurrency) { // queue task

                return new Promise (resolve => queue.push (() => this.run (task).then (resolve)))

            } else { // execute task

                let p = task ().then (x => {
                    numActive--
                    return (queue.length && (numActive < maxConcurrency))
                                ? queue.shift () ().then (() => x)
                                : x
                })
                numActive++
                pending.push (p)
                return p
            }
        }
    }
}

/*  ------------------------------------------------------------------------ */

async function testAllExchanges () {

    const taskPool = TaskPool (maxConcurrency)
    const results = []

    for (const exchange of exchanges) {
        taskPool.run (() => testExchange (exchange).then (x => results.push (x)))
    }

    await Promise.all (taskPool.pending)

    return results
}

/*  ------------------------------------------------------------------------ */

(async function () {

    log.bright.magenta.noPretty ('Testing'.white, Object.assign (
                                                            { exchanges, symbol, keys },
                                                            maxConcurrency >= Number.MAX_VALUE ? {} : { maxConcurrency }))

    const tested    = await testAllExchanges ()
        , warnings  = tested.filter (t => !t.failed && t.hasWarnings)
        , failed    = tested.filter (t =>  t.failed)
        , succeeded = tested.filter (t => !t.failed && !t.hasWarnings)

    log.newline ()

    warnings.forEach (t => t.explain ())
    failed.forEach (t => t.explain ())

    log.newline ()

    if (failed.length)   { log.noPretty.bright.red    ('FAIL'.bgBrightRed.white, failed.map (t => t.exchange)) }
    if (warnings.length) { log.noPretty.bright.yellow ('WARN'.inverse,           warnings.map (t => t.exchange)) }

    log.newline ()

    log.bright ('All done,', [failed.length    && (failed.length    + ' failed')   .red,
                              succeeded.length && (succeeded.length + ' succeeded').green,
                              warnings.length  && (warnings.length  + ' warnings') .yellow].filter (s => s).join (', '))

    if (failed.length) {

        await sleep (10) // to fight TravisCI log truncation issue, see https://github.com/travis-ci/travis-ci/issues/8189
        process.exit (1)

    } else {
        process.exit (0)
    }

}) ();

/*  ------------------------------------------------------------------------ */
<|endoftext|>


<commit_before>

function startChart () {
    var table = jQuery("#issuetable").first();
    var people = [];
    categories = ['NotReady', 'ReadyForDev', 'PeerReview', 'QA', 'Done'];
    series = [];
    ASSIGNED = 0;
    UNASSIGNED = 1;
    COLORS = [[205, 82, 217],[109, 179, 50],[118, 108, 204],[196, 146, 58],[185, 81, 166],[211, 73, 115],[77, 162, 150],[213, 75, 51],[117, 159, 209],[151, 81, 55],[77, 87, 122],[76, 94, 43],[201, 138, 176],[126, 59, 93],[205, 82, 217],[109, 179, 50],[118, 108, 204],[196, 146, 58],[185, 81, 166],[103, 163, 88],[211, 73, 115],[77, 162, 150],[213, 75, 51],[117, 159, 209],[151, 81, 55],[77, 87, 122],[76, 94, 43],[201, 138, 176],[126, 59, 93]  ];

    rows = jQuery(table).find("tr.issuerow");

    report = [];

    jQuery.each(rows, function (idx, row) {
        var rowdata = {};
        rowdata.summary = jQuery(row).find(".summary").text().trim();
        rowdata.status = jQuery(row).find(".status").text().trim();
        rowdata.assignee = jQuery(row).find(".assignee").text().trim();
        rowdata.timeoriginalestimate = jQuery(row).find(".timeoriginalestimate").text().trim();
        rowdata.issuekey = jQuery(row).find(".issuekey").text().trim();
        report.push(rowdata);
    });

    buckets = {};
    buckets.NotReady = [];
    buckets.ReadyForDev = [];
    buckets.PeerReview = [];
    buckets.QA = [];
    buckets.Done = [];

    stati = [];

    jQuery.each(report, function (idx, row) {
        stati.push(row.status);

        // check to see if any special conditions will set this row to a different category than the status would suggest

        row = checkForSpecialCase(row);

            switch (row.status) {
                case "Open":
                case "Ready for Development":
                case "Development in Progress":
                    addToBucket(row, buckets, "ReadyForDev", series, people);
                    break;
                case "Ready for Code Review":
                case "Code Review in Progress":
                    addToBucket(row, buckets, "PeerReview", series, people);
                    break;
                case "Ready for QA Review":
                case "QA Review in Progress":
                    addToBucket(row, buckets, "QA", series, people);
                    break;
                case "Ready for Security Audit":
                    addToBucket(row, buckets, "Done", series, people);
                    buckets.Done.push(row);
                    break;
                default:
                    addToBucket(row, buckets, "NotReady", series, people);
                    break;
            }
    });

    console.log(categories);
    console.log(series);

    console.log(people);

    drawChart(categories, series);

};

function addToBucket(row, buckets, category, series, people) {
    if (_.indexOf(people, row.assignee) == -1) {
        people.push(row.assignee);
    }
    var colorTripple = COLORS[_.indexOf(people, row.assignee)];
    var colorString = rgbToHex(colorTripple[0], colorTripple[1], colorTripple[2]);
    buckets[category].push(row);
    var stack =  (row.assignee.toLowerCase() == 'unassigned') ? "unassigned" : "assigned";
    var data = [0, 0, 0, 0, 0];

    data[_.indexOf(categories, category)] += convertTime(row.timeoriginalestimate);
    series.push({ name: row.summary, data: data, stack: stack, assignee: row.assignee, status: row.status, issuekey: row.issuekey, color: colorString });
};

function checkForSpecialCase(row) {
    var who = row.assignee.toLowerCase();
    if (who.indexOf("jose") > -1 || who.indexOf("tsitsi") > -1) {
        console.log("fixing category for oddball element");
        row.status = "Assigned to a Non-Developer";
    }

    return row;

}

function convertTime(time_string) {
    return parseInt(time_string);

};

function drawChart(categories, series) {
	jQuery("body").prepend("

");
        jQuery('div#report').highcharts({
            chart: {
                type: 'column'
            },
            title: {
                text: 'Agile Task Board'
            },
            xAxis: {
                categories: categories
            },
            yAxis: {
                min: 0,
                title: {
                    text: 'Number of Hours'
                },
                stackLabels: {
                    enabled: true,
                    style: {
                        fontWeight: 'bold',
                        color: (Highcharts.theme && Highcharts.theme.textColor) || 'gray'
                    }
                }
            },
            legend: { enabled: false},
            tooltip: {
                headerFormat: '{point.x}
',
                formatter:  function() {

                    console.log(series);

                    return "i: " + this.series._i + "
Title:" + this.series.name + "
Ticket#: " + series[this.series._i].issuekey + "
Assigned: " + series[this.series._i].assignee + "
Status: " + series[this.series._i].status;
                }
            },
            plotOptions: {
                column: {
                    stacking: 'normal',
                    dataLabels: {
                        enabled: true,
                        color: (Highcharts.theme && Highcharts.theme.dataLabelsColor) || 'white',
                        style: {
                            textShadow: '0 0 3px black'
                        }
                    }
                }
            },
            series: series
        });

}

function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
}

function rgbToHex(r, g, b) {
    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}

jQuery.getScript("https://code.highcharts.com/highcharts.js", function() {
	startChart();
});<commit_msg>Fixed comment style so the code will work as a bookmarklet<commit_after>

function startChart () {
    var table = jQuery("#issuetable").first();
    var people = [];
    categories = ['NotReady', 'ReadyForDev', 'PeerReview', 'QA', 'Done'];
    series = [];
    ASSIGNED = 0;
    UNASSIGNED = 1;
    COLORS = [[205, 82, 217],[109, 179, 50],[118, 108, 204],[196, 146, 58],[185, 81, 166],[211, 73, 115],[77, 162, 150],[213, 75, 51],[117, 159, 209],[151, 81, 55],[77, 87, 122],[76, 94, 43],[201, 138, 176],[126, 59, 93],[205, 82, 217],[109, 179, 50],[118, 108, 204],[196, 146, 58],[185, 81, 166],[103, 163, 88],[211, 73, 115],[77, 162, 150],[213, 75, 51],[117, 159, 209],[151, 81, 55],[77, 87, 122],[76, 94, 43],[201, 138, 176],[126, 59, 93]  ];

    rows = jQuery(table).find("tr.issuerow");

    report = [];

    jQuery.each(rows, function (idx, row) {
        var rowdata = {};
        rowdata.summary = jQuery(row).find(".summary").text().trim();
        rowdata.status = jQuery(row).find(".status").text().trim();
        rowdata.assignee = jQuery(row).find(".assignee").text().trim();
        rowdata.timeoriginalestimate = jQuery(row).find(".timeoriginalestimate").text().trim();
        rowdata.issuekey = jQuery(row).find(".issuekey").text().trim();
        report.push(rowdata);
    });

    buckets = {};
    buckets.NotReady = [];
    buckets.ReadyForDev = [];
    buckets.PeerReview = [];
    buckets.QA = [];
    buckets.Done = [];

    stati = [];

    jQuery.each(report, function (idx, row) {
        stati.push(row.status);

        /* check to see if any special conditions will set this row to a different category than the status would suggest */

        row = checkForSpecialCase(row);

            switch (row.status) {
                case "Open":
                case "Ready for Development":
                case "Development in Progress":
                    addToBucket(row, buckets, "ReadyForDev", series, people);
                    break;
                case "Ready for Code Review":
                case "Code Review in Progress":
                    addToBucket(row, buckets, "PeerReview", series, people);
                    break;
                case "Ready for QA Review":
                case "QA Review in Progress":
                    addToBucket(row, buckets, "QA", series, people);
                    break;
                case "Ready for Security Audit":
                    addToBucket(row, buckets, "Done", series, people);
                    buckets.Done.push(row);
                    break;
                default:
                    addToBucket(row, buckets, "NotReady", series, people);
                    break;
            }
    });

    console.log(categories);
    console.log(series);

    console.log(people);

    drawChart(categories, series);

};

function addToBucket(row, buckets, category, series, people) {
    if (_.indexOf(people, row.assignee) == -1) {
        people.push(row.assignee);
    }
    var colorTripple = COLORS[_.indexOf(people, row.assignee)];
    var colorString = rgbToHex(colorTripple[0], colorTripple[1], colorTripple[2]);
    buckets[category].push(row);
    var stack =  (row.assignee.toLowerCase() == 'unassigned') ? "unassigned" : "assigned";
    var data = [0, 0, 0, 0, 0];

    data[_.indexOf(categories, category)] += convertTime(row.timeoriginalestimate);
    series.push({ name: row.summary, data: data, stack: stack, assignee: row.assignee, status: row.status, issuekey: row.issuekey, color: colorString });
};

function checkForSpecialCase(row) {
    var who = row.assignee.toLowerCase();
    if (who.indexOf("jose") > -1 || who.indexOf("tsitsi") > -1) {
        console.log("fixing category for oddball element");
        row.status = "Assigned to a Non-Developer";
    }

    return row;

}

function convertTime(time_string) {
    return parseInt(time_string);

};

function drawChart(categories, series) {
	jQuery("body").prepend("

");
        jQuery('div#report').highcharts({
            chart: {
                type: 'column'
            },
            title: {
                text: 'Agile Task Board'
            },
            xAxis: {
                categories: categories
            },
            yAxis: {
                min: 0,
                title: {
                    text: 'Number of Hours'
                },
                stackLabels: {
                    enabled: true,
                    style: {
                        fontWeight: 'bold',
                        color: (Highcharts.theme && Highcharts.theme.textColor) || 'gray'
                    }
                }
            },
            legend: { enabled: false},
            tooltip: {
                headerFormat: '{point.x}
',
                formatter:  function() {

                    console.log(series);

                    return "i: " + this.series._i + "
Title:" + this.series.name + "
Ticket#: " + series[this.series._i].issuekey + "
Assigned: " + series[this.series._i].assignee + "
Status: " + series[this.series._i].status;
                }
            },
            plotOptions: {
                column: {
                    stacking: 'normal',
                    dataLabels: {
                        enabled: true,
                        color: (Highcharts.theme && Highcharts.theme.dataLabelsColor) || 'white',
                        style: {
                            textShadow: '0 0 3px black'
                        }
                    }
                }
            },
            series: series
        });

}

function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
}

function rgbToHex(r, g, b) {
    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}

jQuery.getScript("https://code.highcharts.com/highcharts.js", function() {
	startChart();
});<|endoftext|>


<commit_before>/**
 * @fileoverview
 * Creates npm package files for a given component and AMP version.
 */

const [extension, ampVersion, extensionVersion] = process.argv.slice(2);
const fastGlob = require('fast-glob');
const marked = require('marked');
const path = require('path');
const posthtml = require('posthtml');
const {copyFile, readFile} = require('fs-extra');
const {getNameWithoutComponentPrefix} = require('../tasks/bento-helpers');
const {getSemver} = require('./utils');
const {log} = require('../common/logging');
const {stat, writeFile} = require('fs/promises');
const {valid} = require('semver');

const packageName = getNameWithoutComponentPrefix(extension);
const dir = `extensions/${extension}/${extensionVersion}`;

/**
 * Determines whether to skip
 * @return {Promise<boolean>}
 */
async function shouldSkip() {
  try {
    await stat(dir);
    return false;
  } catch {
    log(`${extension} ${extensionVersion} : skipping, does not exist`);
    return true;
  }
}

/**
 * Returns relative paths to all the extension's CSS file
 *
 * @return {Promise<string[]>}
 */
async function getStylesheets() {
  const extDir = `${dir}/dist`.split('/').join(path.sep);
  const files = await fastGlob(path.join(extDir, '**', '*.css'));
  return files.map((file) => path.relative(extDir, file));
}

/**
 * @param {string} html
 * @return {Promise<string>}
 */
async function getHtmlTextContent(html) {
  const getTextContent = (node, out = []) => {
    if (typeof node === 'string') {
      out.push(node);
    } else if (Array.isArray(node)) {
      for (const child of node) {
        getTextContent(child, out);
      }
    } else if (node?.content) {
      getTextContent(node.content, out);
    }
    return out;
  };
  const result = await posthtml([
    (node) => getTextContent(node).join(''),
  ]).process(html);
  return result.html.trim();
}

/**
 * @param {string} markdown
 * @param {number} maxLengthChars
 * @return {Promise<?string>}
 */
async function getFirstParagraphOrSentence(markdown, maxLengthChars) {
  const token = marked.lexer(markdown).find(({type}) => type === 'paragraph');
  if (!token) {
    return null;
  }
  const html = marked.parser([token]);
  const paragraph = await getHtmlTextContent(html);
  if (paragraph.length <= maxLengthChars) {
    return paragraph;
  }
  const [sentence] = paragraph.split('.', 1);
  if (sentence.length < maxLengthChars) {
    return `${sentence}.`;
  }
  return null;
}

/**
 * Get package description from its README.md file, or a generic description
 * if the file does not exist.
 * @return {Promise<string>}
 */
async function getDescription() {
  let description;
  try {
    const markdown = await readFile(`${dir}/README.md`, 'utf8');
    description = await getFirstParagraphOrSentence(markdown, 200);
  } catch {}
  return description || `Bento ${packageName} Component`;
}

/**
 * Write package.json
 * @return {Promise<void>}
 */
async function writePackageJson() {
  const version = getSemver(extensionVersion, ampVersion);
  if (!valid(version) || ampVersion.length != 13) {
    log(
      'Invalid semver version',
      version,
      'or AMP version',
      ampVersion,
      'or extension version',
      extensionVersion
    );
    process.exitCode = 1;
    return;
  }

  const exports = {
    '.': {
      import: './dist/web-component.module.js',
      require: './dist/web-component.js',
    },
    './web-component': {
      import: './dist/web-component.module.js',
      require: './dist/web-component.js',
    },
    './preact': {
      import: './dist/component-preact.module.js',
      require: './dist/component-preact.js',
    },
    './react': {
      import: './dist/component-react.module.js',
      require: './dist/component-react.js',
    },
  };

  for (const stylesheet of await getStylesheets()) {
    exports[`./${stylesheet}`] = `./dist/${stylesheet}`;
  }

  const json = {
    name: `@bentoproject/${packageName}`,
    version,
    description: await getDescription(),
    author: 'Bento Authors',
    license: 'Apache-2.0',
    main: './dist/web-component.js',
    module: './dist/web-component.module.js',
    exports,
    files: ['dist/*', 'react.js'],
    repository: {
      type: 'git',
      url: 'https://github.com/ampproject/amphtml.git',
      directory: `extensions/${extension}/${extensionVersion}`,
    },
    homepage: `https://github.com/ampproject/amphtml/tree/main/extensions/${extension}/${extensionVersion}`,
    peerDependencies: {
      preact: '^10.2.1',
      react: '^17.0.0',
    },
  };

  try {
    await writeFile(`${dir}/package.json`, JSON.stringify(json, null, 2));
    log(
      json.name,
      extensionVersion,
      ': created package.json for',
      json.version
    );
  } catch (e) {
    log(e);
    process.exitCode = 1;
    return;
  }
}

/**
 * Write react.js
 * @return {Promise<void>}
 */
async function writeReactJs() {
  const content = "module.exports = require('./dist/component-react');";
  try {
    await writeFile(`${dir}/react.js`, content);
    log(packageName, extensionVersion, ': created react.js');
  } catch (e) {
    log(e);
    process.exitCode = 1;
    return;
  }
}

/**
 * todo(kvchari): temporarily copy styles to root of each package to support importing styles from root
 * Remove when we begin properly proxying style imports.
 * See issue from more information:
 * @return {Promise<void>}
 */
async function copyCssToRoot() {
  try {
    const extDir = path.join('extension', extension, '1.0');
    const preactCssDist = path.join(extDir, 'dist', 'styles.css');
    const preactCssRoot = path.join(extDir, 'styles.css');
    await copyFile(preactCssDist, preactCssRoot);
    log('Copied', preactCssDist, 'to npm package root');
  } catch (e) {
    log(e);
    process.exitCode = 1;
    return;
  }
}

/**
 * Main
 * @return {Promise<void>}
 */
async function main() {
  if (await shouldSkip()) {
    return;
  }
  await writePackageJson();
  await writeReactJs();
  await copyCssToRoot();
}

main();
<commit_msg>fix typo for dir (#37140)<commit_after>/**
 * @fileoverview
 * Creates npm package files for a given component and AMP version.
 */

const [extension, ampVersion, extensionVersion] = process.argv.slice(2);
const fastGlob = require('fast-glob');
const marked = require('marked');
const path = require('path');
const posthtml = require('posthtml');
const {copyFile, readFile} = require('fs-extra');
const {getNameWithoutComponentPrefix} = require('../tasks/bento-helpers');
const {getSemver} = require('./utils');
const {log} = require('../common/logging');
const {stat, writeFile} = require('fs/promises');
const {valid} = require('semver');

const packageName = getNameWithoutComponentPrefix(extension);
const dir = `extensions/${extension}/${extensionVersion}`;

/**
 * Determines whether to skip
 * @return {Promise<boolean>}
 */
async function shouldSkip() {
  try {
    await stat(dir);
    return false;
  } catch {
    log(`${extension} ${extensionVersion} : skipping, does not exist`);
    return true;
  }
}

/**
 * Returns relative paths to all the extension's CSS file
 *
 * @return {Promise<string[]>}
 */
async function getStylesheets() {
  const extDir = `${dir}/dist`.split('/').join(path.sep);
  const files = await fastGlob(path.join(extDir, '**', '*.css'));
  return files.map((file) => path.relative(extDir, file));
}

/**
 * @param {string} html
 * @return {Promise<string>}
 */
async function getHtmlTextContent(html) {
  const getTextContent = (node, out = []) => {
    if (typeof node === 'string') {
      out.push(node);
    } else if (Array.isArray(node)) {
      for (const child of node) {
        getTextContent(child, out);
      }
    } else if (node?.content) {
      getTextContent(node.content, out);
    }
    return out;
  };
  const result = await posthtml([
    (node) => getTextContent(node).join(''),
  ]).process(html);
  return result.html.trim();
}

/**
 * @param {string} markdown
 * @param {number} maxLengthChars
 * @return {Promise<?string>}
 */
async function getFirstParagraphOrSentence(markdown, maxLengthChars) {
  const token = marked.lexer(markdown).find(({type}) => type === 'paragraph');
  if (!token) {
    return null;
  }
  const html = marked.parser([token]);
  const paragraph = await getHtmlTextContent(html);
  if (paragraph.length <= maxLengthChars) {
    return paragraph;
  }
  const [sentence] = paragraph.split('.', 1);
  if (sentence.length < maxLengthChars) {
    return `${sentence}.`;
  }
  return null;
}

/**
 * Get package description from its README.md file, or a generic description
 * if the file does not exist.
 * @return {Promise<string>}
 */
async function getDescription() {
  let description;
  try {
    const markdown = await readFile(`${dir}/README.md`, 'utf8');
    description = await getFirstParagraphOrSentence(markdown, 200);
  } catch {}
  return description || `Bento ${packageName} Component`;
}

/**
 * Write package.json
 * @return {Promise<void>}
 */
async function writePackageJson() {
  const version = getSemver(extensionVersion, ampVersion);
  if (!valid(version) || ampVersion.length != 13) {
    log(
      'Invalid semver version',
      version,
      'or AMP version',
      ampVersion,
      'or extension version',
      extensionVersion
    );
    process.exitCode = 1;
    return;
  }

  const exports = {
    '.': {
      import: './dist/web-component.module.js',
      require: './dist/web-component.js',
    },
    './web-component': {
      import: './dist/web-component.module.js',
      require: './dist/web-component.js',
    },
    './preact': {
      import: './dist/component-preact.module.js',
      require: './dist/component-preact.js',
    },
    './react': {
      import: './dist/component-react.module.js',
      require: './dist/component-react.js',
    },
  };

  for (const stylesheet of await getStylesheets()) {
    exports[`./${stylesheet}`] = `./dist/${stylesheet}`;
  }

  const json = {
    name: `@bentoproject/${packageName}`,
    version,
    description: await getDescription(),
    author: 'Bento Authors',
    license: 'Apache-2.0',
    main: './dist/web-component.js',
    module: './dist/web-component.module.js',
    exports,
    files: ['dist/*', 'react.js'],
    repository: {
      type: 'git',
      url: 'https://github.com/ampproject/amphtml.git',
      directory: `extensions/${extension}/${extensionVersion}`,
    },
    homepage: `https://github.com/ampproject/amphtml/tree/main/extensions/${extension}/${extensionVersion}`,
    peerDependencies: {
      preact: '^10.2.1',
      react: '^17.0.0',
    },
  };

  try {
    await writeFile(`${dir}/package.json`, JSON.stringify(json, null, 2));
    log(
      json.name,
      extensionVersion,
      ': created package.json for',
      json.version
    );
  } catch (e) {
    log(e);
    process.exitCode = 1;
    return;
  }
}

/**
 * Write react.js
 * @return {Promise<void>}
 */
async function writeReactJs() {
  const content = "module.exports = require('./dist/component-react');";
  try {
    await writeFile(`${dir}/react.js`, content);
    log(packageName, extensionVersion, ': created react.js');
  } catch (e) {
    log(e);
    process.exitCode = 1;
    return;
  }
}

/**
 * todo(kvchari): temporarily copy styles to root of each package to support importing styles from root
 * Remove when we begin properly proxying style imports.
 * See issue from more information:
 * @return {Promise<void>}
 */
async function copyCssToRoot() {
  try {
    const extDir = path.join('extensions', extension, '1.0');
    const preactCssDist = path.join(extDir, 'dist', 'styles.css');
    const preactCssRoot = path.join(extDir, 'styles.css');
    await copyFile(preactCssDist, preactCssRoot);
    log('Copied', preactCssDist, 'to npm package root');
  } catch (e) {
    log(e);
    process.exitCode = 1;
    return;
  }
}

/**
 * Main
 * @return {Promise<void>}
 */
async function main() {
  if (await shouldSkip()) {
    return;
  }
  await writePackageJson();
  await writeReactJs();
  await copyCssToRoot();
}

main();
<|endoftext|>


<commit_before>/**
 * Selectron assists in counting selection caret's offset in relation to
 * different DOM elements, and saving and restoring selection ranges. Saving
 * and restoring of selection/ranges will work across heavy DOM manipulation if used correctly.
 *
 * @module spytext/selectron
 */

/**
 * Position of a caret (start or end)
 *
 * @typedef {Object} Position
 * @property {Node} ref - Reference node to count `offset` from
 * @property {number} offset - Steps from start of `ref`
 */

/**
 * Positions of start and end caret
 *
 * @typedef {Object} Positions
 * @property {Position} start - Position of start caret
 * @property {Position} end - Position of end caret
 */

var sectionTags = [ 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI' ];

/**
 * Tests whether `node` is a section element, ie if it is of nodeType 1
 * and its tagName is in `sectionTags`.
 *
 * @param	{Node} node - The node to check if it is a section element
 * @return {boolean}
 */
function isSection(node) {
	return node.nodeType === 1 && sectionTags.indexOf(node.tagName) !== -1;
	//return node.nodeType === 1 && !getComputedStyle(node).display.match(/inline/);
}

function getLastPosition(node) {
	var ref = node.lastChild;

	while(ref) {
		if(ref.nodeType === 3)
			break;

		ref = ref.lastChild || ref.previousSibling;
	}

	return {
		ref: ref || node,
		offset: ref ? ref.textContent.length : 0
	};
}

function filter(node) {
	return (!$(node).is('UL,OL') && (node.nodeName !== 'BR' || node.nextSibling && !$(node.nextSibling).is('UL,OL'))) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}

filter.acceptNode = filter;

/**
 * Uses a TreeWalker to traverse and count the offset from `root` to `ref`
 *
 * This is essentially the inverse of restore
 *
 * @static
 * @param	{Node} root - Element to count relative
 * @param	{Node} ref - Element to reach
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all steps
 * @return {number}	The total offset of the caret relative to element
 */
function count(root, ref, countAll) {
	var node,
		off = 0,
		tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL, countAll ? null : filter, false);

	if(ref)
		tw.currentNode = ref;

	node = tw.currentNode;

	while(node) {
		if(node !== root && (countAll || isSection(node) || node.nodeName === 'BR'))
			off++;

		if(node !== ref && node.nodeType === 3)
			off = off + node.textContent.length;

		node = ref ? tw.previousNode() : tw.nextNode();
	}

	return off;
}

/**
 * Return the total offset for a caret (start or end) of a range relative to a
 * specific element
 *
 * @static
 * @param	{Element} element - Containing element to count offset relative to
 * @param	{string} [caret=start] - Parameter that determines whether we should fetch start or endContainer
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all elements
 * @return {number}	The total offset of the caret relative to `element`
 * @see count
 */
function offset(element, caret, countAll) {
	var rng = s().getRangeAt(0),
		ref = rng[(caret || 'start') + 'Container'],
		off = rng[(caret || 'start') + 'Offset'];

	element = element || $(ref).closest(sectionTags.join(','))[0];

	if(ref.nodeType === 1 && off > 0) {
		ref = ref.childNodes[off - 1];
		off = ref.textContent.length;
	}

	return count(element, ref, countAll) + off;
}

/**
 * Uses `root` and `offset` to traverse the DOM to find the innermost element
 * where the caret should be placed.
 *
 * This is essentially the inverse of count
 *
 * @static
 * @param	{Element} root
 * @param	{number} offset
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all elements
 * @return {Position}
 */
function restore(root, offset, countAll) {
	var node,
		ref = root;

	// IE fix. IE does not allow treeWalkers to be created on textNodes
	if(root.nodeType === 1) {
		tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL, countAll ? null : filter, false);

		while((node = tw.nextNode())) {
			if(countAll || isSection(node) || node.nodeName === 'BR') {
				if(offset === 0)
					break;

				offset--;
			}

			ref = node;

			if(node.nodeType === 3) {
				if(offset > node.textContent.length)
					offset = offset - node.textContent.length;
				else
					break;
			}
		}
	}

	return {
		ref: ref,
		offset: offset
	};
}

var descendants = require('./descendants');

var s = window.getSelection;

module.exports = {
	restore: restore,

	count: count,

	offset: offset,

	/**
	 * Returns all elements contained by the current selection
	 *
	 * @param	{Element} element - Element to count relative
	 * @param	{Node[]|NodeList|string|number|function} [ufo] - Filters
	 * @param	{number} [levels] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @param	{boolean} [partlyContained] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @param	{boolean} [onlyDeepest] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @return {Node[]}	Array contained all contained nodes
	 */
	contained: function(element, opts, partlyContained) {
		var _selectron = this,
			check,
			nodes = [];

		if(_.isArray(opts))
			check = opts;
		else if(opts instanceof NodeList || opts instanceof HTMLCollection || opts instanceof jQuery)
			check = _.toArray(opts);
		else
			check = descendants(element, opts);

		// loop through all nodes and check if
		// they are contained by the current selection
		check.forEach(function(node) {
			if(_selectron.contains(node, partlyContained))
				nodes.push(node);
		});

		// return any contained nodes
		return nodes;
	},

	/**
	 * Tests whether `node` is contained by the current selection
	 *
	 * @param	{Node} node - Element to count relative
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 */
	contains: function(node, partlyContained) {
		// default, unoverridable behaviour of Selection.containsNode() for textNodes
		// is to always test partlyContained = true
		partlyContained = node.nodeType === 3 ? true : !!partlyContained;

		var sel = s();

		if(sel.containsNode) {
			// simply use Selection objects containsNode native function if it exists
			return sel.containsNode(node, partlyContained);
		} else {
			var rng = sel.getRangeAt(0),
				element = rng.commonAncestorContainer;

			if(element.nodeType !== 1) {
				element = element.parentNode;
			}

			if(element !== node && !$.contains(element, node)) {
				return partlyContained && $.contains(node,element);
			}

			var rangeStartOffset = offset(element, 'start', true),
				rangeEndOffset = offset(element, 'end', true),
				startOffset = count(element, node, true),
				endOffset = node.nodeType === 1 ? startOffset + count(node, null, true) + 1 : startOffset + node.textContent.length;

			return (startOffset >= rangeStartOffset && endOffset <= rangeEndOffset ||
					(partlyContained && ((rangeStartOffset >= startOffset && rangeStartOffset <= endOffset) || (rangeEndOffset >= startOffset && rangeEndOffset <= endOffset))));
		}
	},

	/**
	 * Tests whether all `nodes` are contained by the current selection
	 *
	 * @param	{Node[]} nodes - nodes to test if they are contained
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 * @see contains
	 */
	containsEvery: function(nodes, partlyContained) {
		var that = this;

		return _.toArray(nodes).every(function(node) {
			return that.contains(node, partlyContained);
		});
	},

	/**
	 * Tests whether any of `nodes` are contained by the current selection
	 *
	 * @param	{Node[]} nodes - nodes to test if they are contained
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 * @see contains
	 */
	containsSome: function(nodes, partlyContained) {
		var that = this;

		return _.toArray(nodes).some(function(node) {
			return that.contains(node, partlyContained);
		});

	},

	normalize: function() {
		var rng = this.range(),
			$container = $(rng.endContainer);

		if(!rng.collapsed) {
			if($container.is(sectionTags.join(','))) {
				var ref = $container[0];

				while(!ref.previousSibling)
					ref = ref.parentNode;

				this.set({
					start: {
						ref: rng.startContainer,
						offset: rng.startOffset
					},
					end: getLastPosition(ref.previousSibling)
				});
			}
		} else {
			var section = $container.closest(sectionTags.join(','))[0];
			this.set({
				ref: section,
				offset: offset(section, 'start')
			});
		}
	},

	/**
	 * Return the current selection's (first) range
	 *
	 * @return {Range}
	 */
	range: function() {
		// retrieve the current selection
		var sel = s();

		if(sel.rangeCount > 0)
			// selection has at least one range, return the first
			return sel.getRangeAt(0);
		else
			// selection has no range, return null
			return null;
	},

	/**
	 * Tests whether the caret is currently at the end of a section
	 *
	 * @return {boolean}
	 */
	isAtEndOfSection: function(section) {
		var endContainer = this.range().endContainer,
			$section;

		if(section) {
			if(section !== endContainer && !$.contains(section,endContainer))
				return false;

			$section = $(section);
		} else {
			$section = $(endContainer).closest(sectionTags.join(','));
		}

		var off = offset($section[0], 'end'),
			$nestedList = $section.children('UL,OL'),
			result = $nestedList.length > 0 ? count($section[0], $nestedList[0]) : count($section[0]);

		return off === result;
	},

	/**
	 * Tests whether the caret is currently at the end of a section
	 *
	 * @return {boolean}
	 */
	isAtStartOfSection: function(section) {
		var $section = $(section || this.range().startContainer).closest(sectionTags.join(','));

		return offset($section[0], 'start') === 0;
	},

	/**
	 * Get Positions of start and end caret of current selection
	 *
	 * @param {Element} [element=document.body] - The reference node (to count the offset from)
	 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all steps
	 * @return {Positions} ref element of both start and end Position will be `element`
	 */
	get: function(element, countAll) {
		element = element || document.body;

		return {
			start: {
				ref: element,
				offset: offset(element, 'start', countAll)
			},
			end: {
				ref: element,
				offset: offset(element, 'end', countAll)
			}
		};
	},

	getLastPosition: getLastPosition,

	/**
	 * Sets the current selection to contain `node`
	 *
	 * @param {Node} node - The node to select
	 */
	select: function(node) {
		var textNodes = node.nodeType === 3 ? [ node ] : descendants(node, { nodeType: 3 });

		if(textNodes.length === 0) {
			this.set({ ref: node, offset: 0 });
		} else {
			var first = _.first(textNodes),
				last = _.last(textNodes);

			this.set({
				start: {
					ref: first,
					offset: 0
				},
				end: {
					ref: last,
					offset: last.textContent.length,
				}
			});
		}
	},

	/**
	 * Sets the selection to `position`
	 *
	 * @param {Position|Positions} position - If a Position, a collapsed range will be set with start and end caret set to `position`
	 */
	set: function(position) {
		if(position.ref) {
			position = {
				start: position
			};
		}

		var start = restore(position.start.ref, position.start.offset || 0),
			end = position.end ? restore(position.end.ref, position.end.offset || 0) : start,
			rng = document.createRange(),
			sel = s();

		if(start.ref.nodeName === 'BR') {
			rng.setStartAfter(start.ref);
		} else {
			rng.setStart(start.ref, start.offset);
		}

		if(end.ref.nodeName === 'BR') {
			rng.setEndAfter(end.ref);
		} else {
			rng.setEnd(end.ref, end.offset);
		}

		sel.removeAllRanges();
		sel.addRange(rng);
	}
};
<commit_msg>Use endContainer and offset as default in offset()... because of IE.<commit_after>/**
 * Selectron assists in counting selection caret's offset in relation to
 * different DOM elements, and saving and restoring selection ranges. Saving
 * and restoring of selection/ranges will work across heavy DOM manipulation if used correctly.
 *
 * @module spytext/selectron
 */

/**
 * Position of a caret (start or end)
 *
 * @typedef {Object} Position
 * @property {Node} ref - Reference node to count `offset` from
 * @property {number} offset - Steps from start of `ref`
 */

/**
 * Positions of start and end caret
 *
 * @typedef {Object} Positions
 * @property {Position} start - Position of start caret
 * @property {Position} end - Position of end caret
 */

var sectionTags = [ 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI' ];

/**
 * Tests whether `node` is a section element, ie if it is of nodeType 1
 * and its tagName is in `sectionTags`.
 *
 * @param	{Node} node - The node to check if it is a section element
 * @return {boolean}
 */
function isSection(node) {
	return node.nodeType === 1 && sectionTags.indexOf(node.tagName) !== -1;
	//return node.nodeType === 1 && !getComputedStyle(node).display.match(/inline/);
}

function getLastPosition(node) {
	var ref = node.lastChild;

	while(ref) {
		if(ref.nodeType === 3)
			break;

		ref = ref.lastChild || ref.previousSibling;
	}

	return {
		ref: ref || node,
		offset: ref ? ref.textContent.length : 0
	};
}

function filter(node) {
	return (!$(node).is('UL,OL') && (node.nodeName !== 'BR' || node.nextSibling && !$(node.nextSibling).is('UL,OL'))) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}

filter.acceptNode = filter;

/**
 * Uses a TreeWalker to traverse and count the offset from `root` to `ref`
 *
 * This is essentially the inverse of restore
 *
 * @static
 * @param	{Node} root - Element to count relative
 * @param	{Node} ref - Element to reach
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all steps
 * @return {number}	The total offset of the caret relative to element
 */
function count(root, ref, countAll) {
	var node,
		off = 0,
		tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL, countAll ? null : filter, false);

	if(ref)
		tw.currentNode = ref;

	node = tw.currentNode;

	while(node) {
		if(node !== root && (countAll || isSection(node) || node.nodeName === 'BR'))
			off++;

		if(node !== ref && node.nodeType === 3)
			off = off + node.textContent.length;

		node = ref ? tw.previousNode() : tw.nextNode();
	}

	return off;
}

/**
 * Return the total offset for a caret (start or end) of a range relative to a
 * specific element
 *
 * @static
 * @param	{Element} element - Containing element to count offset relative to
 * @param	{string} [caret=start] - Parameter that determines whether we should fetch start or endContainer
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all elements
 * @return {number}	The total offset of the caret relative to `element`
 * @see count
 */
function offset(element, caret, countAll) {
	var rng = s().getRangeAt(0),
		ref = rng[(caret || 'end') + 'Container'],
		off = rng[(caret || 'end') + 'Offset'];

	element = element || $(ref).closest(sectionTags.join(','))[0];

	if(ref.nodeType === 1 && off > 0) {
		ref = ref.childNodes[off - 1];
		off = ref.textContent.length;
	}

	return count(element, ref, countAll) + off;
}

/**
 * Uses `root` and `offset` to traverse the DOM to find the innermost element
 * where the caret should be placed.
 *
 * This is essentially the inverse of count
 *
 * @static
 * @param	{Element} root
 * @param	{number} offset
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all elements
 * @return {Position}
 */
function restore(root, offset, countAll) {
	var node,
		ref = root;

	// IE fix. IE does not allow treeWalkers to be created on textNodes
	if(root.nodeType === 1) {
		tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL, countAll ? null : filter, false);

		while((node = tw.nextNode())) {
			if(countAll || isSection(node) || node.nodeName === 'BR') {
				if(offset === 0)
					break;

				offset--;
			}

			ref = node;

			if(node.nodeType === 3) {
				if(offset > node.textContent.length)
					offset = offset - node.textContent.length;
				else
					break;
			}
		}
	}

	return {
		ref: ref,
		offset: offset
	};
}

var descendants = require('./descendants');

var s = window.getSelection;

module.exports = {
	restore: restore,

	count: count,

	offset: offset,

	/**
	 * Returns all elements contained by the current selection
	 *
	 * @param	{Element} element - Element to count relative
	 * @param	{Node[]|NodeList|string|number|function} [ufo] - Filters
	 * @param	{number} [levels] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @param	{boolean} [partlyContained] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @param	{boolean} [onlyDeepest] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @return {Node[]}	Array contained all contained nodes
	 */
	contained: function(element, opts, partlyContained) {
		var _selectron = this,
			check,
			nodes = [];

		if(_.isArray(opts))
			check = opts;
		else if(opts instanceof NodeList || opts instanceof HTMLCollection || opts instanceof jQuery)
			check = _.toArray(opts);
		else
			check = descendants(element, opts);

		// loop through all nodes and check if
		// they are contained by the current selection
		check.forEach(function(node) {
			if(_selectron.contains(node, partlyContained))
				nodes.push(node);
		});

		// return any contained nodes
		return nodes;
	},

	/**
	 * Tests whether `node` is contained by the current selection
	 *
	 * @param	{Node} node - Element to count relative
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 */
	contains: function(node, partlyContained) {
		// default, unoverridable behaviour of Selection.containsNode() for textNodes
		// is to always test partlyContained = true
		partlyContained = node.nodeType === 3 ? true : !!partlyContained;

		var sel = s();

		if(sel.containsNode) {
			// simply use Selection objects containsNode native function if it exists
			return sel.containsNode(node, partlyContained);
		} else {
			var rng = sel.getRangeAt(0),
				element = rng.commonAncestorContainer;

			if(element.nodeType !== 1) {
				element = element.parentNode;
			}

			if(element !== node && !$.contains(element, node)) {
				return partlyContained && $.contains(node,element);
			}

			var rangeStartOffset = offset(element, 'start', true),
				rangeEndOffset = offset(element, 'end', true),
				startOffset = count(element, node, true),
				endOffset = node.nodeType === 1 ? startOffset + count(node, null, true) + 1 : startOffset + node.textContent.length;

			return (startOffset >= rangeStartOffset && endOffset <= rangeEndOffset ||
					(partlyContained && ((rangeStartOffset >= startOffset && rangeStartOffset <= endOffset) || (rangeEndOffset >= startOffset && rangeEndOffset <= endOffset))));
		}
	},

	/**
	 * Tests whether all `nodes` are contained by the current selection
	 *
	 * @param	{Node[]} nodes - nodes to test if they are contained
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 * @see contains
	 */
	containsEvery: function(nodes, partlyContained) {
		var that = this;

		return _.toArray(nodes).every(function(node) {
			return that.contains(node, partlyContained);
		});
	},

	/**
	 * Tests whether any of `nodes` are contained by the current selection
	 *
	 * @param	{Node[]} nodes - nodes to test if they are contained
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 * @see contains
	 */
	containsSome: function(nodes, partlyContained) {
		var that = this;

		return _.toArray(nodes).some(function(node) {
			return that.contains(node, partlyContained);
		});

	},

	normalize: function() {
		var rng = this.range(),
			$container = $(rng.endContainer);

		if(!rng.collapsed) {
			if($container.is(sectionTags.join(','))) {
				var ref = $container[0];

				while(!ref.previousSibling)
					ref = ref.parentNode;

				this.set({
					start: {
						ref: rng.startContainer,
						offset: rng.startOffset
					},
					end: getLastPosition(ref.previousSibling)
				});
			}
		} else {
			var section = $container.closest(sectionTags.join(','))[0];
			this.set({
				ref: section,
				offset: offset(section, 'start')
			});
		}
	},

	/**
	 * Return the current selection's (first) range
	 *
	 * @return {Range}
	 */
	range: function() {
		// retrieve the current selection
		var sel = s();

		if(sel.rangeCount > 0)
			// selection has at least one range, return the first
			return sel.getRangeAt(0);
		else
			// selection has no range, return null
			return null;
	},

	/**
	 * Tests whether the caret is currently at the end of a section
	 *
	 * @return {boolean}
	 */
	isAtEndOfSection: function(section) {
		var endContainer = this.range().endContainer,
			$section;

		if(section) {
			if(section !== endContainer && !$.contains(section,endContainer))
				return false;

			$section = $(section);
		} else {
			$section = $(endContainer).closest(sectionTags.join(','));
		}

		var off = offset($section[0], 'end'),
			$nestedList = $section.children('UL,OL'),
			result = $nestedList.length > 0 ? count($section[0], $nestedList[0]) : count($section[0]);

		return off === result;
	},

	/**
	 * Tests whether the caret is currently at the end of a section
	 *
	 * @return {boolean}
	 */
	isAtStartOfSection: function(section) {
		var $section = $(section || this.range().startContainer).closest(sectionTags.join(','));

		return offset($section[0], 'start') === 0;
	},

	/**
	 * Get Positions of start and end caret of current selection
	 *
	 * @param {Element} [element=document.body] - The reference node (to count the offset from)
	 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all steps
	 * @return {Positions} ref element of both start and end Position will be `element`
	 */
	get: function(element, countAll) {
		element = element || document.body;

		return {
			start: {
				ref: element,
				offset: offset(element, 'start', countAll)
			},
			end: {
				ref: element,
				offset: offset(element, 'end', countAll)
			}
		};
	},

	getLastPosition: getLastPosition,

	/**
	 * Sets the current selection to contain `node`
	 *
	 * @param {Node} node - The node to select
	 */
	select: function(node) {
		var textNodes = node.nodeType === 3 ? [ node ] : descendants(node, { nodeType: 3 });

		if(textNodes.length === 0) {
			this.set({ ref: node, offset: 0 });
		} else {
			var first = _.first(textNodes),
				last = _.last(textNodes);

			this.set({
				start: {
					ref: first,
					offset: 0
				},
				end: {
					ref: last,
					offset: last.textContent.length,
				}
			});
		}
	},

	/**
	 * Sets the selection to `position`
	 *
	 * @param {Position|Positions} position - If a Position, a collapsed range will be set with start and end caret set to `position`
	 */
	set: function(position) {
		if(position.ref) {
			position = {
				start: position
			};
		}

		var start = restore(position.start.ref, position.start.offset || 0),
			end = position.end ? restore(position.end.ref, position.end.offset || 0) : start,
			rng = document.createRange(),
			sel = s();

		if(start.ref.nodeName === 'BR') {
			rng.setStartAfter(start.ref);
		} else {
			rng.setStart(start.ref, start.offset);
		}

		if(end.ref.nodeName === 'BR') {
			rng.setEndAfter(end.ref);
		} else {
			rng.setEnd(end.ref, end.offset);
		}

		sel.removeAllRanges();
		sel.addRange(rng);
	}
};
<|endoftext|>


<commit_before>//noinspection JSUnresolvedVariable
const APICredentials = {
    cognitoUserPoolId: process.env.REACT_APP_AWS_COGNITO_USER_POOL_ID,
    cognitoAppClientId: process.env.REACT_APP_AWS_COGNITO_APP_CLIENT_ID,
    awsCognitoIdentityPoolId: process.env.REACT_APP_AWS_COGNITO_IDENTITY_POOL_ID,
    googleLoginClientId: process.env.REACT_APP_GOOGLE_LOGIN_CLIENT_ID
}

export default APICredentials<commit_msg>removed unused api credentials<commit_after>//noinspection JSUnresolvedVariable
export default {
    cognitoUserPoolId: process.env.REACT_APP_AWS_COGNITO_USER_POOL_ID,
    cognitoAppClientId: process.env.REACT_APP_AWS_COGNITO_APP_CLIENT_ID
}<|endoftext|>


<commit_before>/**
 * Selectron assists in counting selection caret's offset in relation to
 * different DOM elements, and saving and restoring selection ranges. Saving
 * and restoring of selection/ranges will work across heavy DOM manipulation if used correctly.
 *
 * @module spytext/selectron
 */

/**
 * Position of a caret (start or end)
 *
 * @typedef {Object} Position
 * @property {Node} ref - Reference node to count `offset` from
 * @property {number} offset - Steps from start of `ref`
 */

/**
 * Positions of start and end caret
 *
 * @typedef {Object} Positions
 * @property {Position} start - Position of start caret
 * @property {Position} end - Position of end caret
 */

var sectionTags = [ 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI' ];

/**
 * Tests whether `node` is a section element, ie if it is of nodeType 1
 * and its tagName is in `sectionTags`.
 *
 * @param	{Node} node - The node to check if it is a section element
 * @return {boolean}
 */
function isSection(node) {
	return node.nodeType === 1 && sectionTags.indexOf(node.tagName) !== -1;
	//return node.nodeType === 1 && !getComputedStyle(node).display.match(/inline/);
}

function getLastPosition(node) {
	var ref = node.lastChild;

	while(ref) {
		if(ref.nodeType === 3)
			break;

		ref = ref.lastChild || ref.previousSibling;
	}

	return {
		ref: ref || node,
		offset: ref ? ref.textContent.length : 0
	};
}

function filter(node) {
	return (!$(node).is('UL,OL') && (node.nodeName !== 'BR' || node.nextSibling && !$(node.nextSibling).is('UL,OL'))) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}

filter.acceptNode = filter;

/**
 * Uses a TreeWalker to traverse and count the offset from `root` to `ref`
 *
 * This is essentially the inverse of restore
 *
 * @static
 * @param	{Node} root - Element to count relative
 * @param	{Node} ref - Element to reach
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all steps
 * @return {number}	The total offset of the caret relative to element
 */
function count(root, ref, countAll) {
	var node,
		off = 0,
		tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL, countAll ? null : filter, false);

	if(ref)
		tw.currentNode = ref;

	node = tw.currentNode;

	while(node) {
		if(node !== root && (countAll || isSection(node) || node.nodeName === 'BR'))
			off++;

		if(node !== ref && node.nodeType === 3)
			off = off + node.textContent.length;

		node = ref ? tw.previousNode() : tw.nextNode();
	}

	return off;
}

/**
 * Return the total offset for a caret (start or end) of a range relative to a
 * specific element
 *
 * @static
 * @param	{Element} element - Containing element to count offset relative to
 * @param	{string} [caret=start] - Parameter that determines whether we should fetch start or endContainer
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all elements
 * @return {number}	The total offset of the caret relative to `element`
 * @see count
 */
function offset(element, caret, countAll) {
	var rng = s().getRangeAt(0),
		ref = rng[(caret || 'end') + 'Container'],
		off = rng[(caret || 'end') + 'Offset'];

	element = element || $(ref).closest(sectionTags.join(','))[0];

	if(ref.nodeType === 1 && off > 0) {
		ref = ref.childNodes[off - 1];
		off = ref.textContent.length;
	}

	return count(element, ref, countAll) + off;
}

/**
 * Uses `root` and `offset` to traverse the DOM to find the innermost element
 * where the caret should be placed.
 *
 * This is essentially the inverse of count
 *
 * @static
 * @param	{Element} root
 * @param	{number} offset
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all elements
 * @return {Position}
 */
function restore(root, offset, countAll) {
	var node,
		ref = root;

	// IE fix. IE does not allow treeWalkers to be created on textNodes
	if(root.nodeType === 1) {
		tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL, countAll ? null : filter, false);

		while((node = tw.nextNode())) {
			if(countAll || isSection(node) || node.nodeName === 'BR') {
				if(offset === 0)
					break;

				offset--;
			}

			ref = node;

			if(node.nodeType === 3) {
				if(offset > node.textContent.length)
					offset = offset - node.textContent.length;
				else
					break;
			}
		}
	}

	return {
		ref: ref,
		offset: offset
	};
}

var descendants = require('./descendants');

var s = window.getSelection;

module.exports = {
	restore: restore,

	count: count,

	offset: offset,

	/**
	 * Returns all elements contained by the current selection
	 *
	 * @param	{Element} element - Element to count relative
	 * @param	{Node[]|NodeList|string|number|function} [ufo] - Filters
	 * @param	{number} [levels] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @param	{boolean} [partlyContained] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @param	{boolean} [onlyDeepest] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @return {Node[]}	Array contained all contained nodes
	 */
	contained: function(opts, partlyContained) {
		opts = opts || {};

		var _selectron = this,
			check,
			nodes = [],
			element = opts.element || this._element || document.body;

		if(_.isArray(opts))
			check = opts;
		else if(opts instanceof NodeList || opts instanceof HTMLCollection || opts instanceof jQuery)
			check = _.toArray(opts);
		else {
			if(opts.sections) opts = { selector: sectionTags.join(',') };
			check = descendants(element, opts);
		}

		// loop through all nodes and check if
		// they are contained by the current selection
		check.forEach(function(node) {
			if(_selectron.contains(node, partlyContained))
				nodes.push(node);
		});

		// return any contained nodes
		return nodes;
	},

	/**
	 * Tests whether `node` is contained by the current selection
	 *
	 * @param	{Node} node - Element to count relative
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 */
	contains: function(node, partlyContained) {
		// default, unoverridable behaviour of Selection.containsNode() for textNodes
		// is to always test partlyContained = true
		partlyContained = node.nodeType === 3 ? true : !!partlyContained;

		var sel = s();

		if(sel.containsNode) {
			// simply use Selection objects containsNode native function if it exists
			return sel.containsNode(node, partlyContained);
		} else {
			var rng = sel.getRangeAt(0),
				element = rng.commonAncestorContainer;

			if(element.nodeType !== 1) {
				element = element.parentNode;
			}

			if(element !== node && !$.contains(element, node)) {
				return partlyContained && $.contains(node,element);
			}

			var rangeStartOffset = offset(element, 'start', true),
				rangeEndOffset = offset(element, 'end', true),
				startOffset = count(element, node, true),
				endOffset = node.nodeType === 1 ? startOffset + count(node, null, true) + 1 : startOffset + node.textContent.length;

			return (startOffset >= rangeStartOffset && endOffset <= rangeEndOffset ||
					(partlyContained && ((rangeStartOffset >= startOffset && rangeStartOffset <= endOffset) || (rangeEndOffset >= startOffset && rangeEndOffset <= endOffset))));
		}
	},

	/**
	 * Tests whether all `nodes` are contained by the current selection
	 *
	 * @param	{Node[]} nodes - nodes to test if they are contained
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 * @see contains
	 */
	containsEvery: function(nodes, partlyContained) {
		var that = this;

		return _.toArray(nodes).every(function(node) {
			return that.contains(node, partlyContained);
		});
	},

	/**
	 * Tests whether any of `nodes` are contained by the current selection
	 *
	 * @param	{Node[]} nodes - nodes to test if they are contained
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 * @see contains
	 */
	containsSome: function(nodes, partlyContained) {
		var that = this;

		return _.toArray(nodes).some(function(node) {
			return that.contains(node, partlyContained);
		});

	},

	normalize: function() {
		var rng = this.range(),
			$container = $(rng.endContainer);

		if(!rng.collapsed) {
			if($container.is(sectionTags.join(','))) {
				var ref = $container[0];

				while(!ref.previousSibling)
					ref = ref.parentNode;

				section = $(rng.startContainer).closest(sectionTags.join(','))[0];

				this.set({
					start: {
						ref: section,
						offset: offset(section, 'start')
					},
					end: getLastPosition(ref.previousSibling)
				});
			}
		} else {
			section = $container.closest(sectionTags.join(','))[0];
			this.set({
				ref: section,
				offset: offset(section, 'end')
			});
		}
	},

	/**
	 * Return the current selection's (first) range
	 *
	 * @return {Range}
	 */
	range: function() {
		// retrieve the current selection
		var sel = s();

		if(sel.rangeCount > 0)
			// selection has at least one range, return the first
			return sel.getRangeAt(0);
		else
			// selection has no range, return null
			return null;
	},

	/**
	 * Tests whether the caret is currently at the end of a section
	 *
	 * @return {boolean}
	 */
	isAtEndOfSection: function(section) {
		var endContainer = this.range().endContainer,
			$section;

		if(section) {
			if(section !== endContainer && !$.contains(section,endContainer))
				return false;

			$section = $(section);
		} else {
			$section = $(endContainer).closest(sectionTags.join(','));
		}

		var off = offset($section[0], 'end'),
			$nestedList = $section.children('UL,OL'),
			result = $nestedList.length > 0 ? count($section[0], $nestedList[0]) : count($section[0]);

		return off === result;
	},

	/**
	 * Tests whether the caret is currently at the end of a section
	 *
	 * @return {boolean}
	 */
	isAtStartOfSection: function(section) {
		var $section = $(section || this.range().startContainer).closest(sectionTags.join(','));

		return offset($section[0], 'start') === 0;
	},

	/**
	 * Get Positions of start and end caret of current selection
	 *
	 * @param {Element} [element=document.body] - The reference node (to count the offset from)
	 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all steps
	 * @return {Positions} ref element of both start and end Position will be `element`
	 */
	get: function(element, countAll) {
		element = element || this._element || document.body;

		return {
			start: {
				ref: element,
				offset: offset(element, 'start', countAll)
			},
			end: {
				ref: element,
				offset: offset(element, 'end', countAll)
			}
		};
	},

	getLastPosition: getLastPosition,

	/**
	 * Sets the current selection to contain `node`
	 *
	 * @param {Node} node - The node to select
	 */
	select: function(node) {
		var textNodes = node.nodeType === 3 ? [ node ] : descendants(node, { nodeType: 3 });

		if(textNodes.length === 0) {
			this.set({ ref: node, offset: 0 });
		} else {
			var first = _.first(textNodes),
				last = _.last(textNodes);

			this.set({
				start: {
					ref: first,
					offset: 0
				},
				end: {
					ref: last,
					offset: last.textContent.length,
				}
			});
		}
	},

	/**
	 * Sets the selection to `position`
	 *
	 * @param {Position|Positions} position - If a Position, a collapsed range will be set with start and end caret set to `position`
	 */
	set: function(position, force) {
		if(position.ref) {
			position = {
				start: position
			};
		}
		position.start.offset = position.start.offset || 0;
		if(position.end)
			position.end.offset = position.end.offset || 0;

		var start = force ? position.start : restore(position.start.ref, position.start.offset),
			end = position.end ? force ? position.end : restore(position.end.ref, position.end.offset) : start,
			rng = document.createRange(),
			sel = s();

		if(start.ref.nodeName === 'BR') {
			rng.setStartAfter(start.ref);
		} else {
			if(start.ref.nodeType === 1 && start.offset > start.ref.childNodes.length || start.ref.nodeType !== 1 && start.offset > start.ref.textContent.length)
				return;
			rng.setStart(start.ref, start.offset);
		}

		if(end.ref.nodeName === 'BR') {
			rng.setEndAfter(end.ref);
		} else {
			if(end.ref.nodeType === 1 && end.offset > end.ref.childNodes.length || end.ref.nodeType !== 1 && end.offset > end.ref.textContent.length)
				return;
			rng.setEnd(end.ref, end.offset);
		}

		sel.removeAllRanges();
		sel.addRange(rng);
	},

	setElement: function(element) {
		this._element = element;
	}
};
<commit_msg>Implemented setContained and update in selectron.<commit_after>/**
 * Selectron assists in counting selection caret's offset in relation to
 * different DOM elements, and saving and restoring selection ranges. Saving
 * and restoring of selection/ranges will work across heavy DOM manipulation if used correctly.
 *
 * @module spytext/selectron
 */

/**
 * Position of a caret (start or end)
 *
 * @typedef {Object} Position
 * @property {Node} ref - Reference node to count `offset` from
 * @property {number} offset - Steps from start of `ref`
 */

/**
 * Positions of start and end caret
 *
 * @typedef {Object} Positions
 * @property {Position} start - Position of start caret
 * @property {Position} end - Position of end caret
 */

var sectionTags = [ 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI' ];

/**
 * Tests whether `node` is a section element, ie if it is of nodeType 1
 * and its tagName is in `sectionTags`.
 *
 * @param	{Node} node - The node to check if it is a section element
 * @return {boolean}
 */
function isSection(node) {
	return node.nodeType === 1 && sectionTags.indexOf(node.tagName) !== -1;
	//return node.nodeType === 1 && !getComputedStyle(node).display.match(/inline/);
}

function getLastPosition(node) {
	var ref = node.lastChild;

	while(ref) {
		if(ref.nodeType === 3)
			break;

		ref = ref.lastChild || ref.previousSibling;
	}

	return {
		ref: ref || node,
		offset: ref ? ref.textContent.length : 0
	};
}

function filter(node) {
	return (!$(node).is('UL,OL') && (node.nodeName !== 'BR' || node.nextSibling && !$(node.nextSibling).is('UL,OL'))) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}

filter.acceptNode = filter;

/**
 * Uses a TreeWalker to traverse and count the offset from `root` to `ref`
 *
 * This is essentially the inverse of restore
 *
 * @static
 * @param	{Node} root - Element to count relative
 * @param	{Node} ref - Element to reach
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all steps
 * @return {number}	The total offset of the caret relative to element
 */
function count(root, ref, countAll) {
	var node,
		off = 0,
		tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL, countAll ? null : filter, false);

	if(ref)
		tw.currentNode = ref;

	node = tw.currentNode;

	while(node) {
		if(node !== root && (countAll || isSection(node) || node.nodeName === 'BR'))
			off++;

		if(node !== ref && node.nodeType === 3)
			off = off + node.textContent.length;

		node = ref ? tw.previousNode() : tw.nextNode();
	}

	return off;
}

/**
 * Return the total offset for a caret (start or end) of a range relative to a
 * specific element
 *
 * @static
 * @param	{Element} element - Containing element to count offset relative to
 * @param	{string} [caret=start] - Parameter that determines whether we should fetch start or endContainer
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all elements
 * @return {number}	The total offset of the caret relative to `element`
 * @see count
 */
function offset(element, caret, countAll) {
	var rng = s().getRangeAt(0),
		ref = rng[(caret || 'end') + 'Container'],
		off = rng[(caret || 'end') + 'Offset'];

	element = element || $(ref).closest(sectionTags.join(','))[0];

	if(ref.nodeType === 1 && off > 0) {
		ref = ref.childNodes[off - 1];
		off = ref.textContent.length;
	}

	return count(element, ref, countAll) + off;
}

/**
 * Uses `root` and `offset` to traverse the DOM to find the innermost element
 * where the caret should be placed.
 *
 * This is essentially the inverse of count
 *
 * @static
 * @param	{Element} root
 * @param	{number} offset
 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all elements
 * @return {Position}
 */
function restore(root, offset, countAll) {
	var node,
		ref = root;

	// IE fix. IE does not allow treeWalkers to be created on textNodes
	if(root.nodeType === 1) {
		tw = document.createTreeWalker(root, NodeFilter.SHOW_ALL, countAll ? null : filter, false);

		while((node = tw.nextNode())) {
			if(countAll || isSection(node) || node.nodeName === 'BR') {
				if(offset === 0)
					break;

				offset--;
			}

			ref = node;

			if(node.nodeType === 3) {
				if(offset > node.textContent.length)
					offset = offset - node.textContent.length;
				else
					break;
			}
		}
	}

	return {
		ref: ref,
		offset: offset
	};
}

var descendants = require('./descendants');

var s = window.getSelection;

module.exports = {
	restore: restore,

	count: count,

	offset: offset,

	/**
	 * Returns all elements contained by the current selection
	 *
	 * @param	{Element} element - Element to count relative
	 * @param	{Node[]|NodeList|string|number|function} [ufo] - Filters
	 * @param	{number} [levels] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @param	{boolean} [partlyContained] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @param	{boolean} [onlyDeepest] - How many levels of descendants should be collected. If `levels` is not set, all levels will be traversed
	 * @return {Node[]}	Array contained all contained nodes
	 */
	contained: function(opts, partlyContained) {
		opts = opts || {};

		var _selectron = this,
			check,
			nodes = [],
			element = opts.element || this._element || document.body;

		if(_.isArray(opts))
			check = opts;
		else if(opts instanceof NodeList || opts instanceof HTMLCollection || opts instanceof jQuery)
			check = _.toArray(opts);
		else {
			if(opts.sections) opts = { selector: sectionTags.join(',') };
			check = descendants(element, opts);
		}

		// loop through all nodes and check if
		// they are contained by the current selection
		check.forEach(function(node) {
			if(_selectron.contains(node, partlyContained))
				nodes.push(node);
		});

		// return any contained nodes
		return nodes;
	},

	/**
	 * Tests whether `node` is contained by the current selection
	 *
	 * @param	{Node} node - Element to count relative
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 */
	contains: function(node, partlyContained) {
		// default, unoverridable behaviour of Selection.containsNode() for textNodes
		// is to always test partlyContained = true
		partlyContained = node.nodeType === 3 ? true : !!partlyContained;

		var sel = s();

		if(sel.containsNode) {
			// simply use Selection objects containsNode native function if it exists
			return sel.containsNode(node, partlyContained);
		} else {
			var rng = sel.getRangeAt(0),
				element = rng.commonAncestorContainer;

			if(element.nodeType !== 1) {
				element = element.parentNode;
			}

			if(element !== node && !$.contains(element, node)) {
				return partlyContained && $.contains(node,element);
			}

			var rangeStartOffset = offset(element, 'start', true),
				rangeEndOffset = offset(element, 'end', true),
				startOffset = count(element, node, true),
				endOffset = node.nodeType === 1 ? startOffset + count(node, null, true) + 1 : startOffset + node.textContent.length;

			return (startOffset >= rangeStartOffset && endOffset <= rangeEndOffset ||
					(partlyContained && ((rangeStartOffset >= startOffset && rangeStartOffset <= endOffset) || (rangeEndOffset >= startOffset && rangeEndOffset <= endOffset))));
		}
	},

	/**
	 * Tests whether all `nodes` are contained by the current selection
	 *
	 * @param	{Node[]} nodes - nodes to test if they are contained
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 * @see contains
	 */
	containsEvery: function(nodes, partlyContained) {
		var that = this;

		return _.toArray(nodes).every(function(node) {
			return that.contains(node, partlyContained);
		});
	},

	/**
	 * Tests whether any of `nodes` are contained by the current selection
	 *
	 * @param	{Node[]} nodes - nodes to test if they are contained
	 * @param	{boolean} [partlyContained] - Return nodes that are not completely contained by selection
	 * @return {boolean}
	 * @see contains
	 */
	containsSome: function(nodes, partlyContained) {
		var that = this;

		return _.toArray(nodes).some(function(node) {
			return that.contains(node, partlyContained);
		});

	},

	normalize: function() {
		var rng = this.range(),
			$container = $(rng.endContainer);

		if(!rng.collapsed) {
			if($container.is(sectionTags.join(','))) {
				var ref = $container[0];

				while(!ref.previousSibling)
					ref = ref.parentNode;

				section = $(rng.startContainer).closest(sectionTags.join(','))[0];

				this.set({
					start: {
						ref: section,
						offset: offset(section, 'start')
					},
					end: getLastPosition(ref.previousSibling)
				});
			}
		} else {
			section = $container.closest(sectionTags.join(','))[0];
			this.set({
				ref: section,
				offset: offset(section, 'end')
			});
		}
	},

	/**
	 * Return the current selection's (first) range
	 *
	 * @return {Range}
	 */
	range: function() {
		// retrieve the current selection
		var sel = s();

		if(sel.rangeCount > 0)
			// selection has at least one range, return the first
			return sel.getRangeAt(0);
		else
			// selection has no range, return null
			return null;
	},

	/**
	 * Tests whether the caret is currently at the end of a section
	 *
	 * @return {boolean}
	 */
	isAtEndOfSection: function(section) {
		var endContainer = this.range().endContainer,
			$section;

		if(section) {
			if(section !== endContainer && !$.contains(section,endContainer))
				return false;

			$section = $(section);
		} else {
			$section = $(endContainer).closest(sectionTags.join(','));
		}

		var off = offset($section[0], 'end'),
			$nestedList = $section.children('UL,OL'),
			result = $nestedList.length > 0 ? count($section[0], $nestedList[0]) : count($section[0]);

		return off === result;
	},

	/**
	 * Tests whether the caret is currently at the end of a section
	 *
	 * @return {boolean}
	 */
	isAtStartOfSection: function(section) {
		var $section = $(section || this.range().startContainer).closest(sectionTags.join(','));

		return offset($section[0], 'start') === 0;
	},

	/**
	 * Get Positions of start and end caret of current selection
	 *
	 * @param {Element} [element=document.body] - The reference node (to count the offset from)
	 * @param	{boolean} [countAll] - Boolean parameter to determine whether to count all steps
	 * @return {Positions} ref element of both start and end Position will be `element`
	 */
	get: function(element, countAll) {
		element = element || this._element || document.body;

		if(element === this._element && this._positions)
			return this._positions;

		return {
			start: {
				ref: element,
				offset: offset(element, 'start', countAll)
			},
			end: {
				ref: element,
				offset: offset(element, 'end', countAll)
			}
		};
	},

	getLastPosition: getLastPosition,

	/**
	 * Sets the current selection to contain `node`
	 *
	 * @param {Node} node - The node to select
	 */
	select: function(node) {
		var textNodes = node.nodeType === 3 ? [ node ] : descendants(node, { nodeType: 3 });

		if(textNodes.length === 0) {
			this.set({ ref: node, offset: 0 });
		} else {
			var first = _.first(textNodes),
				last = _.last(textNodes);

			this.set({
				start: {
					ref: first,
					offset: 0
				},
				end: {
					ref: last,
					offset: last.textContent.length,
				}
			});
		}
	},

	/**
	 * Sets the selection to `position`
	 *
	 * @param {Position|Positions} position - If a Position, a collapsed range will be set with start and end caret set to `position`
	 */
	set: function(position, force) {
		if(position.ref) {
			position = {
				start: position
			};
		}
		position.start.offset = position.start.offset || 0;
		if(position.end)
			position.end.offset = position.end.offset || 0;

		var start = force ? position.start : restore(position.start.ref, position.start.offset),
			end = position.end ? force ? position.end : restore(position.end.ref, position.end.offset) : start,
			rng = document.createRange(),
			sel = s();

		if(start.ref.nodeName === 'BR') {
			rng.setStartAfter(start.ref);
		} else {
			if(start.ref.nodeType === 1 && start.offset > start.ref.childNodes.length || start.ref.nodeType !== 1 && start.offset > start.ref.textContent.length)
				return;
			rng.setStart(start.ref, start.offset);
		}

		if(end.ref.nodeName === 'BR') {
			rng.setEndAfter(end.ref);
		} else {
			if(end.ref.nodeType === 1 && end.offset > end.ref.childNodes.length || end.ref.nodeType !== 1 && end.offset > end.ref.textContent.length)
				return;
			rng.setEnd(end.ref, end.offset);
		}

		sel.removeAllRanges();
		sel.addRange(rng);
	},

	setContained: function() {
		var _selectron = this;

		this.contained.sections = this.contained({ sections: true }, true);

		this.contained.listItems = _.unique(this.contained.sections.filter(function(node) {
			return node.nodeName === 'LI';
		}));

		this.contained.lists = _.unique(this.contained.listItems.map(function(node) {
			return $(node).closest(_selectron._element.children)[0];
		}));

		this.contained.blocks = this.contained.sections.filter(function(node) {
			return node.nodeName !== 'LI';
		});

		var commonAncestor = this.range().commonAncestorContainer;

		if(commonAncestor.nodeType === 3) commonAncestor = commonAncestor.parentNode;

		this.contained.textNodes = this.contained({ element: commonAncestor, nodeType: 3 }, true);
	},

	setElement: function(element) {
		this._element = element;
	},

	update: function() {
		this.normalize();
		this._positions = {
			start: {
				ref: this._element,
				offset: offset(this._element, 'start')
			},
			end: {
				ref: this._element,
				offset: offset(this._element, 'end')
			}
		};

		this.setContained();
	}
};
<|endoftext|>