<commit_before>/*eslint-env browser */
/*global $, Modernizr, hopscotch */

$(document).ready(function() {
    // 'use strict'; // uncomment later on after full cleanup
    var citedGreaterThan = $('#id_cited_gt');
    var citedLessThan = $('#id_cited_lt');

    function makeSearchPath(tabSwitch) {
        // Empty the sliders if they are both at their max
        if (citedGreaterThan.val() === 0 && citedLessThan.val() === 20000) {
        // see https://github.com/freelawproject/courtlistener/issues/303
            citedGreaterThan.val('');
            citedLessThan.val('');
        }

        // Gather all form fields that are necessary
        var gathered = $();

        // If the user is switching tabs, then make sure their ordering is
        // valid for the tab they're headed to.
        var dropDown = $('#id_order_by');
        if (tabSwitch){
            var value;
            switch (dropDown.val()) {
                case 'dateFiled desc':
                    value = 'dateArgued desc';
                    break;
                case 'dateFiled asc':
                    value = 'dateArgued asc';
                    break;
                case 'dateArgued desc':
                    value = 'dateFiled desc';
                    break;
                case 'dateArgued asc':
                    value = 'dateFiled asc';
                    break;
                default:
                    value = 'score desc';
                    break;
            }
            var el = $('<input type="hidden" name="order_by" />').val(value);
            gathered = gathered.add(el);
        } else {
            // Not a tab switch; make sure to gather the element regardless.
            gathered = gathered.add(dropDown);
        }

        // Add the input boxes that aren't empty
        var selector = '.external-input[type=text]';
        gathered = gathered.add($(selector).filter(function () {
            return this.value !== '';
        }));

        // Add selected radio buttons
        gathered = gathered.add($('.external-input[type=radio]:checked'));

        // Add the court checkboxes that are selected as a single input element
        var checkedCourts = $('.court-checkbox:checked');
        if (checkedCourts.length !== $('.court-checkbox').length) {
            // Only do this if all courts aren't checked to keep URLs short.
            var values = [];
            for (var i = 0; i < checkedCourts.length; i++) {
                values.push(checkedCourts[i].id.split('_')[1]);
            }
            var courtString = values.join(' ');
            var el = jQuery('<input/>', {
                value: courtString,
                name: 'court'
            });
        }
        gathered = gathered.add(el);

        if ($('.status-checkbox:checked').length <= $('.status-checkbox').length) {
            // Add the status checkboxes that are selected
            gathered = gathered.add($('.status-checkbox:checked'));
        }

        // Remove any inputs that are direct children of the form. These are
        // pernicious leftovers caused by the evils of the back button.
        $('#search-form > input').remove();

        gathered.each(function () {
            // Make and submit a hidden input element for all gathered fields
            var el = $(this);
            $('<input type="hidden" name="' + el.attr('name') + '" />')
                .val(el.val())
                .appendTo('#search-form');
        });
        var path = '/?' + $('#search-form').serialize();
        return path;
    }

    //////////////
    // Homepage //
    //////////////
    function showAdvancedHomepage() {
        $('#homepage #advanced-search-starter').hide();
        $('#homepage #advanced-search-inputs').show('fast').removeClass('hidden');
        $('#main-query-box').addClass('wide');
        $('#id_q').focus();
    }
    $('#homepage #advanced-search-starter a').click(function (event) {
        event.preventDefault();
        showAdvancedHomepage();
    });
    $('#show-all-statuses').click(function (event) {
        event.preventDefault();
        $('.status-item').removeClass('hidden');
        $('#show-all-statuses').addClass('hidden');
    });

    ///////////////////////
    // Search submission //
    ///////////////////////
    $('#search-form, ' +
      '#sidebar-search-form, ' +
      '.search-page #court-picker-search-form').submit(function (e) {
        e.preventDefault();

        // Ensure that the correct value is set in the radio button (correct
        // is defined by the label that is .selected). This is needed because
        // the "wrong" value will be selected after a user presses the back
        // button in their browser.
        $('#type-switcher .selected input').prop('checked', true);

        document.location = makeSearchPath(false);
    });

    $('#id_order_by').change(function () {
        $('#search-form').submit();
    });

    $('#homepage #court-picker-search-form').submit(function(e){
        e.preventDefault();

        // Indicate the count of selected jurisdictions when switching to
        // advanced search page.
        $('#jurisdiction-count').text($(this).find('input:checked').length);
        $('#court-picker').modal('hide');
        showAdvancedHomepage();
        $('#jurisdiction-count').css({
            'background-color': 'yellow',
            'font-weight': 'bold'
        });
        setTimeout(function () {
            $('#jurisdiction-count').css({
                'background-color': 'transparent',
                'font-weight': 'normal'
            });
        }, 1500);
    });

    //////////////////////////
    // Source Tab Switching //
    //////////////////////////
    $('#type-switcher label:not(.selected) input[name=type]').click(function () {
        // Note that we can't do submit here, because that'd trigger a
        // switching of the the checked radio button, and nothing would happen.
        document.location = makeSearchPath(true);
    });

    ////////////
    // Slider //
    ////////////
    /*
    if (cited_gt.val() == "") {
        cited_gt.val(0);
    }
    if (cited_lt.val() == ""){
        cited_lt.val(20000);
    }
    $(function() {
        // Load up the slider in the UI
        $("#slider-range").slider({
            range: true,
            min: 0,
            max: 20000,
            step: 10,
            values: [cited_gt.val(),
                     cited_lt.val()],
            slide: function(event, ui) {
                // Update the text
                if (ui.values[0] == 0 && ui.values[1] == 20000){
                    $('#citation-count').text("(Any)");
                } else {
                    $("#citation-count").text( "(" + ui.values[0] + " - " + ui.values[1] + ")");
                }
                cited_gt.val(ui.values[0]);
                cited_lt.val(ui.values[1]);
            }
        });
    });
    if (cited_gt.val() != 0 || cited_lt.val() != 20000) {
        $('#citation-count').text("(" + $("#id_cited_gt").val() + " - " + $("#id_cited_lt").val() + ")")
    }
    */

    //////////////////
    // Court Picker //
    //////////////////
    function courtFilter () {
        var tabs = $('.tab-content'),
            checkboxes = tabs.find('.checkbox'),
            regex = new RegExp('\\b' + this.value, 'i'),
            matches = checkboxes.filter(function () {
                return regex.test($(this).find('label').text());
            });
        checkboxes.not(matches).find('input').prop('checked', false);
        matches.find('input').prop('checked', true);
    }

    $('#court-filter').keyup(courtFilter).change(courtFilter);

    // Check/clear the tab/everything
    $('#check-all').click(function() {
        $('#modal-court-picker .tab-pane input').prop('checked', true);
    });
    $('#clear-all').click(function () {
        $('#modal-court-picker .tab-pane input').prop('checked', false);
    });
    $('#check-current').click(function () {
        $('#modal-court-picker .tab-pane.active input').prop('checked', true);
    });
    $('#clear-current').click(function () {
        $('#modal-court-picker .tab-pane.active input').prop('checked', false);
    });

    ////////////////
    // Auto Focus //
    ////////////////
    $('.auto-focus:first').focus();

    //////////
    // Tour //
    //////////
    var tour = {
        id: 'feature-tour',
        showPrevButton: true,
        steps: [
            {//0
                target: '#id_q',
                placement: 'bottom',
                xOffset: 'center',
                arrowOffset: 'center',
                title: 'Welcome to the Tour!',
                content: 'Broad queries can be a great way to start a ' +
                    'research task. Our search box can understand ' +
                    'everything you might expect… terms, concepts, ' +
                    'citations, you name it.',
                // If the advanced page is already shown, we skip to step 2.
                onNext: function(){
                    if (!$('#advanced-search-starter').is(':visible')){
                        hopscotch.showStep(2);
                    }
                }
            },
            {//1
                target: '#advanced-search-starter',
                placement: 'bottom',
                xOffset: 'center',
                arrowOffset: 'center',
                nextOnTargetClick: true,
                title: 'More Power Please!',
                content: 'If you are the kind of person that wants more ' +
                    'power, you\'ll love the advanced search box. ' +
                    'Click on \"Advanced Search\" to turn it on.',
                onNext: function(){
                    showAdvancedHomepage();
                }
            },
            {//2
                target: '#extra-sidebar-fields',
                placement: 'top',
                arrowOffset: 'center',
                zindex: 10,
                title: 'Sophisticated Search',
                content: 'In the Advanced Search area, you can make ' +
                    'sophisticated searches against many fields. ' +
                    'Press \"Next\" and we\'ll make a query for you.',
                multipage: true,
                showPrevButton: false,
                onNext: function(){
                    window.location = '/?q=roe+v.+wade&order_by=score+desc&stat_Precedential=on&court=scotus';
                },
                delay: 250 // let advanced search area get exposed.
            },
            {//3
                // This step will be skipped if on a dev machine with no
                // results. Be not alarmed!
                target: document.querySelector('.search-page article'),
                placement: 'top',
                arrowOffset: 'center',
                title: 'Detailed Results',
                content: 'Here you can see the results for the query "Roe ' +
                    'v. Wade" sorted by relevance and filtered to only one ' +
                    'jurisdiction, the Supreme Court.',
                showPrevButton: false
            },
            {//4
                target: '#type-switcher',
                placement: 'bottom',
                arrowOffset: 'top',
                title: 'What are you Looking For?',
                content: 'By default you\'ll get opinion results, but use ' +
                    'this to research and listen to oral arguments instead.',
                showPrevButton: false
            },
            {//5
                target: '#create-alert-header',
                placement: 'top',
                arrowOffset: 'center',
                yOffset: '-75px',
                title: 'Make Alerts',
                content: '
Once you have placed a query, you can create ' +
                    'an alert. If there are ever any new results for your ' +
                    'query, CourtListener will send you an email to keep ' +
                    'you up to date.

Hit next to check out Roe ' +
                    'v. Wade.
',
                multipage: true,
                onNext: function(){
                    window.location = '/opinion/108713/roe-v-wade/';
                }
            },
            {//6
                target: '#cited-by',
                placement: 'top',
                arrowOffset: 'center',
                title: 'The Power of Citation',
                content: 'Roe v. Wade has been cited hundreds of times since ' +
                    'it was issued in 1973. Looking at these citations can ' +
                    'be a good way to see related cases.'
            },
            {//7
                target: '#authorities',
                placement: 'top',
                arrowOffset: 'center',
                title: 'Authorities',
                content: '
The Authorities section lists all of the ' +
                    'opinions that Roe v. Wade references. These can be ' +
                    'thought of as the principles it rests on.
' +
                    '
That\'s everything for now! Let us know if ' +
                    'you have any questions.
',
                onNext: function(){
                    hopscotch.endTour();
                }
            }
        ]
    };

    $('.tour-link').click(function (event) {
        event.preventDefault();
        var loc = location.pathname + location.search;
        if (loc !== '/') {
            sessionStorage.setItem('hopscotch.tour.state', 'feature-tour:0');
            window.location = '/';
        } else {
            hopscotch.startTour(tour, 0);
        }
    });
    // Start it automatically for certain steps.
    if (hopscotch.getState() === 'feature-tour:0') {
        hopscotch.startTour(tour);
    }
    if (hopscotch.getState() === 'feature-tour:3') {
        hopscotch.startTour(tour);
    }
    if (hopscotch.getState() === 'feature-tour:6') {
        hopscotch.startTour(tour);
    }
});

