<commit_before>/**
 * Global
 */
var ktane = ktane || {};

$('h2').on('dblclick', function () {
    $(this).closest('section').toggleClass('collapsed');
}).attr('title', 'Double-click to expand/collapse');

ktane.resetInputs = function ($container) {
    $container.find('textarea').val('');

    return $container.find('input').each(function () {
        if (this.type === 'text') {
            $(this).val('').trigger('keyup');
        } else if (this.type === 'checkbox') {
            this.checked = false;
        } else if (this.type === 'radio' && !this.value) {
            this.checked = true;
        }
    });
}

ktane.resetInputs($('body'));
$('.jsResetSection').on('click', function () {
    ktane.resetInputs($(this).closest('section')).filter('[type="text"]').first().focus();
});

(function () {
    /**
     * Wires
     */
    var count = {
        red: 0,
        yellow: 0,
        blue: 0,
        white: 0,
        black: 0
    },
        $wireList = $('#wireList');

    $('#wireOptions button').on('click', function () {
        var colour = this.getAttribute('data-colour'),
            letter = this.getAttribute('data-letter'),
            $li = $('
<button class="button' + colour.charAt(0).toUpperCase() + colour.substring(1)
                        + '" data-colour="' + colour + '" data-letter="' + letter + '"></button>
');

        $wireList.append($li);
        count[colour]++;
        checkWireToCut();
    });

    $wireList.on('click', 'button', function () {
        var deleteIndex = $(this).parent().index();

        $wireList.children('li:eq(' + deleteIndex + '), li:gt(' + deleteIndex + ')').each(function () {
            var button = this.firstChild;
            count[button.getAttribute('data-colour')]--;
            $(this).remove();
        });

        checkWireToCut();
    });

    function checkWireToCut() {
        var lastColour = $wireList.children(':last-child').children().first().data('colour'),
            oddPossible;
        $wireList.find('.wireToCut').removeClass('wireToCut odd even');

        switch ($wireList.children().length) {
            case 3:
                oddPossible = false;

                if (!count['red']) {
                    addWireToCut(2);
                } else if (lastColour === 'white') {
                    addWireToCut();
                } else if (count['blue'] > 1) {
                    $wireList.find('[data-colour="blue"]').last().parent().addClass('wireToCut');
                } else {
                    addWireToCut();
                }
                break;
            case 4:
                oddPossible = count['red'] > 1;
                if (oddPossible) {
                    $wireList.find('[data-colour="red"]').parent().last().addClass('wireToCut odd');
                }

                if (lastColour === 'yellow' && !count['red'] || count['blue'] === 1) {
                    addWireToCut(1);
                } else if (count['yellow'] > 1) {
                    addWireToCut();
                } else {
                    addWireToCut(2);
                }
                break;
            case 5:
                oddPossible = lastColour === 'black';
                if (oddPossible) {
                    addWireToCut(4, 'odd');
                }

                if (count['red'] === 1 && count['yellow'] > 1) {
                    addWireToCut(1);
                } else if (!count['black']) {
                    addWireToCut(2);
                } else {
                    addWireToCut(1);
                }
                break;
            case 6:
                oddPossible = !count['yellow'];
                if (oddPossible) {
                    addWireToCut(3, 'odd');
                }

                if (count['yellow'] === 1 && count['white'] > 1) {
                    addWireToCut(4);
                } else if (!count['red']) {
                    addWireToCut();
                } else {
                    addWireToCut(4);
                }
                break;
            default:
                break;
        }

        function addWireToCut(child, className) {
            if (typeof className === 'undefined') {
                className = 'even';
            }
            $wireList.children(isNaN(child) ? ':last-child' : ':nth-child(' + child + ')')
                    .addClass('wireToCut' + (className !== 'even' || oddPossible ? ' ' + className : ''));
        }
    }
})();

(function () {
    /**
     * Button
     */
    var $section = $('#sectionButton'),
            $instruction = $('#buttonInstruction'),
            instruction;

    $section.find('input').on('click', function () {
        var colour = $('input[name="buttonColour"]:checked').val(),
                text = $('input[name="buttonText"]:checked').val();

        if (text === 'Detonate') {
            instruction = 'If there is more than 1 battery, press and release; otherwise, hold the button.'
        } else if (colour === 'white') {
            instruction = 'If there is a lit indicator with label CAR, hold the button. Otherwise, if there are more than 2 batteries and a lit indicator with label FRK, press and release the button. If neither apply, hold the button.'
        } else if (colour === 'blue' && text === 'Abort'
                || colour === 'yellow') {
            instruction = 'Hold the button.';
        } else if (colour === 'red' && text === 'Hold') {
            instruction = 'Press and release the button.';
        } else {
            instruction = 'If there are more than 2 batteries and a lit indicator with label FRK, press and release the button; otherwise, hold the button.';
        }

        $instruction.html(instruction);
    });
})();

(function () {
    /**
     * Simon
     */
    var $checkbox = $('#simonVowel'),
        $strikeButtons = $('#sectionSimon input[name="simonStrikes"]');

    $('#sectionSimon input').on('change', function () {
        var hasVowel = $checkbox.prop('checked'),
            strikes = $strikeButtons.filter(':checked').val();

        // Do something with arrows and stuff!
        console.log({vowel: hasVowel, strikes: strikes});
    });
})();

(function () {
    /**
     * Memory
     */
    var inputRegex = /^[1-4]$/,
        $s = {}, // Cache jQuery objects for speed and convenience
        answerData;

    for (var i = 1; i <= 5; i++) {
        $s[i] = {
            d: $('#stage_' + i + '_display'),
            p: $('#stage_' + i + '_pos'),
            l: $('#stage_' + i + '_label')
        };

        if (i < 5) {
            $s[i].p.add($s[i].l).on('keyup', {j: i + 1}, function (e) {
                if (inputRegex.test(this.value)) {
                    $s[e.data.j].d.focus();
                }
            });
        }
    }

    $('#sectionMemory input').on('focus', function () {
        this.select();
    });

    answerData = { // {stage -> {display -> [element, value]} }
        1: {
            1: ['p', 2],
            2: ['p', 2],
            3: ['p', 3],
            4: ['p', 4]
        },
        2: {
            1: ['l', 4],
            2: ['p', $s[1].p],
            3: ['p', 1],
            4: ['p', $s[1].p]
        },
        3: {
            1: ['l', $s[2].l],
            2: ['l', $s[1].l],
            3: ['p', 3],
            4: ['l', 4]
        },
        4: {
            1: ['p', $s[1].p],
            2: ['p', 1],
            3: ['p', $s[2].p],
            4: ['p', $s[2].p]
        },
        5: {
            1: ['l', $s[1].l],
            2: ['l', $s[2].l],
            3: ['l', $s[4].l],
            4: ['l', $s[3].l]
        }
    };

    $('#sectionMemory input[id$="_display"]').on('keyup', function () {
        if (!inputRegex.test(this.value)) {
            return;
        }

        var index = parseInt(this.getAttribute('data-index'), 10),
            data = answerData[index][this.value],
            element = $s[index][data[0]],
            value = data[1];

        if (typeof value === 'object') {
            value = value.val() || value.attr('id').replace(/(\d).+$/, '$1');
        }

        element.val(value);
        $s[index][data[0] === 'p' ? 'l' : 'p'].focus();
    });
})();

(function () {
    /**
     * Morse
     */
    var freqs = {
        shell: '3.505',
        halls: '3.515',
        slick: '3.522',
        trick: '3.532',
        boxes: '3.535',
        leaks: '3.542',
        strobe: '3.545',
        bistro: '3.552',
        flick: '3.555',
        bombs: '3.565',
        'break': '3.572',
        brick: '3.575',
        steak: '3.582',
        sting: '3.592',
        vector: '3.595',
        beats: '3.600'
    },
        codes = {
            '.-':   'a',
            '-...': 'b',
            '-.-.': 'c',
            '.':    'e',
            '..-.': 'f',
            '--.':  'g',
            '....': 'h',
            '..':   'i',
            '-.-':  'k',
            '.-..': 'l',
            '--':   'm',
            '-.':   'n',
            '---':  'o',
            '.-.':  'r',
            '...':  's',
            '-':    't',
            '...-': 'v',
            '-..-': 'x'
        },
        codeRegex = new RegExp(' *(' + Object.keys(codes).join('|').replace(/\./g, '\\.') + ')(?![\.-]) *', 'g'),
        $rows = {},
        $table = $('#morseTable');

    for (var word in freqs) {
        var $row = $('
' + word + '	' + freqs[word] + '
');
        $table.append($row);
        $rows[word + word] = $row; // Concat to handle the wrap around
    }

    $('#morseInput').on('keyup', function () {
        var lines = this.value.split('\n'),
            regexes = [];

        lines.forEach(function (line) {
            if (!line) return;

            // JS has no negative lookbehind; gotta do the replacement twice
            regexes.push(new RegExp(
                line.replace(codeRegex, codeRegexCallback)
                    .replace(codeRegex, codeRegexCallback)
            ));
        });
        console.log(regexes);

        for (var word in $rows) {
            var matchAll = true;
            $.each(regexes, function (i, regex) {
                if (!regex.test(word)) {
                    matchAll = false;
                    return false;
                }
            });

            $rows[word].toggleClass('inactive', !matchAll);
        }
    });

    function codeRegexCallback(match, code) {
        return codes[code];
    }
})();

(function () {
    /**
     * Wire sequence
     */
    var $instructions = $('#sequenceInstruction'),
        count = {
            red: 0,
            blue: 0,
            black: 0
        },
        occurrences = {
            red: ['C', 'B', 'A', 'AC', 'B', 'AC', 'ABC', 'AB', 'B'],
            blue: ['B', 'AC', 'B', 'A', 'B', 'BC', 'C', 'AC', 'A'],
            black: ['ABC', 'AC', 'B', 'AC', 'B', 'BC', 'AB', 'C', 'C']
        };

    $('#sequenceOptions button').on('click', function () {
        var colour = this.getAttribute('data-colour'),
                letter = this.getAttribute('data-letter'),
                $li = $('
<button class="button ' + colour + '" data-colour="' + colour + '" data-letter="' + letter
                        + '">' + letter + '</button>
');

        if (typeof occurrences[colour][count[colour]] !== 'undefined') {
            $li.append(occurrences[colour][count[colour]].indexOf(letter) === -1 ? 'Ignore' : 'Cut');
            count[colour]++;
            $instructions.append($li);
        }
    });

    $instructions.on('click', 'button', function () {
        var deleteIndex = $(this).parent().index();

        $instructions.children('li:eq(' + deleteIndex + '), li:gt(' + deleteIndex + ')').each(function () {
            var button = this.firstChild;
            count[button.getAttribute('data-colour')]--;
            $(this).remove();
        });
    });
})();

(function () {
    /**
     * Venn diagram
     */
    var instructions = {
                C: 'Cut the wire',
                D: 'Do not cut the wire',
                S: 'If the last digit of the serial number is even, cut the wire',
                P: 'If the bomb has a parallel port, cut the wire',
                B: 'If the bomb has two or more batteries, cut the wire'
            },
            conditions = { // Initialize
                false: {
                    false: {
                        false: {}, true: {}
                    },
                    true: {
                        false: {}, true: {}
                    }
                },
                true: {
                    false: {
                        false: {}, true: {}
                    },
                    true: {
                        false: {}, true: {}
                    }
                }
            },
            $checkboxes = $('#vennOptions > li > input'),
            $instruction = $('#vennInstruction');

    //         Red    Blue   Star   LED
    conditions[false][false][false][false] = 'C';
    conditions[false][false][false][true ] = 'D';
    conditions[false][false][true ][false] = 'C';
    conditions[false][false][true ][true ] = 'B';
    conditions[false][true ][false][false] = 'S';
    conditions[false][true ][false][true ] = 'P';
    conditions[false][true ][true ][false] = 'D';
    conditions[false][true ][true ][true ] = 'P';
    conditions[true ][false][false][false] = 'S';
    conditions[true ][false][false][true ] = 'B';
    conditions[true ][false][true ][false] = 'C';
    conditions[true ][false][true ][true ] = 'B';
    conditions[true ][true ][false][false] = 'S';
    conditions[true ][true ][false][true ] = 'S';
    conditions[true ][true ][true ][false] = 'P';
    conditions[true ][true ][true ][true ] = 'D';

    $checkboxes.on('change', function () {
        var checked = [];
        $checkboxes.each(function () {
            checked.push(this.checked);
        });

        $instruction.html(instructions[conditions[checked[0]][checked[1]][checked[2]][checked[3]]]);
    });
})();

(function () {
    /**
     * Passwords
     * */
    var passwords = [
                'about', 'after', 'again', 'below', 'could',
                'every', 'first', 'found', 'great', 'house',
                'large', 'learn', 'never', 'other', 'place',
                'plant', 'point', 'right', 'small', 'sound',
                'spell', 'still', 'study', 'their', 'there',
                'these', 'thing', 'think', 'three', 'water',
                'where', 'which', 'world', 'would', 'write'
            ],
            $passwordContainer = $('#passwords'),
            $passwords = [],
            $row,
            $inputs = $('#passwordOptions > li > input');

    passwords.forEach(function (password, i) {
        var $cell;
        if (i % 5 === 0) {
            $row = $('

').appendTo($passwordContainer);
        }

        $cell = $('' + password + '	').appendTo($row);
        $passwords.push($cell);
    });

    $inputs.on('keyup', function () {
        var pattern = '^',
                regex;
        this.value = this.value.replace(/[^a-z]/gi, '').toLowerCase();
        $inputs.each(function (i, input) {
            pattern += input.value ? '[' + input.value + ']' : '.';
        });
        pattern += '$';
        regex = new RegExp(pattern);

        $passwords.forEach(function ($password) {
            $password.toggleClass("inactive", !$password.text().match(regex));
        });
    });
})();
<commit_msg>Missed one place in the button colour refactor<commit_after>/**
 * Global
 */
var ktane = ktane || {};

$('h2').on('dblclick', function () {
    $(this).closest('section').toggleClass('collapsed');
}).attr('title', 'Double-click to expand/collapse');

ktane.resetInputs = function ($container) {
    $container.find('textarea').val('');

    return $container.find('input').each(function () {
        if (this.type === 'text') {
            $(this).val('').trigger('keyup');
        } else if (this.type === 'checkbox') {
            this.checked = false;
        } else if (this.type === 'radio' && !this.value) {
            this.checked = true;
        }
    });
}

ktane.resetInputs($('body'));
$('.jsResetSection').on('click', function () {
    ktane.resetInputs($(this).closest('section')).filter('[type="text"]').first().focus();
});

(function () {
    /**
     * Wires
     */
    var count = {
        red: 0,
        yellow: 0,
        blue: 0,
        white: 0,
        black: 0
    },
        $wireList = $('#wireList');

    $('#wireOptions button').on('click', function () {
        var colour = this.getAttribute('data-colour'),
            letter = this.getAttribute('data-letter'),
            $li = $('
<button class="button ' + colour + '" data-colour="' + colour
                    + '" data-letter="' + letter + '"></button>
');

        $wireList.append($li);
        count[colour]++;
        checkWireToCut();
    });

    $wireList.on('click', 'button', function () {
        var deleteIndex = $(this).parent().index();

        $wireList.children('li:eq(' + deleteIndex + '), li:gt(' + deleteIndex + ')').each(function () {
            var button = this.firstChild;
            count[button.getAttribute('data-colour')]--;
            $(this).remove();
        });

        checkWireToCut();
    });

    function checkWireToCut() {
        var lastColour = $wireList.children(':last-child').children().first().data('colour'),
            oddPossible;
        $wireList.find('.wireToCut').removeClass('wireToCut odd even');

        switch ($wireList.children().length) {
            case 3:
                oddPossible = false;

                if (!count['red']) {
                    addWireToCut(2);
                } else if (lastColour === 'white') {
                    addWireToCut();
                } else if (count['blue'] > 1) {
                    $wireList.find('[data-colour="blue"]').last().parent().addClass('wireToCut');
                } else {
                    addWireToCut();
                }
                break;
            case 4:
                oddPossible = count['red'] > 1;
                if (oddPossible) {
                    $wireList.find('[data-colour="red"]').parent().last().addClass('wireToCut odd');
                }

                if (lastColour === 'yellow' && !count['red'] || count['blue'] === 1) {
                    addWireToCut(1);
                } else if (count['yellow'] > 1) {
                    addWireToCut();
                } else {
                    addWireToCut(2);
                }
                break;
            case 5:
                oddPossible = lastColour === 'black';
                if (oddPossible) {
                    addWireToCut(4, 'odd');
                }

                if (count['red'] === 1 && count['yellow'] > 1) {
                    addWireToCut(1);
                } else if (!count['black']) {
                    addWireToCut(2);
                } else {
                    addWireToCut(1);
                }
                break;
            case 6:
                oddPossible = !count['yellow'];
                if (oddPossible) {
                    addWireToCut(3, 'odd');
                }

                if (count['yellow'] === 1 && count['white'] > 1) {
                    addWireToCut(4);
                } else if (!count['red']) {
                    addWireToCut();
                } else {
                    addWireToCut(4);
                }
                break;
            default:
                break;
        }

        function addWireToCut(child, className) {
            if (typeof className === 'undefined') {
                className = 'even';
            }
            $wireList.children(isNaN(child) ? ':last-child' : ':nth-child(' + child + ')')
                    .addClass('wireToCut' + (className !== 'even' || oddPossible ? ' ' + className : ''));
        }
    }
})();

(function () {
    /**
     * Button
     */
    var $section = $('#sectionButton'),
            $instruction = $('#buttonInstruction'),
            instruction;

    $section.find('input').on('click', function () {
        var colour = $('input[name="buttonColour"]:checked').val(),
                text = $('input[name="buttonText"]:checked').val();

        if (text === 'Detonate') {
            instruction = 'If there is more than 1 battery, press and release; otherwise, hold the button.'
        } else if (colour === 'white') {
            instruction = 'If there is a lit indicator with label CAR, hold the button. Otherwise, if there are more than 2 batteries and a lit indicator with label FRK, press and release the button. If neither apply, hold the button.'
        } else if (colour === 'blue' && text === 'Abort'
                || colour === 'yellow') {
            instruction = 'Hold the button.';
        } else if (colour === 'red' && text === 'Hold') {
            instruction = 'Press and release the button.';
        } else {
            instruction = 'If there are more than 2 batteries and a lit indicator with label FRK, press and release the button; otherwise, hold the button.';
        }

        $instruction.html(instruction);
    });
})();

(function () {
    /**
     * Simon
     */
    var $checkbox = $('#simonVowel'),
        $strikeButtons = $('#sectionSimon input[name="simonStrikes"]');

    $('#sectionSimon input').on('change', function () {
        var hasVowel = $checkbox.prop('checked'),
            strikes = $strikeButtons.filter(':checked').val();

        // Do something with arrows and stuff!
        console.log({vowel: hasVowel, strikes: strikes});
    });
})();

(function () {
    /**
     * Memory
     */
    var inputRegex = /^[1-4]$/,
        $s = {}, // Cache jQuery objects for speed and convenience
        answerData;

    for (var i = 1; i <= 5; i++) {
        $s[i] = {
            d: $('#stage_' + i + '_display'),
            p: $('#stage_' + i + '_pos'),
            l: $('#stage_' + i + '_label')
        };

        if (i < 5) {
            $s[i].p.add($s[i].l).on('keyup', {j: i + 1}, function (e) {
                if (inputRegex.test(this.value)) {
                    $s[e.data.j].d.focus();
                }
            });
        }
    }

    $('#sectionMemory input').on('focus', function () {
        this.select();
    });

    answerData = { // {stage -> {display -> [element, value]} }
        1: {
            1: ['p', 2],
            2: ['p', 2],
            3: ['p', 3],
            4: ['p', 4]
        },
        2: {
            1: ['l', 4],
            2: ['p', $s[1].p],
            3: ['p', 1],
            4: ['p', $s[1].p]
        },
        3: {
            1: ['l', $s[2].l],
            2: ['l', $s[1].l],
            3: ['p', 3],
            4: ['l', 4]
        },
        4: {
            1: ['p', $s[1].p],
            2: ['p', 1],
            3: ['p', $s[2].p],
            4: ['p', $s[2].p]
        },
        5: {
            1: ['l', $s[1].l],
            2: ['l', $s[2].l],
            3: ['l', $s[4].l],
            4: ['l', $s[3].l]
        }
    };

    $('#sectionMemory input[id$="_display"]').on('keyup', function () {
        if (!inputRegex.test(this.value)) {
            return;
        }

        var index = parseInt(this.getAttribute('data-index'), 10),
            data = answerData[index][this.value],
            element = $s[index][data[0]],
            value = data[1];

        if (typeof value === 'object') {
            value = value.val() || value.attr('id').replace(/(\d).+$/, '$1');
        }

        element.val(value);
        $s[index][data[0] === 'p' ? 'l' : 'p'].focus();
    });
})();

(function () {
    /**
     * Morse
     */
    var freqs = {
        shell: '3.505',
        halls: '3.515',
        slick: '3.522',
        trick: '3.532',
        boxes: '3.535',
        leaks: '3.542',
        strobe: '3.545',
        bistro: '3.552',
        flick: '3.555',
        bombs: '3.565',
        'break': '3.572',
        brick: '3.575',
        steak: '3.582',
        sting: '3.592',
        vector: '3.595',
        beats: '3.600'
    },
        codes = {
            '.-':   'a',
            '-...': 'b',
            '-.-.': 'c',
            '.':    'e',
            '..-.': 'f',
            '--.':  'g',
            '....': 'h',
            '..':   'i',
            '-.-':  'k',
            '.-..': 'l',
            '--':   'm',
            '-.':   'n',
            '---':  'o',
            '.-.':  'r',
            '...':  's',
            '-':    't',
            '...-': 'v',
            '-..-': 'x'
        },
        codeRegex = new RegExp(' *(' + Object.keys(codes).join('|').replace(/\./g, '\\.') + ')(?![\.-]) *', 'g'),
        $rows = {},
        $table = $('#morseTable');

    for (var word in freqs) {
        var $row = $('
' + word + '	' + freqs[word] + '
');
        $table.append($row);
        $rows[word + word] = $row; // Concat to handle the wrap around
    }

    $('#morseInput').on('keyup', function () {
        var lines = this.value.split('\n'),
            regexes = [];

        lines.forEach(function (line) {
            if (!line) return;

            // JS has no negative lookbehind; gotta do the replacement twice
            regexes.push(new RegExp(
                line.replace(codeRegex, codeRegexCallback)
                    .replace(codeRegex, codeRegexCallback)
            ));
        });
        console.log(regexes);

        for (var word in $rows) {
            var matchAll = true;
            $.each(regexes, function (i, regex) {
                if (!regex.test(word)) {
                    matchAll = false;
                    return false;
                }
            });

            $rows[word].toggleClass('inactive', !matchAll);
        }
    });

    function codeRegexCallback(match, code) {
        return codes[code];
    }
})();

(function () {
    /**
     * Wire sequence
     */
    var $instructions = $('#sequenceInstruction'),
        count = {
            red: 0,
            blue: 0,
            black: 0
        },
        occurrences = {
            red: ['C', 'B', 'A', 'AC', 'B', 'AC', 'ABC', 'AB', 'B'],
            blue: ['B', 'AC', 'B', 'A', 'B', 'BC', 'C', 'AC', 'A'],
            black: ['ABC', 'AC', 'B', 'AC', 'B', 'BC', 'AB', 'C', 'C']
        };

    $('#sequenceOptions button').on('click', function () {
        var colour = this.getAttribute('data-colour'),
                letter = this.getAttribute('data-letter'),
                $li = $('
<button class="button ' + colour + '" data-colour="' + colour + '" data-letter="' + letter
                        + '">' + letter + '</button>
');

        if (typeof occurrences[colour][count[colour]] !== 'undefined') {
            $li.append(occurrences[colour][count[colour]].indexOf(letter) === -1 ? 'Ignore' : 'Cut');
            count[colour]++;
            $instructions.append($li);
        }
    });

    $instructions.on('click', 'button', function () {
        var deleteIndex = $(this).parent().index();

        $instructions.children('li:eq(' + deleteIndex + '), li:gt(' + deleteIndex + ')').each(function () {
            var button = this.firstChild;
            count[button.getAttribute('data-colour')]--;
            $(this).remove();
        });
    });
})();

(function () {
    /**
     * Venn diagram
     */
    var instructions = {
                C: 'Cut the wire',
                D: 'Do not cut the wire',
                S: 'If the last digit of the serial number is even, cut the wire',
                P: 'If the bomb has a parallel port, cut the wire',
                B: 'If the bomb has two or more batteries, cut the wire'
            },
            conditions = { // Initialize
                false: {
                    false: {
                        false: {}, true: {}
                    },
                    true: {
                        false: {}, true: {}
                    }
                },
                true: {
                    false: {
                        false: {}, true: {}
                    },
                    true: {
                        false: {}, true: {}
                    }
                }
            },
            $checkboxes = $('#vennOptions > li > input'),
            $instruction = $('#vennInstruction');

    //         Red    Blue   Star   LED
    conditions[false][false][false][false] = 'C';
    conditions[false][false][false][true ] = 'D';
    conditions[false][false][true ][false] = 'C';
    conditions[false][false][true ][true ] = 'B';
    conditions[false][true ][false][false] = 'S';
    conditions[false][true ][false][true ] = 'P';
    conditions[false][true ][true ][false] = 'D';
    conditions[false][true ][true ][true ] = 'P';
    conditions[true ][false][false][false] = 'S';
    conditions[true ][false][false][true ] = 'B';
    conditions[true ][false][true ][false] = 'C';
    conditions[true ][false][true ][true ] = 'B';
    conditions[true ][true ][false][false] = 'S';
    conditions[true ][true ][false][true ] = 'S';
    conditions[true ][true ][true ][false] = 'P';
    conditions[true ][true ][true ][true ] = 'D';

    $checkboxes.on('change', function () {
        var checked = [];
        $checkboxes.each(function () {
            checked.push(this.checked);
        });

        $instruction.html(instructions[conditions[checked[0]][checked[1]][checked[2]][checked[3]]]);
    });
})();

(function () {
    /**
     * Passwords
     * */
    var passwords = [
                'about', 'after', 'again', 'below', 'could',
                'every', 'first', 'found', 'great', 'house',
                'large', 'learn', 'never', 'other', 'place',
                'plant', 'point', 'right', 'small', 'sound',
                'spell', 'still', 'study', 'their', 'there',
                'these', 'thing', 'think', 'three', 'water',
                'where', 'which', 'world', 'would', 'write'
            ],
            $passwordContainer = $('#passwords'),
            $passwords = [],
            $row,
            $inputs = $('#passwordOptions > li > input');

    passwords.forEach(function (password, i) {
        var $cell;
        if (i % 5 === 0) {
            $row = $('

').appendTo($passwordContainer);
        }

        $cell = $('' + password + '	').appendTo($row);
        $passwords.push($cell);
    });

    $inputs.on('keyup', function () {
        var pattern = '^',
                regex;
        this.value = this.value.replace(/[^a-z]/gi, '').toLowerCase();
        $inputs.each(function (i, input) {
            pattern += input.value ? '[' + input.value + ']' : '.';
        });
        pattern += '$';
        regex = new RegExp(pattern);

        $passwords.forEach(function ($password) {
            $password.toggleClass("inactive", !$password.text().match(regex));
        });
    });
})();
<|endoftext|>


<commit_before>const express = require('express');
const path = require('path');
const { Sparky, FuseBox, WebIndexPlugin, CSSPlugin, EnvPlugin, QuantumPlugin } = require('fuse-box');

let production = false;

Sparky.task('build', () => {
    // config
    const fuse = FuseBox.init({
        homeDir: 'src',
        output: 'dist/$name.js',
        target: 'browser',
        experimentalFeatures: true,
        cache: !production,
        sourceMaps: !production,
        hash: production,
        plugins: [
            EnvPlugin({ NODE_ENV: production ? 'production' : 'development' }),
            WebIndexPlugin({
                title: 'rbtv.ts.youtube',
                template: 'src/index.html',
                path: '/'
            }),
            CSSPlugin(),
            production &&
                QuantumPlugin({
                    treeshake: true,
                    removeExportsInterop: false,
                    uglify: true,
                    api: core => {
                        core.solveComputed('moment/moment.js');
                    }
                })
        ]
    });

    // if (!production) {
    //     // server
    //     fuse.dev({ root: false }, server => {
    //         const dist = path.join(__dirname, 'dist');
    //         const app = server.httpServer.app;
    //
    //         app.use('/static/', express.static(path.join(dist, 'static')));
    //         app.get('*', (req, res) => {
    //             res.sendFile(path.join(dist, 'static/index.html'));
    //         });
    //     });
    // }

    // vendor
    const vendor = fuse.bundle('vendor').instructions(`~ **/**.{js,ts,tsx} +tslib - [@types/**/**.d.ts]`);

    // app
    const app = fuse
        .bundle('app')
        .splitConfig({ browser: '/', dest: '/' })
        .split('containers/activities/**', 'activities > containers/activities/index.tsx')
        .split('containers/video/**', 'video > containers/video/index.tsx')
        .split('containers/playlists/**', 'playlists > containers/playlists/index.tsx')
        .instructions(`> [index.tsx] + [containers/**/**.{ts, tsx}]`);

    if (!production) {
        // HMR
        vendor.hmr();
        app.hmr().watch();
    }

    // Run
    fuse.run();
});

Sparky.task('clean', () => Sparky.src('dist/*').clean('dist/'));
Sparky.task('default', ['clean', 'build'], () => {});
Sparky.task('set-production-env', () => (production = true));
Sparky.task('dist', ['clean', 'set-production-env', 'build'], () => {});
<commit_msg>chore: disable hmr; use watch<commit_after>const express = require('express');
const path = require('path');
const { Sparky, FuseBox, WebIndexPlugin, CSSPlugin, EnvPlugin, QuantumPlugin } = require('fuse-box');

let production = false;

Sparky.task('build', () => {
    // config
    const fuse = FuseBox.init({
        homeDir: 'src',
        output: 'dist/$name.js',
        target: 'browser',
        experimentalFeatures: true,
        cache: !production,
        sourceMaps: !production,
        hash: production,
        plugins: [
            EnvPlugin({ NODE_ENV: production ? 'production' : 'development' }),
            WebIndexPlugin({
                title: 'rbtv.ts.youtube',
                template: 'src/index.html',
                path: '/'
            }),
            CSSPlugin(),
            production &&
                QuantumPlugin({
                    treeshake: true,
                    removeExportsInterop: false,
                    uglify: true,
                    api: core => {
                        core.solveComputed('moment/moment.js');
                    }
                })
        ]
    });

    // if (!production) {
    //     // server
    //     fuse.dev({ root: false }, server => {
    //         const dist = path.join(__dirname, 'dist');
    //         const app = server.httpServer.app;
    //
    //         app.use('/static/', express.static(path.join(dist, 'static')));
    //         app.get('*', (req, res) => {
    //             res.sendFile(path.join(dist, 'static/index.html'));
    //         });
    //     });
    // }

    // vendor
    const vendor = fuse.bundle('vendor').instructions(`~ **/**.{js,ts,tsx} +tslib - [@types/**/**.d.ts]`);

    // app
    const app = fuse
        .bundle('app')
        .splitConfig({ browser: '/', dest: '/' })
        .split('containers/activities/**', 'activities > containers/activities/index.tsx')
        .split('containers/video/**', 'video > containers/video/index.tsx')
        .split('containers/playlists/**', 'playlists > containers/playlists/index.tsx')
        .instructions(`> [index.tsx] + [containers/**/**.{ts, tsx}]`);

    if (!production) {
        // HMR
        // vendor.hmr();
        // app.hmr().watch();
        vendor.watch();
        app.watch();
    }

    // Run
    fuse.run();
});

Sparky.task('clean', () => Sparky.src('dist/*').clean('dist/'));
Sparky.task('default', ['clean', 'build'], () => {});
Sparky.task('set-production-env', () => (production = true));
Sparky.task('dist', ['clean', 'set-production-env', 'build'], () => {});
<|endoftext|>


<commit_before>exports.getAssoc = function() {

  var Assoc = {
    hasOne: [
      { parent: "Diagnostic", child: "Spectrum", as: "Spectrum" },
      { parent: "Spectrum", child: "Diagnostic", as: "Diagnostic" }
    ],
    hasMany: [
      { parent: "Source", child: "Spectrum", as: "Spectrum" },
      { parent: "Source", child: "Diagnostic", as: "Diagnostic" }
    ]
  };

  return Assoc;
};<commit_msg>version association<commit_after>exports.getAssoc = function() {

  var Assoc = {
    hasOne: [
      { parent: "Diagnostic", child: "Spectrum", as: "Spectrum" },
      { parent: "Spectrum", child: "Diagnostic", as: "Diagnostic" },
      { parent: "Source", child: "Version", as: "AppVersion" }
    ],
    hasMany: [
      { parent: "Source", child: "Spectrum", as: "Spectrum" },
      { parent: "Source", child: "Diagnostic", as: "Diagnostic" }
    ]
  };

  return Assoc;
};<|endoftext|>


<commit_before>'use strict';

module.exports = function(Schema, mongoose){

    var _           = app.utils._
    ,   Q           = app.utils.q
    ,   log         = app.utils.log
    ,   token       = app.utils.token
    ,   lifetime    = app.config.security.expiresIn;

    Q.longStackSupport = true;

    var TokenSchema = new Schema({
        user: {
            type: Schema.Types.ObjectId,
            ref: 'User',
            required: true
        },
        client: {
            type: Schema.Types.ObjectId,
            ref: 'Client',
            required: true
        },
        scope: [ String ],
        access: {
            type: String,
            unique: true,
            required: true,
            default: token(64)
        },
        refresh: {
            type: String,
            unique: true,
            required: true,
            default: token(64)
        },
        created: {
            type: Date,
            required: true,
            default: Date.now
        },
        expires: {
            type: Date,
            required: true
        }
    });

    TokenSchema.pre('validate', function(next){
        mongoose.model("Token").list().then(function(list) {
            app.utils.log.debug(list);
        });

        app.utils.log.debug(this);

        var date = new Date(this.created);
        date.setSeconds(date.getSeconds() + lifetime);
        this.expires = date;

        next();
    });

    TokenSchema.statics.token = function(user, client){

        return Q.nbind(this.findOne, this)({
            user: user,
            client: client,
            expires: { $gte: new Date() }
        }, [
            'scope',
            'access',
            'refresh'
        ], {
            sort: { created: 1 }
        });
    };

    TokenSchema.statics.user = function(access){

        return Q.nbind(this.findOne, this)({
            access: access
        }, 'user', {
            sort: { created: 1 }
        });
    };

    TokenSchema.statics.insert = function(user, client){

        return Q.nbind(this.create, this)({
            user: user,
            client: client
        });
    };

    TokenSchema.statics.refresh = function(refresh){

        return Q.nbind(this.findOne, this)({
            refresh: refresh
        }, {
            sort: { created: 1 }
        });
    };

    return TokenSchema;

};
<commit_msg>@Testing4<commit_after>'use strict';

module.exports = function(Schema, mongoose){

    var _           = app.utils._
    ,   Q           = app.utils.q
    ,   log         = app.utils.log
    ,   token       = app.utils.token
    ,   lifetime    = app.config.security.expiresIn;

    Q.longStackSupport = true;

    var TokenSchema = new Schema({
        user: {
            type: Schema.Types.ObjectId,
            ref: 'User',
            required: true
        },
        client: {
            type: Schema.Types.ObjectId,
            ref: 'Client',
            required: true
        },
        scope: [ String ],
        access: {
            type: String,
            unique: true,
            required: true,
            default: token(64)
        },
        refresh: {
            type: String,
            unique: true,
            required: true,
            default: token(64)
        },
        created: {
            type: Date,
            required: true,
            default: Date.now
        },
        expires: {
            type: Date,
            required: true
        }
    });

    TokenSchema.pre('validate', function(next){
        var Token = mongoose.models.Token;

        Token.list().then(function(list) {
            app.utils.log.debug(list);
        });

        app.utils.log.debug(this);

        var date = new Date(this.created);
        date.setSeconds(date.getSeconds() + lifetime);
        this.expires = date;

        next();
    });

    TokenSchema.statics.list = function(query, fields, projection){
        return Q.nbind(this.find, this)(query, fields, projection);
    };

    TokenSchema.statics.token = function(user, client){

        return Q.nbind(this.findOne, this)({
            user: user,
            client: client,
            expires: { $gte: new Date() }
        }, [
            'scope',
            'access',
            'refresh'
        ], {
            sort: { created: 1 }
        });
    };

    TokenSchema.statics.user = function(access){

        return Q.nbind(this.findOne, this)({
            access: access
        }, 'user', {
            sort: { created: 1 }
        });
    };

    TokenSchema.statics.insert = function(user, client){

        return Q.nbind(this.create, this)({
            user: user,
            client: client
        });
    };

    TokenSchema.statics.refresh = function(refresh){

        return Q.nbind(this.findOne, this)({
            refresh: refresh
        }, {
            sort: { created: 1 }
        });
    };

    return TokenSchema;

};
<|endoftext|>


<commit_before>var keystone = require('arch-keystone');
var transform = require('model-transform');
var Types = keystone.Field.Types;

var Topic = new keystone.List('Topic', {
	autokey: { from: 'name', path: 'key', unique: true },
    track: true,
	sortable: true,
});

Topic.add({
  name: { label: '專題名稱', type: String, required: true },
  subtitle: { label: '副標', type: String, require: false },
  state: { label: '狀態', type: Types.Select, options: 'draft, published', default: 'draft', index: true },
  brief: { label: '前言', type: Types.Html, wysiwyg: true, height: 150 },
  leading: { label: '標頭樣式', type: Types.Select, options: 'video, slideshow, image', index: true },
  sections: { label: '分區', type: Types.Relationship, ref: 'Section', many: true },
  heroVideo: { label: 'Leading Video', type: Types.Relationship, ref: 'Video', dependsOn: { leading: 'video' } },
  heroImage: { label: '首圖', type: Types.ImageRelationship, ref: 'Image', dependsOn: { leading: 'image' } },
  heroImageSize: { label: '首圖尺寸', type: Types.Select, options: 'extend, normal, small', default: 'normal', dependsOn: { heroImage: {'$regex': '.+/i'}}},
  og_title: { label: 'FB分享標題', type: String, require: false},
  og_description: { label: 'FB分享說明', type: String, require: false},
  og_image: { label: 'FB分享縮圖', type: Types.ImageRelationship, ref: 'Image' },
  isFeatured: { label: '置頂', type: Boolean, index: true },
  title_style: { label: '專題樣式', type: Types.Select, options: 'feature, wide', default: 'feature', index: true },
  type: { label: '型態', type: Types.Select, options: 'list, timeline', default: 'list' },
  source: { label: '資料來源', type: Types.Select, options: 'posts, activities', dependsOn: { type: 'timeline' } },
  sort: { label: '時間軸排序', type: Types.Select, options: 'asc, desc', dependsOn: { type: 'timeline' } },
  style: { label: 'CSS', type: Types.Textarea },
  javascript: { label: 'javascript', type: Types.Textarea },
  dfp: { label: 'DFP code', type: String, require: false},
  mobile_dfp: { label: 'Mobile DFP code', type: String, require: false},
});

Topic.relationship({ ref: 'Post', refPath: 'topics' });

transform.toJSON(Topic);
Topic.register();
<commit_msg>add the heroImage to the Topic<commit_after>var keystone = require('arch-keystone');
var transform = require('model-transform');
var Types = keystone.Field.Types;

var Topic = new keystone.List('Topic', {
	autokey: { from: 'name', path: 'key', unique: true },
    track: true,
	sortable: true,
});

Topic.add({
  name: { label: '專題名稱', type: String, required: true },
  heroImage: { label: '專題主圖', type: Types.ImageRelationship, ref: 'Image' },
  subtitle: { label: '副標', type: String, require: false },
  state: { label: '狀態', type: Types.Select, options: 'draft, published', default: 'draft', index: true },
  brief: { label: '前言', type: Types.Html, wysiwyg: true, height: 150 },
  leading: { label: '標頭樣式', type: Types.Select, options: 'video, slideshow, image', index: true },
  sections: { label: '分區', type: Types.Relationship, ref: 'Section', many: true },
  heroVideo: { label: 'Leading Video', type: Types.Relationship, ref: 'Video', dependsOn: { leading: 'video' } },
  heroImage: { label: '首圖', type: Types.ImageRelationship, ref: 'Image', dependsOn: { leading: 'image' } },
  heroImageSize: { label: '首圖尺寸', type: Types.Select, options: 'extend, normal, small', default: 'normal', dependsOn: { heroImage: {'$regex': '.+/i'}}},
  og_title: { label: 'FB分享標題', type: String, require: false},
  og_description: { label: 'FB分享說明', type: String, require: false},
  og_image: { label: 'FB分享縮圖', type: Types.ImageRelationship, ref: 'Image' },
  isFeatured: { label: '置頂', type: Boolean, index: true },
  title_style: { label: '專題樣式', type: Types.Select, options: 'feature, wide', default: 'feature', index: true },
  type: { label: '型態', type: Types.Select, options: 'list, timeline', default: 'list' },
  source: { label: '資料來源', type: Types.Select, options: 'posts, activities', dependsOn: { type: 'timeline' } },
  sort: { label: '時間軸排序', type: Types.Select, options: 'asc, desc', dependsOn: { type: 'timeline' } },
  style: { label: 'CSS', type: Types.Textarea },
  javascript: { label: 'javascript', type: Types.Textarea },
  dfp: { label: 'DFP code', type: String, require: false},
  mobile_dfp: { label: 'Mobile DFP code', type: String, require: false},
});

Topic.relationship({ ref: 'Post', refPath: 'topics' });

transform.toJSON(Topic);
Topic.register();
<|endoftext|>


<commit_before>import {Buttons} from 'widget/buttons'
import {Form} from './form/form'
import {Subject, switchMap, takeUntil} from 'rxjs'
import {compose} from 'compose'
import {connect, select} from 'store'
import {getRecipeType} from 'app/home/body/process/recipeTypes'
import {map} from 'rxjs'
import {msg} from 'translate'
import {recipeAccess} from 'app/home/body/process/recipeAccess'
import {withRecipe} from 'app/home/body/process/recipeContext'
import PropTypes from 'prop-types'
import React from 'react'
import api from 'api'

const mapStateToProps = () => {
    return {
        recipes: select('process.recipes')
    }
}

const mapRecipeToProps = recipe => {
    return ({
        projectId: recipe.projectId
    })
}

class _RecipeInput extends React.Component {
    state = {
        all: false
    }
    cancel$ = new Subject()

    render() {
        const {projectId, stream, recipes, filter, input, label, placeholder, autoFocus} = this.props
        const {all} = this.state
        const options = recipes
            .filter(recipe => {
                const {projectId: p, type} = recipe
                return (all || p === projectId || (!p && !projectId))
                   && (filter ? filter(getRecipeType(type), recipe) : true)
            })
            .map(recipe => ({
                value: recipe.id,
                label: recipe.name
            }))

        const additionalButtons = (
            <Buttons
                key={'inverted'}
                selected={[all]}
                look='transparent'
                shape={'pill'}
                air={'less'}
                size={'x-small'}
                options={[
                    {value: true, label: 'ALL', tooltip: msg('widget.recipeInput.all.tooltip')}
                ]}
                multiple
                tabIndex={-1}
                onChange={() => {
                    this.setState({all: !all})
                }}
            />
        )

        return (
            <Form.Combo
                input={input}
                label={label || msg('widget.recipeInput.label')}
                placeholder={placeholder || msg('widget.recipeInput.placeholder')}
                options={options}
                autoFocus={autoFocus}
                additionalButtons={additionalButtons}
                errorMessage
                busyMessage={stream('LOAD_RECIPE').active}
                onChange={({value}) => this.loadRecipe(value)}
            />
        )
    }

    loadRecipe(recipeId) {
        const {stream, onError, onLoading, onLoaded, loadRecipe$} = this.props
        this.cancel$.next()
        if (recipeId) {
            onLoading && onLoading(recipeId)
            stream('LOAD_RECIPE',
                loadRecipe$(recipeId).pipe(
                    switchMap(recipe =>
                        api.gee.bands$({recipe}).pipe(
                            map(bandNames => ({
                                recipe,
                                bandNames,
                                type: getRecipeType(recipe.type)
                            }))
                        )
                    )
                ).pipe(
                    takeUntil(this.cancel$)
                ),
                result => onLoaded(result),
                error => onError && onError(error)
            )
        }
    }
}

export const RecipeInput = compose(
    _RecipeInput,
    connect(mapStateToProps),
    withRecipe(mapRecipeToProps),
    recipeAccess(),
)

RecipeInput.propTypes = {
    input: PropTypes.object.isRequired,
    filter: PropTypes.func,
    label: PropTypes.any,
    placeholder: PropTypes.string,
    onError: PropTypes.func,
    onLoaded: PropTypes.func,
    onLoading: PropTypes.func,
}
<commit_msg>Put all button in a list.<commit_after>import {Buttons} from 'widget/buttons'
import {Form} from './form/form'
import {Subject, switchMap, takeUntil} from 'rxjs'
import {compose} from 'compose'
import {connect, select} from 'store'
import {getRecipeType} from 'app/home/body/process/recipeTypes'
import {map} from 'rxjs'
import {msg} from 'translate'
import {recipeAccess} from 'app/home/body/process/recipeAccess'
import {withRecipe} from 'app/home/body/process/recipeContext'
import PropTypes from 'prop-types'
import React from 'react'
import api from 'api'

const mapStateToProps = () => {
    return {
        recipes: select('process.recipes')
    }
}

const mapRecipeToProps = recipe => {
    return ({
        projectId: recipe.projectId
    })
}

class _RecipeInput extends React.Component {
    state = {
        all: false
    }
    cancel$ = new Subject()

    render() {
        const {projectId, stream, recipes, filter, input, label, placeholder, autoFocus} = this.props
        const {all} = this.state
        const options = recipes
            .filter(recipe => {
                const {projectId: p, type} = recipe
                return (all || p === projectId || (!p && !projectId))
                   && (filter ? filter(getRecipeType(type), recipe) : true)
            })
            .map(recipe => ({
                value: recipe.id,
                label: recipe.name
            }))

        const additionalButtons = [
            <Buttons
                key={'inverted'}
                selected={[all]}
                look='transparent'
                shape={'pill'}
                air={'less'}
                size={'x-small'}
                options={[
                    {value: true, label: 'ALL', tooltip: msg('widget.recipeInput.all.tooltip')}
                ]}
                multiple
                tabIndex={-1}
                onChange={() => {
                    this.setState({all: !all})
                }}
            />
        ]

        return (
            <Form.Combo
                input={input}
                label={label || msg('widget.recipeInput.label')}
                placeholder={placeholder || msg('widget.recipeInput.placeholder')}
                options={options}
                autoFocus={autoFocus}
                additionalButtons={additionalButtons}
                errorMessage
                busyMessage={stream('LOAD_RECIPE').active}
                onChange={({value}) => this.loadRecipe(value)}
            />
        )
    }

    loadRecipe(recipeId) {
        const {stream, onError, onLoading, onLoaded, loadRecipe$} = this.props
        this.cancel$.next()
        if (recipeId) {
            onLoading && onLoading(recipeId)
            stream('LOAD_RECIPE',
                loadRecipe$(recipeId).pipe(
                    switchMap(recipe =>
                        api.gee.bands$({recipe}).pipe(
                            map(bandNames => ({
                                recipe,
                                bandNames,
                                type: getRecipeType(recipe.type)
                            }))
                        )
                    )
                ).pipe(
                    takeUntil(this.cancel$)
                ),
                result => onLoaded(result),
                error => onError && onError(error)
            )
        }
    }
}

export const RecipeInput = compose(
    _RecipeInput,
    connect(mapStateToProps),
    withRecipe(mapRecipeToProps),
    recipeAccess(),
)

RecipeInput.propTypes = {
    input: PropTypes.object.isRequired,
    filter: PropTypes.func,
    label: PropTypes.any,
    placeholder: PropTypes.string,
    onError: PropTypes.func,
    onLoaded: PropTypes.func,
    onLoading: PropTypes.func,
}
<|endoftext|>


<commit_before>/*The file to interface with the beam API */

//run 'npm i -S beam-client-node' to get these two things
const BeamClient = require('beam-client-node');
const BeamSocket = require('beam-client-node/lib/ws');
const config = require('./config.json');
const spawn = require('child_process').spawn;

const debug = true;

let userInfo;
let channelId = -1;

const client = new BeamClient();

//OAuth gives permision for all following actions
client.use('oauth',{
  tokens:{
    access: config.OAuth,
    expires: Date.now() + (365 * 24 *60 *60 *1000)
  },
});

client.request('GET', 'users/current')
.then(response => {
    if(debug) console.log(response.body);
    userInfo = response.body;
    return client.request('GET','users/'+userInfo.id+'/follows');
})
.then(response =>{
  channelId = response.body[0].id;
  if(debug) console.log(response.body[0].name);
  return client.chat.join(channelId);
})
.then(response => {
    const body = response.body;
    if(debug) console.log(body);
    if(!body.roles.includes('Mod')){
      return;
    }
    if(channelId !== -1){
      return createChatSocket(userInfo.id, channelId, body.endpoints, body.authkey);
    }else{
      return createChatSocket(userInfo.id, userInfo.channel.id, body.endpoints, body.authkey);
    }
})
.catch(error => {
      console.log("Something went wrong:", error);
});

/**
 * Creates a beam chat socket and sets up listeners to various chat events.
 * @param {number} userId The user to authenticate as
 * @param {number} channelId The channel id to join
 * @param {any} endpoints An endpoints array from a beam.chat.join call.
 * @param {any} authkey An authentication key from a beam.chat.join call.
 * @returns {Promise.<>}
 */
function createChatSocket (userId, channelId, endpoints, authkey) {
    const socket = new BeamSocket(endpoints).boot();

    socket.auth(channelId, userId, authkey)
    .then(() => {
        if(debug) console.log('You are now authenticated!');
        // Send a chat message
        return socket.call('msg', ['Hello world!']);
    })
    .catch(error => {
        console.log('Oh no! An error occurred!', error);
    });

    // Listen to chat messages, note that you will also receive your own!
    socket.on('ChatMessage', data => {
        if(debug) console.log('We got a ChatMessage packet!');
        if(debug) console.log(data);
        if(debug) console.log(data.message); // lets take a closer look

        moderate(socket,data);
    });

    // Listen to socket errors, you'll need to handle these!
    socket.on('error', error => {
        console.error('Socket error', error);
    });
}

function moderate(socket,messageData){
  if(debug) console.log("In moderate function");
  //This is how to send the data to be processed by the python
  var moderator = spawn('python', ['hammer.py']);
  moderator.stdin.write(messageToString(messageData.message.message));
  moderator.stdin.end();

  moderator.stdout.on('data', function(data){
    var pythonOut = data.toString();
    var action = pythonOut.split(" ")[0].trim();
    var indexOfSpace = pythonOut.indexOf(" ");
    var response = pythonOut.substr(indexOfSpace);
    if(indexOfSpace === -1){
      response = "";
    }
    var user = messageData.user_name;
    if(debug){
      console.log("User is:", user);
      console.log("Action to take:",action);
      console.log("Response is: ","@"+user+": "+response);
    }
    if(action === "timeout"){
      if(debug){
        console.log("Need to timeout",user);
      }else{
        socket.timeout(user,config.timeoutDuration);
        if(!config.silentBans) socket.msg("@"+user+": "+response);
      }
    }else if(action === "ban"){
      if(debug){
        console.log("Need to ban",user);
      }else{
        socket.timeout(user,config.banDuration);
        if(!config.silentBans) socket.msg("@"+user+": "+response);
      }
    }else if(action === "purge"){
      if(debug){
        console.log("Need to purge",user);
      }else{
        socket.purge(user);
        if(!config.silentBans) socket.msg("@"+user+": "+response);
      }
    }else if(action === "nothing"){
      if(debug){console.log("No action to take");}
      //THIS SPACE INTENTIONALLY LEFT BLANK
    }
  });

  moderator.stdout.on('end', function(){
    if(debug) console.log("Finished moderate parse");
  });
}

function messageToString(array){
  var result = "";
  for(var index in array){
    var element = array[index];
    result += element.text;
  }
  if(debug) console.log("messageToString is: ", result);
  return result;
}
<commit_msg>added some comments<commit_after>/*The file to interface with the beam API */

//run 'npm i -S beam-client-node' to get these two things
const BeamClient = require('beam-client-node');
const BeamSocket = require('beam-client-node/lib/ws');
const config = require('./config.json');
const spawn = require('child_process').spawn;

const debug = true;

let userInfo;
let channelId = -1;

const client = new BeamClient();

//OAuth gives permision for all following actions
client.use('oauth',{
  tokens:{
    access: config.OAuth,
    expires: Date.now() + (365 * 24 *60 *60 *1000)
  },
});

//get following channel and join its chat
client.request('GET', 'users/current')
.then(response => {
    if(debug) console.log(response.body);
    userInfo = response.body;
    return client.request('GET','users/'+userInfo.id+'/follows');
})
.then(response =>{
  //TODO modify to join all followed channels
  channelId = response.body[0].id;
  if(debug) console.log(response.body[0].name);
  return client.chat.join(channelId);
})
.then(response => {
    const body = response.body;
    if(debug) console.log(body);
    if(!body.roles.includes('Mod')){
      return;
    }
    if(channelId !== -1){
      return createChatSocket(userInfo.id, channelId, body.endpoints, body.authkey);
    }else{
      return createChatSocket(userInfo.id, userInfo.channel.id, body.endpoints, body.authkey);
    }
})
.catch(error => {
      console.log("Something went wrong:", error);
});

/**
 * Creates a beam chat socket and sets up listeners to various chat events.
 * @param {number} userId The user to authenticate as
 * @param {number} channelId The channel id to join
 * @param {any} endpoints An endpoints array from a beam.chat.join call.
 * @param {any} authkey An authentication key from a beam.chat.join call.
 * @returns {Promise.<>}
 */
function createChatSocket (userId, channelId, endpoints, authkey) {
    const socket = new BeamSocket(endpoints).boot();

    socket.auth(channelId, userId, authkey)
    .then(() => {
        if(debug) console.log('You are now authenticated!');
        // Send a chat message
        return socket.call('msg', ['Hello world!']);
    })
    .catch(error => {
        console.log('Oh no! An error occurred!', error);
    });

    // Listen to chat messages, note that you will also receive your own!
    socket.on('ChatMessage', data => {
        if(debug) console.log('We got a ChatMessage packet!');
        if(debug) console.log(data);
        if(debug) console.log(data.message); // lets take a closer look

        moderate(socket,data);
    });

    // Listen to socket errors, you'll need to handle these!
    socket.on('error', error => {
        console.error('Socket error', error);
    });
}

function moderate(socket,messageData){
  if(debug) console.log("In moderate function");
  //This is how to send the data to be processed by the python
  var moderator = spawn('python', ['hammer.py']);
  moderator.stdin.write(messageToString(messageData.message.message));
  moderator.stdin.end();

  moderator.stdout.on('data', function(data){
    var pythonOut = data.toString();
    var action = pythonOut.split(" ")[0].trim();
    var indexOfSpace = pythonOut.indexOf(" ");
    var response = pythonOut.substr(indexOfSpace);
    if(indexOfSpace === -1){
      response = "";
    }
    var user = messageData.user_name;
    if(debug){
      console.log("User is:", user);
      console.log("Action to take:",action);
      console.log("Response is: ","@"+user+": "+response);
    }
    //Parse the actions and take action if needed
    if(action === "timeout"){
      if(debug){
        console.log("Need to timeout",user);
      }else{
        socket.timeout(user,config.timeoutDuration);
        if(!config.silentBans) socket.msg("@"+user+": "+response);
      }
    }else if(action === "ban"){
      if(debug){
        console.log("Need to ban",user);
      }else{
        socket.timeout(user,config.banDuration);
        if(!config.silentBans) socket.msg("@"+user+": "+response);
      }
    }else if(action === "purge"){
      if(debug){
        console.log("Need to purge",user);
      }else{
        socket.purge(user);
        if(!config.silentBans) socket.msg("@"+user+": "+response);
      }
    }else if(action === "nothing"){
      if(debug){console.log("No action to take");}
      //THIS SPACE INTENTIONALLY LEFT BLANK
    }
  });

  moderator.stdout.on('end', function(){
    if(debug) console.log("Finished moderate parse");
  });
}

function messageToString(array){
  var result = "";
  for(var index in array){
    var element = array[index];
    result += element.text;
  }
  if(debug) console.log("messageToString is: ", result);
  return result;
}
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/ui/layout/cssgrid/GridLayoutBase",
	"sap/ui/Device",
	"sap/ui/layout/library"
], function (
	GridLayoutBase,
	Device
) {
	"use strict";

	var RCL_RANGE_SET = "RCLRangeSet";

	Device.media.initRangeSet(RCL_RANGE_SET, [600, 1024, 1280, 1440, 1620, 1920], "px", ["S", "M", "ML", "L", "XL", "XXL", "XXXL"], true);

	/**
	 * Constructor for a new ResponsiveColumnLayout.
	 *
	 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
	 * @param {object} [mSettings] Initial settings for the new control
	 *
	 * @class
	 *
	 * Represents a layout which displays variable number of columns, depending on available screen size.
	 * With that it achieves flexible layouts and line breaks for large, medium,
	 * and small-sized screens, such as desktop, tablet, and mobile.
	 *
	 * Grid row's height is dynamically determined by the height of the highest grid element on this row.
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @extends sap.ui.layout.cssgrid.GridLayoutBase
	 *
	 * @since 1.72
	 * @constructor
	 * @public
	 * @alias sap.ui.layout.cssgrid.ResponsiveColumnLayout
	 * @ui5-metamodel This simple type will also be described in the UI5 (legacy) designtime metamodel
	 */
	var ResponsiveColumnLayout = GridLayoutBase.extend("sap.ui.layout.cssgrid.ResponsiveColumnLayout", {
		metadata: {
			library: "sap.ui.layout",
			properties: {
			},
			events: {

				/**
				 * Fired when the currently active layout changes
				 */
				layoutChange: {
					parameters: {

						/**
						 * The name of the newly active layout - "S", "M", "ML", "L", "XL", "XXL" or "XXXL".
						 */
						layout: { type: "string" }
					}
				}
			}
		}
	});

	/**
	 * CSS class for the current layout.
	 * @string
	 * @private
	 */
	ResponsiveColumnLayout.prototype._sCurrentLayoutClassName = "";

	/**
	 * Returns if the Grid Layout is responsive.
	 * @public
	 * @returns {boolean} If the Grid Layout is responsive.
	 */
	ResponsiveColumnLayout.prototype.isResponsive = function () {
		return true;
	};

	/**
	 * Handler for IGridConfigurable onAfterRendering
	 *
	 * @private
	 * @param {sap.ui.layout.cssgrid.IGridConfigurable} oGrid The grid
	 */
	ResponsiveColumnLayout.prototype.onGridAfterRendering = function (oGrid) {
		this._applyLayout(oGrid);
	};

	/**
	 * Provides active settings for the CSSGrid.
	 *
	 * @returns {sap.ui.layout.cssgrid.GridSettings} ResponsiveColumnLayout The active GridSettings
	 * @override
	 */
	ResponsiveColumnLayout.prototype.getActiveGridSettings = function () {
		return null;
	};

	/**
	 * Resize handler for the ResponsiveColumnLayout.
	 *
	 * @param {object} oEvent - The event from a resize
	 * @private
	 */
	ResponsiveColumnLayout.prototype.onGridResize = function (oEvent) {
		if (!oEvent || oEvent.size.width === 0) {
			return;
		}

		this._applyLayout(oEvent.control);
	};

	/**
	 * @override
	 */
	ResponsiveColumnLayout.prototype.addGridStyles = function (oRM) {
		GridLayoutBase.prototype.addGridStyles.apply(this, arguments);

		oRM.class("sapUiLayoutCSSGridRCL");

	};

	/**
	 * Changes the active layout if it's different than the currently active one.
	 *
	 * @param {sap.ui.layout.cssgrid.IGridConfigurable} oGrid The grid which layout is going to be updated
	 * @private
	 */
	ResponsiveColumnLayout.prototype._applyLayout = function (oGrid) {
		var iWidth = oGrid.getDomRef().parentElement.clientWidth,
			oRange = Device.media.getCurrentRange(RCL_RANGE_SET, iWidth),
			sClassName = "sapUiLayoutCSSGridRCL-Layout" + oRange.name;

		if (this._sCurrentLayoutClassName === sClassName) {
			return;
		}

		oGrid.removeStyleClass(this._sCurrentLayoutClassName);
		oGrid.addStyleClass(sClassName);

		this._sCurrentLayoutClassName = sClassName;
		this.fireLayoutChange({
			layout: oRange.name
		});
	};

	return ResponsiveColumnLayout;
});<commit_msg>[INTERNAL] sap.ui.layout.cssgrid.ResponsiveColumnLayout: Correction in breakpoint<commit_after>/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/ui/layout/cssgrid/GridLayoutBase",
	"sap/ui/Device",
	"sap/ui/layout/library"
], function (
	GridLayoutBase,
	Device
) {
	"use strict";

	var RCL_RANGE_SET = "RCLRangeSet";

	Device.media.initRangeSet(RCL_RANGE_SET, [600, 1024, 1280, 1440, 1680, 1920], "px", ["S", "M", "ML", "L", "XL", "XXL", "XXXL"], true);

	/**
	 * Constructor for a new ResponsiveColumnLayout.
	 *
	 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
	 * @param {object} [mSettings] Initial settings for the new control
	 *
	 * @class
	 *
	 * Represents a layout which displays variable number of columns, depending on available screen size.
	 * With that it achieves flexible layouts and line breaks for large, medium,
	 * and small-sized screens, such as desktop, tablet, and mobile.
	 *
	 * Grid row's height is dynamically determined by the height of the highest grid element on this row.
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @extends sap.ui.layout.cssgrid.GridLayoutBase
	 *
	 * @since 1.72
	 * @constructor
	 * @public
	 * @alias sap.ui.layout.cssgrid.ResponsiveColumnLayout
	 * @ui5-metamodel This simple type will also be described in the UI5 (legacy) designtime metamodel
	 */
	var ResponsiveColumnLayout = GridLayoutBase.extend("sap.ui.layout.cssgrid.ResponsiveColumnLayout", {
		metadata: {
			library: "sap.ui.layout",
			properties: {
			},
			events: {

				/**
				 * Fired when the currently active layout changes
				 */
				layoutChange: {
					parameters: {

						/**
						 * The name of the newly active layout - "S", "M", "ML", "L", "XL", "XXL" or "XXXL".
						 */
						layout: { type: "string" }
					}
				}
			}
		}
	});

	/**
	 * CSS class for the current layout.
	 * @string
	 * @private
	 */
	ResponsiveColumnLayout.prototype._sCurrentLayoutClassName = "";

	/**
	 * Returns if the Grid Layout is responsive.
	 * @public
	 * @returns {boolean} If the Grid Layout is responsive.
	 */
	ResponsiveColumnLayout.prototype.isResponsive = function () {
		return true;
	};

	/**
	 * Handler for IGridConfigurable onAfterRendering
	 *
	 * @private
	 * @param {sap.ui.layout.cssgrid.IGridConfigurable} oGrid The grid
	 */
	ResponsiveColumnLayout.prototype.onGridAfterRendering = function (oGrid) {
		this._applyLayout(oGrid);
	};

	/**
	 * Provides active settings for the CSSGrid.
	 *
	 * @returns {sap.ui.layout.cssgrid.GridSettings} ResponsiveColumnLayout The active GridSettings
	 * @override
	 */
	ResponsiveColumnLayout.prototype.getActiveGridSettings = function () {
		return null;
	};

	/**
	 * Resize handler for the ResponsiveColumnLayout.
	 *
	 * @param {object} oEvent - The event from a resize
	 * @private
	 */
	ResponsiveColumnLayout.prototype.onGridResize = function (oEvent) {
		if (!oEvent || oEvent.size.width === 0) {
			return;
		}

		this._applyLayout(oEvent.control);
	};

	/**
	 * @override
	 */
	ResponsiveColumnLayout.prototype.addGridStyles = function (oRM) {
		GridLayoutBase.prototype.addGridStyles.apply(this, arguments);

		oRM.class("sapUiLayoutCSSGridRCL");

	};

	/**
	 * Changes the active layout if it's different than the currently active one.
	 *
	 * @param {sap.ui.layout.cssgrid.IGridConfigurable} oGrid The grid which layout is going to be updated
	 * @private
	 */
	ResponsiveColumnLayout.prototype._applyLayout = function (oGrid) {
		var iWidth = oGrid.getDomRef().parentElement.clientWidth,
			oRange = Device.media.getCurrentRange(RCL_RANGE_SET, iWidth),
			sClassName = "sapUiLayoutCSSGridRCL-Layout" + oRange.name;

		if (this._sCurrentLayoutClassName === sClassName) {
			return;
		}

		oGrid.removeStyleClass(this._sCurrentLayoutClassName);
		oGrid.addStyleClass(sClassName);

		this._sCurrentLayoutClassName = sClassName;
		this.fireLayoutChange({
			layout: oRange.name
		});
	};

	return ResponsiveColumnLayout;
});<|endoftext|>


<commit_before>(function (window, $, undefined) {
  'use strict';
  String.prototype.toBool = function () {
    switch (this.toLowerCase()) {
    case 'false':
    case 'no':
    case '0':
    case '':
      return false;
    default:
      return true;
    }
  };
  var SoftwareUpdateModel = function SoftwareUpdateModel(updateChecker, configManager) {
    var self = this;

    self.showAlerts = ko.observable(false);
    self.isSaved = ko.observable(false);
    self.configLoaded = ko.observable(false);

    configManager.getShowAlerts(function(showAlerts){
      self.showAlerts(showAlerts);
      self.configLoaded(true);
      subscribeToShowAlerts();
    });

    function subscribeToShowAlerts() {
      var showAlertsSubscription;
      showAlertsSubscription = self.showAlerts.subscribe(function(newValue){
        if (self.showAlerts()) {
          showAlertsSubscription.dispose();
        }
        configManager.setShowAlerts(self.showAlerts());
        self.isSaved(true);
        setTimeout(function() {self.isSaved(false);}, 2000);
        return true;
      });
    }
  };

  var SoftwareUpdater = function SoftwareUpdater(cockpit) {
    var self = this;

    var configManager = new SoftwareUpdaterConfig();

    var checker = new SoftwareUpdateChecker(configManager);
    this.model = new SoftwareUpdateModel(checker, configManager);

    console.log('Loading Software update plugin.');
    this.cockpit = cockpit;
    var packagesUpdated = false;

    this.cockpit.extensionPoints.rovSettings.append('

');
    $('body').prepend('

');
    //this technique forces relative path to the js file instead of the excution directory
    var jsFileLocation = urlOfJsFile('software-update.js');
    var updateSettings = this.cockpit.extensionPoints.rovSettings.find('#software-update-settings');
      updateSettings.load(jsFileLocation + '../settings.html', function () {
      ko.applyBindings(self.model, updateSettings[0]);
    });
    $('#software-update-alert-container').load(jsFileLocation + '../ui-templates.html', function () {
    });
    $('body').append('

');
    $('#proxy-container').hide();

    var logoTitleModel = {};
    logoTitleModel.cockpitVersion = ko.observable('N/A');
    logoTitleModel.bbSerial = ko.observable('N/A');
    $('html /deep/ a.brand').attr('data-bind', 'attr: {title: "Version: " + cockpitVersion() + "\\nBB Serial: " + bbSerial()}');

    $.get(configManager.dashboardUrl() + '/plugin/software/installed/openrov-cockpit', function(data) {
        if (data.length > 0) {
          logoTitleModel.cockpitVersion(data[0].package + ' - ' + data[0].version);
          console.log('Cockpit version: ' + logoTitleModel.cockpitVersion());
        }
       })
      .fail(function() { console.log('Error getting the cockpit version'); });
    $.get(configManager.dashboardUrl() + '/plugin/software/bbserial', function(data) {
         logoTitleModel.bbSerial(data.bbSerial);
         console.log('BB Serial: ' + logoTitleModel.bbSerial());
       })
      .fail(function() { console.log('Error getting the cockpit version'); });
    ko.applyBindings(logoTitleModel, $('a.brand')[0]);

    self.model.showAlerts.subscribe(function(newValue) {
      if (self.model.configLoaded() === true && newValue === true && packagesUpdated === true) {
        setTimeout(function() {
          checkForUpdates(checker);
        }, 5000);
      }
    });

    setTimeout(
      function() {
        $.post(configManager.dashboardUrl() + '/plugin/software/update/start')
          .done(function() {
            packagesUpdated = true;
            console.log('Done apt-get update on cockpit');
            checkForUpdates(checker);
           })
          .fail(function() { console.log('Error starting apt-get update on cockpit'); });
      }, 2 * 60 * 1000); //two minutes

    function checkForUpdates(checker) {
      checker.checkForUpdates(function (updates) {
        if (updates && updates.length > 0) {
          var model = { packages: updates, dashboardUrl: configManager.dashboardUrl };
          var container = $('#software-update-alert-container');
          ko.applyBindings(model, container[0]);
          container.removeClass('hide');
        }
      });

    }

  };
  window.Cockpit.plugins.push(SoftwareUpdater);

}(window, jQuery));
<commit_msg>Cleanup from review<commit_after>(function (window, $, undefined) {
  'use strict';
  String.prototype.toBool = function () {
    switch (this.toLowerCase()) {
    case 'false':
    case 'no':
    case '0':
    case '':
      return false;
    default:
      return true;
    }
  };
  var SoftwareUpdateModel = function SoftwareUpdateModel(updateChecker, configManager) {
    var self = this;

    self.showAlerts = ko.observable(false);
    self.isSaved = ko.observable(false);
    self.configLoaded = ko.observable(false);

    configManager.getShowAlerts(function(showAlerts){
      self.showAlerts(showAlerts);
      self.configLoaded(true);
      subscribeToShowAlerts();
    });

    function subscribeToShowAlerts() {
      var showAlertsSubscription;
      showAlertsSubscription = self.showAlerts.subscribe(function(newValue){
        if (self.showAlerts()) {
          showAlertsSubscription.dispose();
        }
        configManager.setShowAlerts(self.showAlerts());
        self.isSaved(true);
        setTimeout(function() {self.isSaved(false);}, 2000);
        return true;
      });
    }
  };

  var SoftwareUpdater = function SoftwareUpdater(cockpit) {
    var self = this;

    var configManager = new SoftwareUpdaterConfig();

    var checker = new SoftwareUpdateChecker(configManager);
    this.model = new SoftwareUpdateModel(checker, configManager);

    console.log('Loading Software update plugin.');
    this.cockpit = cockpit;
    var packagesUpdated = false;

    this.cockpit.extensionPoints.rovSettings.append('

');
    $('body').prepend('

');
    //this technique forces relative path to the js file instead of the excution directory
    var jsFileLocation = urlOfJsFile('software-update.js');
    var updateSettings = this.cockpit.extensionPoints.rovSettings.find('#software-update-settings');
      updateSettings.load(jsFileLocation + '../settings.html', function () {
      ko.applyBindings(self.model, updateSettings[0]);
    });
    $('#software-update-alert-container').load(jsFileLocation + '../ui-templates.html', function () {
    });
    $('body').append('

');
    $('#proxy-container').hide();

    var logoTitleModel = {};
    logoTitleModel.cockpitVersion = ko.observable('N/A');
    logoTitleModel.bbSerial = ko.observable('N/A');
    //todo: we should get rid of the /deep/ reference
    $('html /deep/ a.brand').attr('data-bind', 'attr: {title: "Version: " + cockpitVersion() + "\\nBB Serial: " + bbSerial()}');

    $.get(configManager.dashboardUrl() + '/plugin/software/installed/openrov-cockpit', function(data) {
        if (data.length > 0) {
          logoTitleModel.cockpitVersion(data[0].package + ' - ' + data[0].version);
          console.log('Cockpit version: ' + logoTitleModel.cockpitVersion());
        }
       })
      .fail(function() { console.log('Error getting the cockpit version'); });
    $.get(configManager.dashboardUrl() + '/plugin/software/bbserial', function(data) {
         logoTitleModel.bbSerial(data.bbSerial);
         console.log('BB Serial: ' + logoTitleModel.bbSerial());
       })
      .fail(function() { console.log('Error getting the cockpit version'); });
    ko.applyBindings(logoTitleModel, $('a.brand')[0]);

    self.model.showAlerts.subscribe(function(newValue) {
      if (self.model.configLoaded() === true && newValue === true && packagesUpdated === true) {
        setTimeout(function() {
          checkForUpdates(checker);
        }, 5000);
      }
    });

    setTimeout(
      function() {
        $.post(configManager.dashboardUrl() + '/plugin/software/update/start')
          .done(function() {
            packagesUpdated = true;
            console.log('Done apt-get update on cockpit');
            checkForUpdates(checker);
           })
          .fail(function() { console.log('Error starting apt-get update on cockpit'); });
      }, 2 * 60 * 1000); //two minutes

    function checkForUpdates(checker) {
      checker.checkForUpdates(function (updates) {
        if (updates && updates.length > 0) {
          var model = { packages: updates, dashboardUrl: configManager.dashboardUrl };
          var container = $('#software-update-alert-container');
          ko.applyBindings(model, container[0]);
          container.removeClass('hide');
        }
      });

    }

  };
  window.Cockpit.plugins.push(SoftwareUpdater);

}(window, jQuery));
<|endoftext|>