Modernizr.load({
    // Sets up HTML5 input placeholders in browsers that don't support them.
    test: Modernizr.placeholder,
    nope: '/static/js/placeholder-1.8.6.min.js',
    complete: function () {
        $('input, textarea').placeholder();
    }
});
<commit_msg>tour prevent prev page third<commit_after>/*eslint-env browser */
/*global $, Modernizr, hopscotch */

$(document).ready(function() {
    // 'use strict'; // uncomment later on after full cleanup
    var citedGreaterThan = $('#id_cited_gt');
    var citedLessThan = $('#id_cited_lt');

    function makeSearchPath(tabSwitch) {
        // Empty the sliders if they are both at their max
        if (citedGreaterThan.val() === 0 && citedLessThan.val() === 20000) {
        // see https://github.com/freelawproject/courtlistener/issues/303
            citedGreaterThan.val('');
            citedLessThan.val('');
        }

        // Gather all form fields that are necessary
        var gathered = $();

        // If the user is switching tabs, then make sure their ordering is
        // valid for the tab they're headed to.
        var dropDown = $('#id_order_by');
        if (tabSwitch){
            var value;
            switch (dropDown.val()) {
                case 'dateFiled desc':
                    value = 'dateArgued desc';
                    break;
                case 'dateFiled asc':
                    value = 'dateArgued asc';
                    break;
                case 'dateArgued desc':
                    value = 'dateFiled desc';
                    break;
                case 'dateArgued asc':
                    value = 'dateFiled asc';
                    break;
                default:
                    value = 'score desc';
                    break;
            }
            var el = $('<input type="hidden" name="order_by" />').val(value);
            gathered = gathered.add(el);
        } else {
            // Not a tab switch; make sure to gather the element regardless.
            gathered = gathered.add(dropDown);
        }

        // Add the input boxes that aren't empty
        var selector = '.external-input[type=text]';
        gathered = gathered.add($(selector).filter(function () {
            return this.value !== '';
        }));

        // Add selected radio buttons
        gathered = gathered.add($('.external-input[type=radio]:checked'));

        // Add the court checkboxes that are selected as a single input element
        var checkedCourts = $('.court-checkbox:checked');
        if (checkedCourts.length !== $('.court-checkbox').length) {
            // Only do this if all courts aren't checked to keep URLs short.
            var values = [];
            for (var i = 0; i < checkedCourts.length; i++) {
                values.push(checkedCourts[i].id.split('_')[1]);
            }
            var courtString = values.join(' ');
            var el = jQuery('<input/>', {
                value: courtString,
                name: 'court'
            });
        }
        gathered = gathered.add(el);

        if ($('.status-checkbox:checked').length <= $('.status-checkbox').length) {
            // Add the status checkboxes that are selected
            gathered = gathered.add($('.status-checkbox:checked'));
        }

        // Remove any inputs that are direct children of the form. These are
        // pernicious leftovers caused by the evils of the back button.
        $('#search-form > input').remove();

        gathered.each(function () {
            // Make and submit a hidden input element for all gathered fields
            var el = $(this);
            $('<input type="hidden" name="' + el.attr('name') + '" />')
                .val(el.val())
                .appendTo('#search-form');
        });
        var path = '/?' + $('#search-form').serialize();
        return path;
    }

    //////////////
    // Homepage //
    //////////////
    function showAdvancedHomepage() {
        $('#homepage #advanced-search-starter').hide();
        $('#homepage #advanced-search-inputs').show('fast').removeClass('hidden');
        $('#main-query-box').addClass('wide');
        $('#id_q').focus();
    }
    $('#homepage #advanced-search-starter a').click(function (event) {
        event.preventDefault();
        showAdvancedHomepage();
    });
    $('#show-all-statuses').click(function (event) {
        event.preventDefault();
        $('.status-item').removeClass('hidden');
        $('#show-all-statuses').addClass('hidden');
    });

    ///////////////////////
    // Search submission //
    ///////////////////////
    $('#search-form, ' +
      '#sidebar-search-form, ' +
      '.search-page #court-picker-search-form').submit(function (e) {
        e.preventDefault();

        // Ensure that the correct value is set in the radio button (correct
        // is defined by the label that is .selected). This is needed because
        // the "wrong" value will be selected after a user presses the back
        // button in their browser.
        $('#type-switcher .selected input').prop('checked', true);

        document.location = makeSearchPath(false);
    });

    $('#id_order_by').change(function () {
        $('#search-form').submit();
    });

    $('#homepage #court-picker-search-form').submit(function(e){
        e.preventDefault();

        // Indicate the count of selected jurisdictions when switching to
        // advanced search page.
        $('#jurisdiction-count').text($(this).find('input:checked').length);
        $('#court-picker').modal('hide');
        showAdvancedHomepage();
        $('#jurisdiction-count').css({
            'background-color': 'yellow',
            'font-weight': 'bold'
        });
        setTimeout(function () {
            $('#jurisdiction-count').css({
                'background-color': 'transparent',
                'font-weight': 'normal'
            });
        }, 1500);
    });

    //////////////////////////
    // Source Tab Switching //
    //////////////////////////
    $('#type-switcher label:not(.selected) input[name=type]').click(function () {
        // Note that we can't do submit here, because that'd trigger a
        // switching of the the checked radio button, and nothing would happen.
        document.location = makeSearchPath(true);
    });

    ////////////
    // Slider //
    ////////////
    /*
    if (cited_gt.val() == "") {
        cited_gt.val(0);
    }
    if (cited_lt.val() == ""){
        cited_lt.val(20000);
    }
    $(function() {
        // Load up the slider in the UI
        $("#slider-range").slider({
            range: true,
            min: 0,
            max: 20000,
            step: 10,
            values: [cited_gt.val(),
                     cited_lt.val()],
            slide: function(event, ui) {
                // Update the text
                if (ui.values[0] == 0 && ui.values[1] == 20000){
                    $('#citation-count').text("(Any)");
                } else {
                    $("#citation-count").text( "(" + ui.values[0] + " - " + ui.values[1] + ")");
                }
                cited_gt.val(ui.values[0]);
                cited_lt.val(ui.values[1]);
            }
        });
    });
    if (cited_gt.val() != 0 || cited_lt.val() != 20000) {
        $('#citation-count').text("(" + $("#id_cited_gt").val() + " - " + $("#id_cited_lt").val() + ")")
    }
    */

    //////////////////
    // Court Picker //
    //////////////////
    function courtFilter () {
        var tabs = $('.tab-content'),
            checkboxes = tabs.find('.checkbox'),
            regex = new RegExp('\\b' + this.value, 'i'),
            matches = checkboxes.filter(function () {
                return regex.test($(this).find('label').text());
            });
        checkboxes.not(matches).find('input').prop('checked', false);
        matches.find('input').prop('checked', true);
    }

    $('#court-filter').keyup(courtFilter).change(courtFilter);

    // Check/clear the tab/everything
    $('#check-all').click(function() {
        $('#modal-court-picker .tab-pane input').prop('checked', true);
    });
    $('#clear-all').click(function () {
        $('#modal-court-picker .tab-pane input').prop('checked', false);
    });
    $('#check-current').click(function () {
        $('#modal-court-picker .tab-pane.active input').prop('checked', true);
    });
    $('#clear-current').click(function () {
        $('#modal-court-picker .tab-pane.active input').prop('checked', false);
    });

    ////////////////
    // Auto Focus //
    ////////////////
    $('.auto-focus:first').focus();

    //////////
    // Tour //
    //////////
    var tour = {
        id: 'feature-tour',
        showPrevButton: true,
        steps: [
            {//0
                target: '#id_q',
                placement: 'bottom',
                xOffset: 'center',
                arrowOffset: 'center',
                title: 'Welcome to the Tour!',
                content: 'Broad queries can be a great way to start a ' +
                    'research task. Our search box can understand ' +
                    'everything you might expect… terms, concepts, ' +
                    'citations, you name it.',
                // If the advanced page is already shown, we skip to step 2.
                onNext: function(){
                    if (!$('#advanced-search-starter').is(':visible')){
                        hopscotch.showStep(2);
                    }
                }
            },
            {//1
                target: '#advanced-search-starter',
                placement: 'bottom',
                xOffset: 'center',
                arrowOffset: 'center',
                nextOnTargetClick: true,
                title: 'More Power Please!',
                content: 'If you are the kind of person that wants more ' +
                    'power, you\'ll love the advanced search box. ' +
                    'Click on \"Advanced Search\" to turn it on.',
                onNext: function(){
                    showAdvancedHomepage();
                }
            },
            {//2
                target: '#extra-sidebar-fields',
                placement: 'top',
                arrowOffset: 'center',
                zindex: 10,
                title: 'Sophisticated Search',
                content: 'In the Advanced Search area, you can make ' +
                    'sophisticated searches against many fields. ' +
                    'Press \"Next\" and we\'ll make a query for you.',
                multipage: true,
                showPrevButton: false,
                onNext: function(){
                    window.location = '/?q=roe+v.+wade&order_by=score+desc&stat_Precedential=on&court=scotus';
                },
                delay: 250 // let advanced search area get exposed.
            },
            {//3
                // This step will be skipped if on a dev machine with no
                // results. Be not alarmed!
                target: document.querySelector('.search-page article'),
                placement: 'top',
                arrowOffset: 'center',
                title: 'Detailed Results',
                content: 'Here you can see the results for the query "Roe ' +
                    'v. Wade" sorted by relevance and filtered to only one ' +
                    'jurisdiction, the Supreme Court.',
                showPrevButton: false
            },
            {//4
                target: '#type-switcher',
                placement: 'bottom',
                arrowOffset: 'top',
                title: 'What are you Looking For?',
                content: 'By default you\'ll get opinion results, but use ' +
                    'this to research and listen to oral arguments instead.',
                showPrevButton: false
            },
            {//5
                target: '#create-alert-header',
                placement: 'top',
                arrowOffset: 'center',
                yOffset: '-75px',
                title: 'Make Alerts',
                content: '
Once you have placed a query, you can create ' +
                    'an alert. If there are ever any new results for your ' +
                    'query, CourtListener will send you an email to keep ' +
                    'you up to date.

Hit next to check out Roe ' +
                    'v. Wade.
',
                multipage: true,
                onNext: function(){
                    window.location = '/opinion/108713/roe-v-wade/';
                }
            },
            {//6
                target: '#cited-by',
                placement: 'top',
                arrowOffset: 'center',
                title: 'The Power of Citation',
                content: 'Roe v. Wade has been cited hundreds of times since ' +
                    'it was issued in 1973. Looking at these citations can ' +
                    'be a good way to see related cases.'
            },
            {//7
                target: '#authorities',
                placement: 'top',
                arrowOffset: 'center',
                title: 'Authorities',
                showPrevButton: false,
                content: '
The Authorities section lists all of the ' +
                    'opinions that Roe v. Wade references. These can be ' +
                    'thought of as the principles it rests on.
' +
                    '
That\'s everything for now! Let us know if ' +
                    'you have any questions.
',
                onNext: function(){
                    hopscotch.endTour();
                }
            }
        ]
    };

    $('.tour-link').click(function (event) {
        event.preventDefault();
        var loc = location.pathname + location.search;
        if (loc !== '/') {
            sessionStorage.setItem('hopscotch.tour.state', 'feature-tour:0');
            window.location = '/';
        } else {
            hopscotch.startTour(tour, 0);
        }
    });
    // Start it automatically for certain steps.
    if (hopscotch.getState() === 'feature-tour:0') {
        hopscotch.startTour(tour);
    }
    if (hopscotch.getState() === 'feature-tour:3') {
        hopscotch.startTour(tour);
    }
    if (hopscotch.getState() === 'feature-tour:6') {
        hopscotch.startTour(tour);
    }
});

Modernizr.load({
    // Sets up HTML5 input placeholders in browsers that don't support them.
    test: Modernizr.placeholder,
    nope: '/static/js/placeholder-1.8.6.min.js',
    complete: function () {
        $('input, textarea').placeholder();
    }
});
<|endoftext|>


<commit_before>// Copyright (c) 2015, David Hwang
// Check LICENSE for fully detailed description.

var dates = [];
var revisions = [];
var matches = [];
var title = '';
var loadData = function() {
    findTimestamps();
    var max_date = new Date(Math.max.apply(null,dates));
    var min_date = new Date(Math.min.apply(null,dates));
    findRevisions(min_date, max_date, new Date(0));
}

var renderData = function() {
    var body_html = document.getElementById('bodyContent').innerHTML;
    for (var i = 0; i < matches.length; i++) {
        revision_id = matchToRevision(dates[i]);
        replacement = '' + matches[i] + '';
        body_html = body_html.replace(matches[i], replacement);
    };
    document.getElementById('bodyContent').innerHTML = body_html;
}

var findTimestamps = function() {
    // This should be doen with one regex string, each of these constructed with
    // a setFlags. Tried to do it with RegExp(re_str, 'mg') and RegExp(re_str, 'm').
    // Didn't work.
    var re = /(\d*):(\d*), (\d*) (\w*) (\d*) \(UTC\)$/mg;
    var re1 = /(\d*):(\d*), (\d*) (\w*) (\d*) \(UTC\)$/m;

    var body = document.getElementById('bodyContent');
    matches = body.textContent.match(re);
    if (matches) {
        for (var i = 0; i < matches.length; i++) {
            temp = matches[i].match(re1);
            hours = temp[1];
            minutes = temp[2];
            day = temp[3];
            month = temp[4];
            year = temp[5];
            datestring = month + ' ' + day + ', ' + year + ' ' + hours + ':' + minutes;
            dates.push(new Date(datestring));
        };
    }
}

var findRevisions = function(min_date, max_date, prev_last_date) {
    var min_date_str = dateToAPIstring(min_date);
    var max_date_str = dateToAPIstring(max_date);

    // get the title of the article for the URL
    // http://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(technical_restrictions)
    var parser = document.createElement('a');
    parser.href = document.URL;  // gets rid of '#' and everything after it
    potential_title = parser.pathname.split('Talk:')[1].split('/');
    title = potential_title[0];
    var i = 1;
    while(i < potential_title.length &&
        potential_title[i].search(/archive/i) == -1) {
        // The edge case this fails on is if the article title has the word 'archive'
        // in it after a forward slash.
        title += '/' + potential_title[i];
        i++;
    }

    var query_arr = ['http://en.wikipedia.org/w/api.php?action=query&prop=revisions',
                'titles=' + title,
                'rvstart=' + max_date_str,
                'rvend=' + min_date_str,
                'rvlimit=' + '500',
                'rvprop=' + 'timestamp|ids',
                'format=json'];

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            response = JSON.parse(xmlhttp.responseText)['query']['pages'];
            pageid = Object.keys(response)[0];
            revisions = revisions.concat(response[pageid]['revisions']);

            curr_last_date = APItimeToDate(revisions[revisions.length - 1]['timestamp'])
            if (curr_last_date.getTime() > min_date.getTime() &&
                curr_last_date.getTime() != prev_last_date.getTime()) {
                //when API for rvcontinue settles, this section should change to use it
                findRevisions(min_date, curr_last_date, curr_last_date);
            } else {
                for (var i = 0; i < revisions.length; i++) {
                    revisions[i]['date'] = APItimeToDate(revisions[i]['timestamp']);
                };
                renderData();
            }
        }
    }
    xmlhttp.open("GET", query_arr.join('&'), true);
    xmlhttp.send();
}

var matchToRevision = function(d) {
    // linear search
    var i = 0;
    while (i < revisions.length &&
            d.getTime() < revisions[i]['date'].getTime()) {
        i++;
    }

    if (i == revisions.length) {
        return revisions[revisions.length - 1]['parentid'];
    } else {
        return revisions[i]['revid'];
    }
}

var dateToAPIstring = function(d) {
    var year = d.getFullYear().toString();
    var month = pad(d.getMonth() + 1, 2);
    var date = pad(d.getDate(), 2);
    var hour = pad(d.getHours(), 2);
    var minute = pad(d.getMinutes(), 2);
    var second = pad(d.getSeconds(), 2);

    return year + month + date + hour + minute + second;
}

var APItimeToDate = function(timestamp_str) {
    return new Date(timestamp_str.replace('T', ' ').replace('Z', ''));
}

var pad = function(n, width, z) {
    z = z || '0';
    n = n + '';
    return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}

function escapeRegExp(str) {
    //http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

loadData();<commit_msg>changed the wikiAPI query to https<commit_after>// Copyright (c) 2015, David Hwang
// Check LICENSE for fully detailed description.

var dates = [];
var revisions = [];
var matches = [];
var title = '';
var loadData = function() {
    findTimestamps();
    var max_date = new Date(Math.max.apply(null,dates));
    var min_date = new Date(Math.min.apply(null,dates));
    findRevisions(min_date, max_date, new Date(0));
}

var renderData = function() {
    var body_html = document.getElementById('bodyContent').innerHTML;
    for (var i = 0; i < matches.length; i++) {
        revision_id = matchToRevision(dates[i]);
        replacement = '' + matches[i] + '';
        body_html = body_html.replace(matches[i], replacement);
    };
    document.getElementById('bodyContent').innerHTML = body_html;
}

var findTimestamps = function() {
    // This should be doen with one regex string, each of these constructed with
    // a setFlags. Tried to do it with RegExp(re_str, 'mg') and RegExp(re_str, 'm').
    // Didn't work.
    var re = /(\d*):(\d*), (\d*) (\w*) (\d*) \(UTC\)$/mg;
    var re1 = /(\d*):(\d*), (\d*) (\w*) (\d*) \(UTC\)$/m;

    var body = document.getElementById('bodyContent');
    matches = body.textContent.match(re);
    if (matches) {
        for (var i = 0; i < matches.length; i++) {
            temp = matches[i].match(re1);
            hours = temp[1];
            minutes = temp[2];
            day = temp[3];
            month = temp[4];
            year = temp[5];
            datestring = month + ' ' + day + ', ' + year + ' ' + hours + ':' + minutes;
            dates.push(new Date(datestring));
        };
    }
}

var findRevisions = function(min_date, max_date, prev_last_date) {
    var min_date_str = dateToAPIstring(min_date);
    var max_date_str = dateToAPIstring(max_date);

    // get the title of the article for the URL
    // http://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(technical_restrictions)
    var parser = document.createElement('a');
    parser.href = document.URL;  // gets rid of '#' and everything after it
    potential_title = parser.pathname.split('Talk:')[1].split('/');
    title = potential_title[0];
    var i = 1;
    while(i < potential_title.length &&
        potential_title[i].search(/archive/i) == -1) {
        // The edge case this fails on is if the article title has the word 'archive'
        // in it after a forward slash.
        title += '/' + potential_title[i];
        i++;
    }

    var query_arr = ['https://en.wikipedia.org/w/api.php?action=query&prop=revisions',
                'titles=' + title,
                'rvstart=' + max_date_str,
                'rvend=' + min_date_str,
                'rvlimit=' + '500',
                'rvprop=' + 'timestamp|ids',
                'format=json'];

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            response = JSON.parse(xmlhttp.responseText)['query']['pages'];
            pageid = Object.keys(response)[0];
            revisions = revisions.concat(response[pageid]['revisions']);

            curr_last_date = APItimeToDate(revisions[revisions.length - 1]['timestamp'])
            if (curr_last_date.getTime() > min_date.getTime() &&
                curr_last_date.getTime() != prev_last_date.getTime()) {
                //when API for rvcontinue settles, this section should change to use it
                findRevisions(min_date, curr_last_date, curr_last_date);
            } else {
                for (var i = 0; i < revisions.length; i++) {
                    revisions[i]['date'] = APItimeToDate(revisions[i]['timestamp']);
                };
                renderData();
            }
        }
    }
    xmlhttp.open("GET", query_arr.join('&'), true);
    xmlhttp.send();
}

var matchToRevision = function(d) {
    // linear search
    var i = 0;
    while (i < revisions.length &&
            d.getTime() < revisions[i]['date'].getTime()) {
        i++;
    }

    if (i == revisions.length) {
        return revisions[revisions.length - 1]['parentid'];
    } else {
        return revisions[i]['revid'];
    }
}

var dateToAPIstring = function(d) {
    var year = d.getFullYear().toString();
    var month = pad(d.getMonth() + 1, 2);
    var date = pad(d.getDate(), 2);
    var hour = pad(d.getHours(), 2);
    var minute = pad(d.getMinutes(), 2);
    var second = pad(d.getSeconds(), 2);

    return year + month + date + hour + minute + second;
}

var APItimeToDate = function(timestamp_str) {
    return new Date(timestamp_str.replace('T', ' ').replace('Z', ''));
}

var pad = function(n, width, z) {
    z = z || '0';
    n = n + '';
    return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}

function escapeRegExp(str) {
    //http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

loadData();<|endoftext|>


<commit_before>'use strict';

angular.module('app')
    .constant("baseURL", "http://0.0.0.0:5000")
    // A service to share 'post' object between controllers
    .service('sharedPost', function () {
        var post = this;
    })
    .service('allPosts', ['$resource', 'baseURL', function ($resource, baseURL) {
        this.getPosts = function () {
            return $resource(baseURL + '/blog/api/posts/:id', {}, {
                query: {
                    method: 'GET',
                    isArray: true
                }
            });
        }
    }])
    .service('postUpload', ['$http', function ($http) {
        this.newPost = function (file, data) {
            var fd = new FormData();
            fd.append('file', file);
            fd.append('post', JSON.stringify(data));
            return $http.post("http://0.0.0.0:5000/blog/api/posts/new", fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            })
        }
    }])
    .service('editPost', ['$http', function ($http) {
        this.editPost = function (file, data) {
            var fd = new FormData();
            fd.append('file', file);
            fd.append('post', JSON.stringify(data));
            return $http.post("http://0.0.0.0:5000/blog/api/posts/" + data.id + "/edit", fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            })
        }
    }])
    // Reusable service to ask user for confirmation and delete a post
    .service('deletePost', ['$http', '$mdDialog', function ($http, $mdDialog) {
        this.delete = function (ev, postId) {
            var confirm = $mdDialog.confirm()
                .title('Are you sure you want to delete this post?')
                .textContent('This action cannot be undone.')
                .ariaLabel('Confirm post deletion')
                .targetEvent(ev)
                .ok('Delete')
                .cancel('Cancel');
            return $mdDialog.show(confirm).then(function () {
                return $http.post("http://0.0.0.0:5000/blog/api/posts/" + postId + "/delete", {})
                    .then(function success() {
                            console.log('Deleted post with id', postId);
                        },
                        function error(response) {
                            console.log('Could not delete', response);
                        })
            });
        }
    }])
    .service('createUser', ['$http', function ($http) {
        this.newUser = function (file, user) {
            var fd = new FormData();
            fd.append('file', file);
            fd.append('user', JSON.stringify(user));
            return $http.post("http://0.0.0.0:5000/blog/api/users", fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            });
        };
        this.loginUser = function (user) {
            return $http.post("http://0.0.0.0:5000/login", user);
        }
    }])
    .service('logoutUser', ['$http', function ($http) {
        this.logout = function () {
            return $http.post("http://0.0.0.0:5000/logout", {});
        }
    }])
    .service('userPosts', ['$http', function ($http) {
        this.getPosts = function (user_id) {
            return $http.get("http://0.0.0.0:5000/blog/api/users/" + user_id + "/posts")
        }
    }])
    .service('imgPreview', function () {
        this.preview = function (element, scope) {
            var reader = new FileReader();
            reader.onload = function (event) {
                scope.imageSrc = event.target.result;
                scope.$apply();
            };
            // when the file is read it triggers the onload event above.
            reader.readAsDataURL(element.files[0]);
        };
        this.activateUpload = function (id) {
            document.getElementById(id).click();
        }
    })
    .service('updateUser', ['$http', function ($http) {
        this.update = function (file, user) {
            var fd = new FormData();
            fd.append('file', file);
            fd.append('user', JSON.stringify(user));
            return $http.post("http://0.0.0.0:5000/blog/api/users/edit", fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            });
        };
    }])
    .service('favoritePost', ['$http', '$cookies', function ($http, $cookies) {
        this.favorite = function (post) {
            return $http.post("/blog/api/posts/" + post.id, {});
        };

        /* Check if the logged in user has favorited the post and add red color to fav icon if yes */
        this.checkFav = function (post) {
            var user = $cookies.getObject('current_user');
            if (user) {
                var filtered = post.favorited_by.filter(function (el) {
                    return el.username == user.username;
                });
                if (filtered.length) {
                    post.favClass = 'red';
                } else {
                    post.favClass = '';
                }
            }
        }
    }])
    .service('addComment', ['$http', function ($http) {
        this.add = function (comment, postId) {
            return $http.post("http://0.0.0.0:5000/blog/api/posts/" + postId + "/comments/new", JSON.stringify(comment))
        }
    }])
    .service('goTo', ['$anchorScroll', '$location', function ($anchorScroll, $location) {
        this.goTo = function (post, el) {
            var selector = document.getElementById(el);
            // If we are on post detail page, scroll to comments
            if (selector) {
                $location.hash(el);
                $anchorScroll();
                // Else go to post detail page and jump to comments
            } else {
                $location.path('/posts/' + post.id).hash(el);
            }
        }
    }])
;

<commit_msg>Remove base url from services<commit_after>'use strict';

angular.module('app')
    .constant("baseURL", "http://0.0.0.0:5000")
    // A service to share 'post' object between controllers
    .service('sharedPost', function () {
        var post = this;
    })
    .service('allPosts', ['$resource', 'baseURL', function ($resource, baseURL) {
        this.getPosts = function () {
            return $resource(baseURL + '/blog/api/posts/:id', {}, {
                query: {
                    method: 'GET',
                    isArray: true
                }
            });
        }
    }])
    .service('postUpload', ['$http', function ($http) {
        this.newPost = function (file, data) {
            var fd = new FormData();
            fd.append('file', file);
            fd.append('post', JSON.stringify(data));
            return $http.post("/blog/api/posts/new", fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            })
        }
    }])
    .service('editPost', ['$http', function ($http) {
        this.editPost = function (file, data) {
            var fd = new FormData();
            fd.append('file', file);
            fd.append('post', JSON.stringify(data));
            return $http.post("/blog/api/posts/" + data.id + "/edit", fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            })
        }
    }])
    // Reusable service to ask user for confirmation and delete a post
    .service('deletePost', ['$http', '$mdDialog', function ($http, $mdDialog) {
        this.delete = function (ev, postId) {
            var confirm = $mdDialog.confirm()
                .title('Are you sure you want to delete this post?')
                .textContent('This action cannot be undone.')
                .ariaLabel('Confirm post deletion')
                .targetEvent(ev)
                .ok('Delete')
                .cancel('Cancel');
            return $mdDialog.show(confirm).then(function () {
                return $http.post("/blog/api/posts/" + postId + "/delete", {})
                    .then(function success() {
                            console.log('Deleted post with id', postId);
                        },
                        function error(response) {
                            console.log('Could not delete', response);
                        })
            });
        }
    }])
    .service('createUser', ['$http', function ($http) {
        this.newUser = function (file, user) {
            var fd = new FormData();
            fd.append('file', file);
            fd.append('user', JSON.stringify(user));
            return $http.post("/blog/api/users", fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            });
        };
        this.loginUser = function (user) {
            return $http.post("/login", user);
        }
    }])
    .service('logoutUser', ['$http', function ($http) {
        this.logout = function () {
            return $http.post("/logout", {});
        }
    }])
    .service('userPosts', ['$http', function ($http) {
        this.getPosts = function (user_id) {
            return $http.get("/blog/api/users/" + user_id + "/posts")
        }
    }])
    .service('imgPreview', function () {
        this.preview = function (element, scope) {
            var reader = new FileReader();
            reader.onload = function (event) {
                scope.imageSrc = event.target.result;
                scope.$apply();
            };
            // when the file is read it triggers the onload event above.
            reader.readAsDataURL(element.files[0]);
        };
        this.activateUpload = function (id) {
            document.getElementById(id).click();
        }
    })
    .service('updateUser', ['$http', function ($http) {
        this.update = function (file, user) {
            var fd = new FormData();
            fd.append('file', file);
            fd.append('user', JSON.stringify(user));
            return $http.post("/blog/api/users/edit", fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            });
        };
    }])
    .service('favoritePost', ['$http', '$cookies', function ($http, $cookies) {
        this.favorite = function (post) {
            return $http.post("/blog/api/posts/" + post.id, {});
        };

        /* Check if the logged in user has favorited the post and add red color to fav icon if yes */
        this.checkFav = function (post) {
            var user = $cookies.getObject('current_user');
            if (user) {
                var filtered = post.favorited_by.filter(function (el) {
                    return el.username == user.username;
                });
                if (filtered.length) {
                    post.favClass = 'red';
                } else {
                    post.favClass = '';
                }
            }
        }
    }])
    .service('addComment', ['$http', function ($http) {
        this.add = function (comment, postId) {
            return $http.post("/blog/api/posts/" + postId + "/comments/new", JSON.stringify(comment))
        }
    }])
    .service('goTo', ['$anchorScroll', '$location', function ($anchorScroll, $location) {
        this.goTo = function (post, el) {
            var selector = document.getElementById(el);
            // If we are on post detail page, scroll to comments
            if (selector) {
                $location.hash(el);
                $anchorScroll();
                // Else go to post detail page and jump to comments
            } else {
                $location.path('/posts/' + post.id).hash(el);
            }
        }
    }])
;

<|endoftext|>


<commit_before>/* global describe expect it */
import moment from 'moment-timezone';

import { dayCount } from './date-utils';

const PST = 'America/Los_Angeles';

describe('date utils', () => {
  describe('dayCount', () => {
    it('should return 1 day given epochs of the same day', () => {
      expect(
        dayCount([
          moment.utc('8/3/2015 3:00', 'M/D/YYYY H:mm').valueOf(),
          moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf()
        ])
      ).toEqual(1);
    });

    it('should return 1 day given same epochs', () => {
      expect(
        dayCount([
          moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(),
          moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf()
        ])
      ).toEqual(1);
    });

    it('should return 2 days when there is a 24 hours difference', () => {});
    expect(
      dayCount([
        moment.utc('8/4/2015 2:00', 'M/D/YYYY H:mm').valueOf(),
        moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf()
      ])
    ).toEqual(2);

    it(
      'should return 2 days when the diff is less than 24h but ' +
        'different in UTC',
      () => {}
    );
    expect(
      dayCount([
        moment.utc('8/4/2015 1:00', 'M/D/YYYY H:mm').valueOf(),
        moment.utc('8/3/2015 23:00', 'M/D/YYYY H:mm').valueOf()
      ])
    ).toEqual(2);

    it(
      'should return 1 day when the diff is less than 24h ' +
        'and days are different in UTC, but given PST',
      () => {}
    );
    expect(
      dayCount(
        [
          moment.utc('8/4/2015 1:00', 'M/D/YYYY H:mm').valueOf(),
          moment.utc('8/3/2015 23:00', 'M/D/YYYY H:mm').valueOf()
        ],
        PST
      )
    ).toEqual(1);

    it('should return correct count when there is very big period', () => {});
    expect(
      dayCount([
        moment.utc('10/27/2015 1:00', 'M/D/YYYY H:mm').valueOf(),
        moment.utc('5/12/1982 1:00', 'M/D/YYYY H:mm').valueOf()
      ])
    ).toEqual(12222);

    it(
      'should return 2 days when there is a 24 hours difference ' +
        'between dates given `undefined` timezone',
      () => {}
    );
    expect(
      dayCount([
        moment.utc('8/4/2015 2:00', 'M/D/YYYY H:mm').valueOf(),
        moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf()
      ])
    ).toEqual(2);
  });
});
<commit_msg>fix: Fix up my broken test reformatting<commit_after>/* global describe expect it */
import moment from 'moment-timezone';

import { dayCount } from './date-utils';

const PST = 'America/Los_Angeles';

describe('date utils', () => {
  describe('dayCount', () => {
    it('should return 1 day given epochs of the same day', () => {
      expect(
        dayCount([
          moment.utc('8/3/2015 3:00', 'M/D/YYYY H:mm').valueOf(),
          moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf()
        ])
      ).toEqual(1);
    });

    it('should return 1 day given same epochs', () => {
      expect(
        dayCount([
          moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(),
          moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf()
        ])
      ).toEqual(1);
    });

    it('should return 2 days when there is a 24 hours difference', () => {
      expect(
        dayCount([
          moment.utc('8/4/2015 2:00', 'M/D/YYYY H:mm').valueOf(),
          moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf()
        ])
      ).toEqual(2);
    });

    it(
      'should return 2 days when the diff is less than 24h but ' +
        'different in UTC',
      () => {
        expect(
          dayCount([
            moment.utc('8/4/2015 1:00', 'M/D/YYYY H:mm').valueOf(),
            moment.utc('8/3/2015 23:00', 'M/D/YYYY H:mm').valueOf()
          ])
        ).toEqual(2);
      }
    );

    it(
      'should return 1 day when the diff is less than 24h ' +
        'and days are different in UTC, but given PST',
      () => {
        expect(
          dayCount(
            [
              moment.utc('8/4/2015 1:00', 'M/D/YYYY H:mm').valueOf(),
              moment.utc('8/3/2015 23:00', 'M/D/YYYY H:mm').valueOf()
            ],
            PST
          )
        ).toEqual(1);
      }
    );

    it('should return correct count when there is very big period', () => {
      expect(
        dayCount([
          moment.utc('10/27/2015 1:00', 'M/D/YYYY H:mm').valueOf(),
          moment.utc('5/12/1982 1:00', 'M/D/YYYY H:mm').valueOf()
        ])
      ).toEqual(12222);
    });

    it(
      'should return 2 days when there is a 24 hours difference ' +
        'between dates given `undefined` timezone',
      () => {
        expect(
          dayCount([
            moment.utc('8/4/2015 2:00', 'M/D/YYYY H:mm').valueOf(),
            moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf()
          ])
        ).toEqual(2);
      }
    );
  });
});
<|endoftext|>


<commit_before>'use strict';

const EventEmitter = require('events');
const Ajv = require('ajv/dist/2019').default;
const formats = require('ajv-formats').default;
const { omit } = require('lodash');
const ValidationError = require('../errors/ValidationError');

/** @typedef {import('../models').User} User */

/**
 * Extensible configuration store.
 *
 * The config store contains named groups of settings. Each setting group is
 * stored in its own MongoDB Document. Groups have associated JSON Schemas to
 * check that the configuration is correct.
 */
class ConfigStore {
  #uw;

  #ajv;

  #emitter = new EventEmitter();

  /** @type {Map<string, import('ajv').ValidateFunction<unknown>>} */
  #registry = new Map();

  /**
   * @param {import('../Uwave')} uw
   */
  constructor(uw) {
    this.#uw = uw;
    this.#ajv = new Ajv({
      useDefaults: true,
      // Allow unknown keywords (`uw:xyz`)
      strict: false,
      strictTypes: true,
    });
    formats(this.#ajv);
    /* eslint-disable global-require */
    this.#ajv.addMetaSchema(require('ajv/dist/refs/json-schema-draft-07.json'));
    this.#ajv.addSchema(require('../schemas/definitions.json'));
    /* eslint-enable global-require */

    this.on = this.#emitter.on.bind(this);
    this.off = this.#emitter.removeListener.bind(this);
    this.emit = this.#emitter.emit.bind(this);
  }

  /**
   * @param {string} key
   * @param {object} values
   * @private
   */
  async save(key, values) {
    const { Config } = this.#uw.models;

    await Config.findByIdAndUpdate(
      key,
      { _id: key, ...values },
      { upsert: true },
    );
  }

  /**
   * @param {string} key
   * @returns {Promise<object|null>}
   * @private
   */
  async load(key) {
    const { Config } = this.#uw.models;

    const model = await Config.findById(key);
    if (!model) return null;

    const doc = model.toJSON();
    return omit(doc, '_id');
  }

  /**
   * Add a config group.
   *
   * @param {string} key - The name of the config group.
   * @param {import('ajv').SchemaObject} schema - The JSON schema that the settings must
   *     follow.
   * @public
   */
  register(key, schema) {
    this.#registry.set(key, this.#ajv.compile(schema));
  }

  /**
   * Get the current settings for a config group.
   *
   * @param {string} key
   * @returns {Promise<undefined | object>} - `undefined` if the config group named `key` does not
   *     exist. An object containing current settings otherwise.
   * @public
   */
  async get(key) {
    const validate = this.#registry.get(key);
    if (!validate) return undefined;

    const config = (await this.load(key)) || {};
    // Allowed to fail--just fills in defaults
    validate(config);

    return config;
  }

  /**
   * Update settings for a config group. Optionally specify the user who is updating the settings.
   *
   * Rejects if the settings do not follow the schema for the config group.
   *
   * @param {string} key
   * @param {object} settings
   * @param {{ user?: User }} [options]
   * @public
   */
  async set(key, settings, { user } = {}) {
    const validate = this.#registry.get(key);
    if (validate) {
      if (!validate(settings)) {
        throw new ValidationError(validate.errors, this.#ajv);
      }
    }

    await this.save(key, settings);

    this.emit('set', key, settings, user);
  }

  /**
   * Get *all* settings.
   *
   * @returns {Promise<{ [key: string]: object }>}
   */
  async getAllConfig() {
    const { Config } = this.#uw.models;

    const all = await Config.find();
    const object = Object.create(null);
    for (const [key, validate] of this.#registry.entries()) {
      const model = all.find((m) => m._id === key);
      object[key] = model ? model.toJSON() : {};
      delete object[key]._id;
      validate(object[key]);
    }
    return object;
  }

  /**
   * @returns {import('ajv').SchemaObject}
   */
  getSchema() {
    const properties = Object.create(null);
    const required = [];
    for (const [key, validate] of this.#registry.entries()) {
      properties[key] = validate.schema;
      required.push(key);
    }

    return {
      type: 'object',
      properties,
      required,
    };
  }
}

/**
 * @param {import('../Uwave')} uw
 */
async function configStorePlugin(uw) {
  uw.config = new ConfigStore(uw);
  uw.config.on('set', (key, value, user) => {
    uw.publish('configStore:update', {
      key,
      user: user ? user.id : null,
    });
  });
}

module.exports = configStorePlugin;
module.exports.ConfigStore = ConfigStore;
<commit_msg>fix EventEmitter binding in config plugin<commit_after>'use strict';

const EventEmitter = require('events');
const Ajv = require('ajv/dist/2019').default;
const formats = require('ajv-formats').default;
const { omit } = require('lodash');
const ValidationError = require('../errors/ValidationError');

/** @typedef {import('../models').User} User */

/**
 * Extensible configuration store.
 *
 * The config store contains named groups of settings. Each setting group is
 * stored in its own MongoDB Document. Groups have associated JSON Schemas to
 * check that the configuration is correct.
 */
class ConfigStore {
  #uw;

  #ajv;

  /** @type {Map<string, import('ajv').ValidateFunction<unknown>>} */
  #registry = new Map();

  /**
   * @param {import('../Uwave')} uw
   */
  constructor(uw) {
    this.#uw = uw;
    this.#ajv = new Ajv({
      useDefaults: true,
      // Allow unknown keywords (`uw:xyz`)
      strict: false,
      strictTypes: true,
    });
    formats(this.#ajv);
    /* eslint-disable global-require */
    this.#ajv.addMetaSchema(require('ajv/dist/refs/json-schema-draft-07.json'));
    this.#ajv.addSchema(require('../schemas/definitions.json'));
    /* eslint-enable global-require */

    const emitter = new EventEmitter();
    this.on = emitter.on.bind(emitter);
    this.off = emitter.removeListener.bind(emitter);
    this.emit = emitter.emit.bind(emitter);
  }

  /**
   * @param {string} key
   * @param {object} values
   * @private
   */
  async save(key, values) {
    const { Config } = this.#uw.models;

    await Config.findByIdAndUpdate(
      key,
      { _id: key, ...values },
      { upsert: true },
    );
  }

  /**
   * @param {string} key
   * @returns {Promise<object|null>}
   * @private
   */
  async load(key) {
    const { Config } = this.#uw.models;

    const model = await Config.findById(key);
    if (!model) return null;

    const doc = model.toJSON();
    return omit(doc, '_id');
  }

  /**
   * Add a config group.
   *
   * @param {string} key - The name of the config group.
   * @param {import('ajv').SchemaObject} schema - The JSON schema that the settings must
   *     follow.
   * @public
   */
  register(key, schema) {
    this.#registry.set(key, this.#ajv.compile(schema));
  }

  /**
   * Get the current settings for a config group.
   *
   * @param {string} key
   * @returns {Promise<undefined | object>} - `undefined` if the config group named `key` does not
   *     exist. An object containing current settings otherwise.
   * @public
   */
  async get(key) {
    const validate = this.#registry.get(key);
    if (!validate) return undefined;

    const config = (await this.load(key)) || {};
    // Allowed to fail--just fills in defaults
    validate(config);

    return config;
  }

  /**
   * Update settings for a config group. Optionally specify the user who is updating the settings.
   *
   * Rejects if the settings do not follow the schema for the config group.
   *
   * @param {string} key
   * @param {object} settings
   * @param {{ user?: User }} [options]
   * @public
   */
  async set(key, settings, { user } = {}) {
    const validate = this.#registry.get(key);
    if (validate) {
      if (!validate(settings)) {
        throw new ValidationError(validate.errors, this.#ajv);
      }
    }

    await this.save(key, settings);

    this.emit('set', key, settings, user);
  }

  /**
   * Get *all* settings.
   *
   * @returns {Promise<{ [key: string]: object }>}
   */
  async getAllConfig() {
    const { Config } = this.#uw.models;

    const all = await Config.find();
    const object = Object.create(null);
    for (const [key, validate] of this.#registry.entries()) {
      const model = all.find((m) => m._id === key);
      object[key] = model ? model.toJSON() : {};
      delete object[key]._id;
      validate(object[key]);
    }
    return object;
  }

  /**
   * @returns {import('ajv').SchemaObject}
   */
  getSchema() {
    const properties = Object.create(null);
    const required = [];
    for (const [key, validate] of this.#registry.entries()) {
      properties[key] = validate.schema;
      required.push(key);
    }

    return {
      type: 'object',
      properties,
      required,
    };
  }
}

/**
 * @param {import('../Uwave')} uw
 */
async function configStorePlugin(uw) {
  uw.config = new ConfigStore(uw);
  uw.config.on('set', (key, value, user) => {
    uw.publish('configStore:update', {
      key,
      user: user ? user.id : null,
    });
  });
}

module.exports = configStorePlugin;
module.exports.ConfigStore = ConfigStore;
<|endoftext|>


<commit_before>// ==UserScript==
// @name          Garmin Connect Course export
// @namespace     http://schlueters.de/garmin-connect-course-export
// @description   Export courses from the Garmin Connect web site
// @include       https://connect.garmin.com/mincourse/*
// @version       1.0
// @updateURL     https://github.com/johannes/garmin-connect-gmscripts/raw/master/garmin-connect-course-export.user.js
// ==/UserScript==

(function() {
  var formats = {
    JSON: 'http://connect.garmin.com/proxy/course-service-1.0/json/course/',
    FIT: 'http://connect.garmin.com/proxy/course-service-1.0/fit/course/',
    TCX: 'http://connect.garmin.com/proxy/course-service-1.0/tcx/course/',
    GPX: 'http://connect.garmin.com/proxy/course-service-1.0/gpx/course/',
    GPOLYLINE: 'http://connect.garmin.com/proxy/course-service-1.0/gpolyline/course/'
  };

  var id = window.location.href.replace('https://connect.garmin.com/mincourse/', '');
  var ex = '';
  for (var f in formats) {
    ex += ''+f+' ';
  }
  $('viewModeItems').innerHTML += ex;
})();
<commit_msg>Added file extension and upgraded the UI<commit_after>// ==UserScript==
// @name          Garmin Connect Course export
// @namespace     http://schlueters.de/garmin-connect-course-export
// @description   Export courses from the Garmin Connect web site
// @include       https://connect.garmin.com/mincourse/*
// @version       1.0
// @updateURL     https://github.com/johannes/garmin-connect-gmscripts/raw/master/garmin-connect-course-export.user.js
// ==/UserScript==

(function() {
    var formats = [
        {type: 'JSON', url: 'http://connect.garmin.com/proxy/course-service-1.0/json/course/',ext: '.json'},
        {type: 'FIT', url: 'http://connect.garmin.com/proxy/course-service-1.0/fit/course/',ext: '.fit'},
        {type: 'TCX', url: 'http://connect.garmin.com/proxy/course-service-1.0/tcx/course/',ext: '.tcx'},
        {type: 'GPOLYLINE', url: 'http://connect.garmin.com/proxy/course-service-1.0/gpolyline/course/',ext: '.kml'},
        {type: 'GPX', url: 'http://connect.garmin.com/proxy/course-service-1.0/gpx/course/',ext: '.gpx'}
    ];

  var id = window.location.href.replace('https://connect.garmin.com/mincourse/', '');
  var ex = '
';
  for (var i=0;i<formats.length; i++) {
    ex += ''+formats[i].type+' ';
  }
    ex += '
(Right click + Save link as)

';
  $('viewModeItems').innerHTML += ex;
})();
<|endoftext|>


<commit_before>// Luke Miles, April 2017
// Algorithms for altruistic hedonic games

// ** Score Functions **

function FOScore(graph, node, coalition) {
  // node's friend oriented score of coalition
  var n = Object.keys(graph).length;
  var friends = graph[node];
  return coalition.map(node2 =>
    (node2 == node ? 0 : (friends.includes(node2) ? n : -1))).sum();
}

function friendAverage(graph, node, coalition) {
  // the average happiness of a node's friends in a given coalition
  var total = 0;
  var count = 0;
  for (const friend of coalition.intersect(graph[node])) {
    total += FOScore(graph, friend, coalition);
    count++;
  }
  return (count > 0 ? total / friendCount : 0);
}

function FOSFScore(graph, node, coalition) {
  // node's selfish-first score of coalition
  var n = Object.keys(graph).length;
  var myScore = FOScore(graph, node, coalition);
  var friendsScore = friendAverage(graph, node, coalition);
  return Math.pow(n, 5) * myScore + friendsScore;
}

function FOALScore(graph, node, coalition) {
  // node's altruistic treatment score of coalition
  var n = Object.keys(graph).length;
  var myScore = FOScore(graph, node, coalition);
  var friendsScore = friendAverage(graph, node, coalition);
  return myScore + Math.pow(n, 5) * friendsScore;
}

function FOEQScore(graph, node, coalition) {
  // node's equal treatment score of coalition
  var n = Object.keys(graph).length;
  var total = FOScore(graph, node, coalition);
  var count = 1;
  for (const friend of coalition.intersect(graph[node])) {
    total += FOScore(graph, friend, coalition)
    count++;
  }
  return total/count;
}

// ** Stability Concepts **

function isIndividuallyRational(graph, partition) {
  // Is every node in every coalition in partition happier in its current coalition than it would be alone?
  // If not, return a counter-example.
  var nodes = Object.keys(graph);
  for (const coalition of partition)
    for (const node of coalition)
      if (!isAcceptable(graph, node, coalition))
        return [false, node];
  return [true, null];
}

function isAcceptable(graph, node, coalition) {
  // Is this coalition acceptable to node?
  return coalition.equals([node]) || graph[node].intersect(coalition).length > 0;
}

// The next three stability concepts repeatedly use the same tests, so it is better to define them all at once
[isNashStable, isIndividuallyStable, isContractuallyIndividuallyStable] =
  (function(){
    // four different tests used in stability:
    // G=graph, P=partition, n=node, C1=homeCoalition, C2=otherCoalition, SF=scoreFunc
    // Is it actually a different coalition?
    var test0 = (G, P, n, C1, C2, SF) => !C1.equals(C2);
    // Do I (the node) want to leave my home?
    var test1 = (G, P, n, C1, C2, SF) => SF(G, n, C2) > SF(G, n, C1);
    // Is the new coalition okay with having me?
    var test2 = (G, P, n, C1, C2, SF) => C2.every(n2 => SF(G, n2, C2.concat([n])) >= SF(G, n2, C2));
    // Is my home okay with me leaving?
    var test3 = (G, P, n, C1, C2, SF) => (C1wn => C1wn.every(n1 => SF(G,n1,C1wn) >= SF(G,n1,C1)))(C1.filter(n1=>n1!=n));

    // check if any possible vertex with a home coalition and a new coalition passes every test
    var makeCheckFunc = tests =>
      function(G, P, SF) {
        for (const C1 of P) for (const n of C1) for (const C2 of P.concat([[]]))
          if (tests.every(test => test(G, P, n, C1, C2, SF))) // if this situation passes every test
            return [false, n, C2];
        return [true, null, null];
      }

    return [
      makeCheckFunc([test0, test1]),
      makeCheckFunc([test0, test1, test2]),
      makeCheckFunc([test0, test1, test2, test3])
    ];
  })();

function isCoreStable(graph, partition, scoreFunc) {
  // Is this partition core-stable?
  // If not, give a counter-example.
  var scores = {};
  for (const coalition of partition)
    for (const node of coalition)
      scores[node] = scoreFunc(graph, node, coalition);
  var nodes = Object.keys(graph);
  for (const coalition of nodes.powerset()) {
    if (coalition.length==0)
      continue;
    var newScores = {}
    for (const node of coalition)
      newScores[node] = scoreFunc(graph, node, coalition)
    if (coalition.every(node => newScores[node] >= scores[node]) &&
      coalition.some(node => newScores[node] > scores[node]))
      return [false, coalition];
  }
  return [true, null];
}

function isStrictlyPopular(graph, partition, scoreFunc) {
  // Is this partition stictly popular?
  // If not, give a counter-example.
  var currentScores = {}
  for (const coalition of partition)
    for (const node of coalition)
      currentScores[node] = scoreFunc(graph, node, coalition);
  var nodes = Object.keys(graph)
  var n = nodes.length;
  var otherScores = {}; // will map nodes to node-coalition-value maps
  for (const node of nodes)
    otherScores[node] = {}; // This map is useful because the same subset occurs in many partitions.
  for (const otherPartition of nodes.partitionSet()) {
    if (partition.equals(otherPartition)) continue;
    var winCount = 0;
    for (const coalition of otherPartition) {
      var coalitionString = JSON.stringify(coalition);
      for (const node of coalition) {
        if (!otherScores[node][coalitionString])
          otherScores[node][coalitionString] = scoreFunc(graph, node, coalition);
        if (currentScores[node] > otherScores[node][coalitionString])
          winCount++;
        if (currentScores[node] < otherScores[node][coalitionString])
          winCount--;
      }
    }
    if (winCount <= 0)
      return [false, otherPartition, winCount];
  }
  return [true, null, null]
}

function isPerfect(graph, partition, scoreFunc) {
  // Is this partition perfect?
  // If not, give a counter-example.
  var nodes = Object.keys(graph);
  for (const coalition of partition)
    for (const node of coalition) {
      var homeScore = scoreFunc(graph, node, coalition);
      for (const otherCoalition of nodes.filter(n=>n!=node).powerset())
        if (scoreFunc(graph, node, otherCoalition) > homeScore)
          return [false, node, otherCoalition];
    }
  return [true, null, null];
}
<commit_msg>fixed bug with computing average friend score<commit_after>// Luke Miles, April 2017
// Algorithms for altruistic hedonic games

// ** Score Functions **

function FOScore(graph, node, coalition) {
  // node's friend oriented score of coalition
  var n = Object.keys(graph).length;
  var friends = graph[node];
  return coalition.map(node2 =>
    (node2 == node ? 0 : (friends.includes(node2) ? n : -1))).sum();
}

function friendAverage(graph, node, coalition) {
  // the average happiness of a node's friends in a given coalition
  var total = 0;
  var count = 0;
  for (const friend of coalition.intersect(graph[node])) {
    total += FOScore(graph, friend, coalition);
    count++;
  }
  return (count > 0 ? total / count : 0);
}

function FOSFScore(graph, node, coalition) {
  // node's selfish-first score of coalition
  var n = Object.keys(graph).length;
  var myScore = FOScore(graph, node, coalition);
  var friendsScore = friendAverage(graph, node, coalition);
  return Math.pow(n, 5) * myScore + friendsScore;
}

function FOALScore(graph, node, coalition) {
  // node's altruistic treatment score of coalition
  var n = Object.keys(graph).length;
  var myScore = FOScore(graph, node, coalition);
  var friendsScore = friendAverage(graph, node, coalition);
  return myScore + Math.pow(n, 5) * friendsScore;
}

function FOEQScore(graph, node, coalition) {
  // node's equal treatment score of coalition
  var n = Object.keys(graph).length;
  var total = FOScore(graph, node, coalition);
  var count = 1;
  for (const friend of coalition.intersect(graph[node])) {
    total += FOScore(graph, friend, coalition)
    count++;
  }
  return total/count;
}

// ** Stability Concepts **

function isIndividuallyRational(graph, partition) {
  // Is every node in every coalition in partition happier in its current coalition than it would be alone?
  // If not, return a counter-example.
  var nodes = Object.keys(graph);
  for (const coalition of partition)
    for (const node of coalition)
      if (!isAcceptable(graph, node, coalition))
        return [false, node];
  return [true, null];
}

function isAcceptable(graph, node, coalition) {
  // Is this coalition acceptable to node?
  return coalition.equals([node]) || graph[node].intersect(coalition).length > 0;
}

// The next three stability concepts repeatedly use the same tests, so it is better to define them all at once
[isNashStable, isIndividuallyStable, isContractuallyIndividuallyStable] =
  (function(){
    // four different tests used in stability:
    // G=graph, P=partition, n=node, C1=homeCoalition, C2=otherCoalition, SF=scoreFunc
    // Is it actually a different coalition?
    var test0 = (G, P, n, C1, C2, SF) => !C1.equals(C2);
    // Do I (the node) want to leave my home?
    var test1 = (G, P, n, C1, C2, SF) => SF(G, n, C2) > SF(G, n, C1);
    // Is the new coalition okay with having me?
    var test2 = (G, P, n, C1, C2, SF) => C2.every(n2 => SF(G, n2, C2.concat([n])) >= SF(G, n2, C2));
    // Is my home okay with me leaving?
    var test3 = (G, P, n, C1, C2, SF) => (C1wn => C1wn.every(n1 => SF(G,n1,C1wn) >= SF(G,n1,C1)))(C1.filter(n1=>n1!=n));

    // check if any possible vertex with a home coalition and a new coalition passes every test
    var makeCheckFunc = tests =>
      function(G, P, SF) {
        for (const C1 of P) for (const n of C1) for (const C2 of P.concat([[]]))
          if (tests.every(test => test(G, P, n, C1, C2, SF))) // if this situation passes every test
            return [false, n, C2];
        return [true, null, null];
      }

    return [
      makeCheckFunc([test0, test1]),
      makeCheckFunc([test0, test1, test2]),
      makeCheckFunc([test0, test1, test2, test3])
    ];
  })();

function isCoreStable(graph, partition, scoreFunc) {
  // Is this partition core-stable?
  // If not, give a counter-example.
  var scores = {};
  for (const coalition of partition)
    for (const node of coalition)
      scores[node] = scoreFunc(graph, node, coalition);
  var nodes = Object.keys(graph);
  for (const coalition of nodes.powerset()) {
    if (coalition.length==0)
      continue;
    var newScores = {}
    for (const node of coalition)
      newScores[node] = scoreFunc(graph, node, coalition)
    if (coalition.every(node => newScores[node] >= scores[node]) &&
      coalition.some(node => newScores[node] > scores[node]))
      return [false, coalition];
  }
  return [true, null];
}

function isStrictlyPopular(graph, partition, scoreFunc) {
  // Is this partition stictly popular?
  // If not, give a counter-example.
  var currentScores = {}
  for (const coalition of partition)
    for (const node of coalition)
      currentScores[node] = scoreFunc(graph, node, coalition);
  var nodes = Object.keys(graph)
  var n = nodes.length;
  var otherScores = {}; // will map nodes to node-coalition-value maps
  for (const node of nodes)
    otherScores[node] = {}; // This map is useful because the same subset occurs in many partitions.
  for (const otherPartition of nodes.partitionSet()) {
    if (partition.equals(otherPartition)) continue;
    var winCount = 0;
    for (const coalition of otherPartition) {
      var coalitionString = JSON.stringify(coalition);
      for (const node of coalition) {
        if (!otherScores[node][coalitionString])
          otherScores[node][coalitionString] = scoreFunc(graph, node, coalition);
        if (currentScores[node] > otherScores[node][coalitionString])
          winCount++;
        if (currentScores[node] < otherScores[node][coalitionString])
          winCount--;
      }
    }
    if (winCount <= 0)
      return [false, otherPartition, winCount];
  }
  return [true, null, null]
}

function isPerfect(graph, partition, scoreFunc) {
  // Is this partition perfect?
  // If not, give a counter-example.
  var nodes = Object.keys(graph);
  for (const coalition of partition)
    for (const node of coalition) {
      var homeScore = scoreFunc(graph, node, coalition);
      for (const otherCoalition of nodes.filter(n=>n!=node).powerset())
        if (scoreFunc(graph, node, otherCoalition) > homeScore)
          return [false, node, otherCoalition];
    }
  return [true, null, null];
}
<|endoftext|>


<commit_before>/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint asi: true undef: true es5: true node: true browser: true devel: true
         forin: true latedef: false globalstrict: true */

"use strict";

var hub = require("plugin-hub/core")
var gcli = require("gcli-ambiance-plugin")
var codemirror = require("codemirror-ambiance-plugin")
var pluginManager = require("plugin-manager")
var fs = require("fs-ambiance-plugin")
var commandManager = require("ambiance-command-manager")

var env = global.env = window.env = {}

hub.plug(env, hub)
hub.plug(env, gcli)
hub.plug(env, codemirror)
hub.plug(env, commandManager)
hub.plug(env, fs)
hub.plug(env, pluginManager)
<commit_msg>Update dependency.<commit_after>/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint asi: true undef: true es5: true node: true browser: true devel: true
         forin: true latedef: false globalstrict: true */

"use strict";

var hub = require("plugin-hub/core")
var gcli = require("gcli-ambiance-plugin")
var codemirror = require("codemirror-ambiance-plugin")
var pluginManager = require("ambiance-plugin-manager")
var fs = require("fs-ambiance-plugin")
var commandManager = require("ambiance-command-manager")

var env = global.env = window.env = {}

hub.plug(env, hub)
hub.plug(env, gcli)
hub.plug(env, codemirror)
hub.plug(env, commandManager)
hub.plug(env, fs)
hub.plug(env, pluginManager)
<|endoftext|>