<commit_before>const DEBUG = false;

// TODO: Use a JavaScript class.

const BUTTON_LABELS = [
    // buttonLabel, eventTitlePrefix, buttonClassNames.
    ['-',       '-',  ['jfk-button', 'jfk-button-standard', 'button-dash']],
    ['1',       '1.', ['jfk-button', 'jfk-button-standard', 'button-1']],
    ['2',       '2.', ['jfk-button', 'jfk-button-standard', 'button-2']],
    ['3',       '3.', ['jfk-button', 'jfk-button-standard', 'button-3']],
    ['4',       '4.', ['jfk-button', 'jfk-button-standard', 'button-4']],
    ['5',       '5.', ['jfk-button', 'jfk-button-standard', 'button-5']],
    ['X',       null, ['jfk-button', 'jfk-button-standard', 'button-x']],
    ['DONE',    '✓',  ['jfk-button', 'jfk-button-default',  'button-d']],
    ['NOPE',    '✗',  ['jfk-button', 'jfk-button-standard', 'button-n']],
    ['OKAY',    '▣',  ['jfk-button', 'jfk-button-standard', 'button-o']],
    ['AWESOME', 'ツ',  ['jfk-button', 'jfk-button-standard', 'button-a']],
];

const BUTTON_SELECTORS = {
    '-': '.button-dash',
    '1': '.button-1',
    '2': '.button-2',
    '3': '.button-3',
    '4': '.button-4',
    '5': '.button-5',
    'x': '.button-x',
    'd': '.button-d',
    'n': '.button-n',
    'o': '.button-o',
    'a': '.button-a',
};

const KEYCODE = {
    'DASH': 189,
};

const COMPLETED_EVENT_TITLE_PREFIXES = ['✓', '✗', '▣', 'ツ'];
const PRIORITY_EVENT_TITLE_PREFIXES = ['-', '1.', '2.', '3.', '4.', '5.', null];

function dispatchEvent(obj, event) {
    var evt = new Event(
        event,
        {
            target: obj,
            bubbles: true,
        }
    );
    obj.dispatchEvent(evt);
}

function insertAfter(newNode, referenceNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

function insertButtons(referenceNode, onclickAction, where, alternateReferenceNode) {
    var insertTarget = referenceNode;
    for (var i in BUTTON_LABELS) {
        var buttonLabel = BUTTON_LABELS[i][0];
        DEBUG && console.log('buttonLabel:', buttonLabel);

        var eventTitlePrefix = BUTTON_LABELS[i][1];
        DEBUG && console.log('eventTitlePrefix:', eventTitlePrefix);

        var buttonClassNames = BUTTON_LABELS[i][2];
        DEBUG && console.log('buttonClassNames:', buttonClassNames);

        var button = document.createElement('button');
        button.innerHTML = '' + buttonLabel[0] + '' + buttonLabel.slice(1);
        button.classList.add(...buttonClassNames);

        (function(myButton, myEventTitlePrefix) {
            myButton.onclick = function() {
                DEBUG && console.log('myButton.onclick');
                onclickAction(myButton, myEventTitlePrefix);
            };
        }(button, eventTitlePrefix));

        DEBUG && console.log('inserting button', button, where, insertTarget);
        if (where === 'after') {
            insertAfter(button, insertTarget);
            insertTarget = button;
        } else if (where === 'inside') {
            if (alternateReferenceNode && !PRIORITY_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
                alternateReferenceNode.appendChild(button);
            } else {
                referenceNode.appendChild(button);
            }
        }
    }
}

function clickButton(clickedData) {
    DEBUG && console.group('clickButton');
    DEBUG && console.log('clickedData:', clickedData);
    DEBUG && console.log('button clicked');

    // Today.
    var date = new Date();
    var monthName = date.toLocaleString('en-us', { 'month': 'short' });
    var todayFormattedDate = monthName + ' ' + date.getDate() + ', ' + date.getFullYear();

    // Event date.
    var startDateInput = document.querySelector('[aria-label="Start date"]');
    var endDateInput = document.querySelector('[aria-label="End date"]');
    var eventDate = startDateInput.value;
    if (endDateInput.value !== startDateInput.value) {
        eventDate += ' - ' + endDateInput.value;
    }
    DEBUG && console.log('eventDate:', eventDate);

    var eventTitle = document.querySelector('[aria-label="Title"]');
    var calendarEventTitle = eventTitle.value;
    DEBUG && console.log('before calendarEventTitle:', calendarEventTitle);

    // Remove leading number (e.g. "1. " in "1. My Calendar Event").
    calendarEventTitle = calendarEventTitle.replace(/^\d+\. /, '');
    // Remove leading ! character.
    calendarEventTitle = calendarEventTitle.replace(/^! /, '');
    // Remove leading - character.
    calendarEventTitle = calendarEventTitle.replace(/^- /, '');
    // Remove leading ~ character.
    calendarEventTitle = calendarEventTitle.replace(/^~ /, '');

    var eventTitlePrefix = clickedData['eventTitlePrefix'];
    DEBUG && console.log('eventTitlePrefix:', eventTitlePrefix);
    if (eventTitlePrefix === '✓') {
        // Remove leading "Tentative: ".
        calendarEventTitle = calendarEventTitle.replace(/^Tentative: /, '');
    }

    DEBUG && console.log(' after calendarEventTitle:', calendarEventTitle);

    // "✓ My Event; Dec 31, 2015; event date: Jan 1, 2016"
    // "1. My Event"
    // "My Event"
    var newCalendarEventTitle = calendarEventTitle;
    if (eventTitlePrefix !== null) {
        newCalendarEventTitle = eventTitlePrefix + ' ' + calendarEventTitle;
        if (! PRIORITY_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
            newCalendarEventTitle += ';' +
                ' ' + todayFormattedDate + ';' +
                ' event date: ' + eventDate;
        }
    }
    DEBUG && console.log('newCalendarEventTitle:', newCalendarEventTitle);
    eventTitle.value = newCalendarEventTitle;

    // Move event to the current move-to date if marked completed.
    if (COMPLETED_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
        DEBUG && console.log('event marked completed');
        var moveToDateInput = document.querySelector('._move-to-date-input');
        if (moveToDateInput.value) {
            DEBUG && console.log('moving event to', moveToDateInput.value);
            setTimeout(function() {
                startDateInput.focus();
                startDateInput.value = moveToDateInput.value;

                setTimeout(function() {
                    dispatchEvent(startDateInput, 'input');
                    endDateInput.focus();

                    setTimeout(function() {
                        dispatchEvent(endDateInput, 'input');
                    }, 100);
                }, 100);
            }, 100);
        }
    }

    setTimeout(function() {
        dispatchEvent(eventTitle, 'input');
    }, 200);

    var saveButton = document.querySelector('[aria-label="Save"]');
    setTimeout(function() {
        saveButton.click();
        setTimeout(updateMoveToDate, 2000);
    }, 700);

    DEBUG && console.groupEnd();
}

function updateMoveToDate() {
    DEBUG && console.info('updateMoveToDate');
    // Find oldest date with a non-full number of events on the calendar day.

    var calendarGridRows = document.querySelectorAll('[data-view-heading] [role="presentation"] [role="row"]');
    DEBUG && console.log('calendarGridRows found:', calendarGridRows.length);
    if (!calendarGridRows.length) {
        return;
    }

    // Iterate rows top to bottom.
    var cellEventCount;
    for (var i = 0; i < calendarGridRows.length; i++) {
        var row = calendarGridRows[i];
        DEBUG && console.log('row', i, row);

        // Iterate cells right to left.
        var cells = row.querySelectorAll('[role="gridcell"]');
        DEBUG && console.log('cells', cells);
        for (var j = cells.length - 1; j >= 0; j--) {
            var cell = cells[j];
            DEBUG && console.log('cell', j, cell);

            var cellEvents = cell.querySelectorAll('[data-eventid]');
            var cellEventsFound = cellEvents.length
            DEBUG && console.log('cellEventsFound:', cellEventsFound);
            var moreCellEvents = cell.querySelector('[data-opens-day-overview]');
            if (moreCellEvents) {
                cellEventsFound += parseInt(moreCellEvents.innerText.match(/(\d+) more/)[1]);
                DEBUG && console.log('cellEventsFound:', cellEventsFound);
            }
            if (cellEventCount === undefined) {
                cellEventCount = cellEventsFound;
            } else if (cellEventsFound < cellEventCount) {
                DEBUG && console.log('move to date found');
                // "October 17".
                var monthDate = cell.querySelector('h2').innerText.match(/ events?, .*day, (.*?)$/)[1];
                DEBUG && console.log('monthDate:', monthDate);

                // "October 17, 2019".
                var monthDateYear = monthDate + ', ' + (new Date()).getFullYear();
                DEBUG && console.log('monthDateYear:', monthDateYear);

                var moveToDateInput = document.querySelector('._move-to-date-input');
                moveToDateInput.value = monthDateYear;
                return;
            }
        }
    }
}

function handleKeyEvent(event) {
    DEBUG && console.info('handleKeyEvent');
    var character = String.fromCharCode(event.which);
    if (!event.shiftKey) {
        character = character.toLowerCase();
    }
    if (event.which === KEYCODE.DASH) {
        character = '-';
    }
    DEBUG && console.log('character:', character);

    // Ignore events with modifiers.
    if (event.shiftKey) {
        return;
    }
    if (event.altKey) {
        return;
    }
    if (event.ctrlKey) {
        return;
    }
    if (event.metaKey) {
        return;
    }

    // Take requested action when event bubble is open and a keyboard shortcut matching the key pressed is found.
    var eventBubble = document.querySelector('#xDetDlg[data-eventid]')
    if (eventBubble && character in BUTTON_SELECTORS) {
        event.preventDefault();

        var buttonSelector = BUTTON_SELECTORS[character];
        var buttonToClick = document.querySelector(buttonSelector);
        if (buttonToClick) {
            buttonToClick.click();
        }
    } else if (character === 'j' || character === 'k') {
        setTimeout(updateMoveToDate, 3000);
    }
}

document.addEventListener('keydown', handleKeyEvent);

var buttonClickedData;
(function(){
    var pathname = window.location.pathname;
    setInterval(function() {
        if (pathname !== window.location.pathname) {
            DEBUG && console.log('pathname changed');
            pathname = window.location.pathname;
            if (pathname.match(/^\/calendar\/r\/eventedit\//)) {
                DEBUG && console.log('on event edit page');

                if (buttonClickedData) {
                    clickButton(buttonClickedData);
                    buttonClickedData = null;
                } else {
                    // Add action buttons to calendar event edit page.
                    DEBUG && console.log('adding buttons');
                    var onclickAction = function(myButton, myEventTitlePrefix) {
                        buttonClickedData = {
                            'button': myButton,
                            'eventTitlePrefix': myEventTitlePrefix,
                        };
                        clickButton(buttonClickedData);
                        buttonClickedData = null;
                    };

                    var saveButton = document.querySelector('[aria-label="Save"]');

                    var buttonWrapper = document.createElement('div');
                    buttonWrapper.style.left = '350px';
                    buttonWrapper.style.position = 'absolute';
                    buttonWrapper.style.top = '65px';
                    buttonWrapper.style.width = '350px';
                    buttonWrapper.style.zIndex = '1';

                    insertAfter(buttonWrapper, saveButton);
                    insertButtons(buttonWrapper, onclickAction, 'inside');
                }
            }
        }
    }, 1000);
})();

document.onclick = function(event) {
    DEBUG && console.group('element clicked');
    DEBUG && console.log('element clicked:', event.target);

    var target = event.target;
    if (!target) {
        return;
    }
    var eventIdFound = false;
    for (var i = 0; i < 5; i++) {
        if (!target) {
            break;
        // Ignore event bubble.
        } else if (target.getAttribute('id') === 'xDetDlg') {
            break;
        } else if (target.getAttribute('data-eventid')) {
            eventIdFound = true;
            break;
        }
        target = target.parentElement;
    }
    DEBUG && console.log('target:', target);
    if (eventIdFound) {
        var eventId = target.getAttribute('data-eventid');
        DEBUG && console.log('event id found:', eventId);

        function modifyEventBubble(attempt) {
            attempt = attempt || 1;
            if (attempt >= 3) {
                return;
            }
            attempt += 1;

            var eventBubble = document.querySelector('#xDetDlg[data-eventid="' + eventId + '"]');
            DEBUG && console.log('eventBubble:', eventBubble);
            if (!eventBubble) {
                setTimeout(modifyEventBubble, 500, attempt);
                return;
            }

            var editEventButton = eventBubble.querySelector('[data-tooltip="Edit event"]');
            var eventEditable = editEventButton ? true : false;
            if (!eventEditable) {
                return;
            }

            var lastEventBubbleMetaItem = document.querySelector('#xDtlDlgCt > div:last-child');

            var newEventBubbleMetaItem = document.createElement('div');
            newEventBubbleMetaItem.classList.add(...lastEventBubbleMetaItem.classList);
            insertAfter(newEventBubbleMetaItem, lastEventBubbleMetaItem);

            var lastNewEventBubbleMetaItem = document.createElement('div');
            lastNewEventBubbleMetaItem.classList.add(...lastEventBubbleMetaItem.classList);
            lastNewEventBubbleMetaItem.classList.add('_gcw-last-event-bubble-meta');
            insertAfter(lastNewEventBubbleMetaItem, newEventBubbleMetaItem);

            var onclickAction = function(myButton, myEventTitlePrefix) {
                buttonClickedData = {
                    'button': myButton,
                    'eventTitlePrefix': myEventTitlePrefix,
                };

                var editEventButton = document.querySelector('[aria-label="Edit event"]');
                DEBUG && console.log('editEventButton:', editEventButton);
                editEventButton.click();
            };
            insertButtons(newEventBubbleMetaItem, onclickAction, 'inside', lastNewEventBubbleMetaItem);
        }
        setTimeout(modifyEventBubble, 100);
    }
    DEBUG && console.groupEnd();
};

var moveToDateContainer = document.createElement('div');
moveToDateContainer.classList.add('_move-to-date-container');
moveToDateContainer.style.position = 'absolute';
moveToDateContainer.style.right = '165px';
moveToDateContainer.style.top = '50px';
moveToDateContainer.style.zIndex = '1000';

var moveToDateInput = document.createElement('input');
moveToDateInput.classList.add('_move-to-date-input');
moveToDateInput.placeholder = 'Move-to Date';
moveToDateInput.style.textAlign = 'center';

moveToDateContainer.appendChild(moveToDateInput);
document.body.appendChild(moveToDateContainer);
DEBUG && console.log('moveToDateContainer:', moveToDateContainer);

setTimeout(updateMoveToDate, 5000);
<commit_msg>Attempt move to date update repeatedly after saving event<commit_after>const DEBUG = false;

// TODO: Use a JavaScript class.

const BUTTON_LABELS = [
    // buttonLabel, eventTitlePrefix, buttonClassNames.
    ['-',       '-',  ['jfk-button', 'jfk-button-standard', 'button-dash']],
    ['1',       '1.', ['jfk-button', 'jfk-button-standard', 'button-1']],
    ['2',       '2.', ['jfk-button', 'jfk-button-standard', 'button-2']],
    ['3',       '3.', ['jfk-button', 'jfk-button-standard', 'button-3']],
    ['4',       '4.', ['jfk-button', 'jfk-button-standard', 'button-4']],
    ['5',       '5.', ['jfk-button', 'jfk-button-standard', 'button-5']],
    ['X',       null, ['jfk-button', 'jfk-button-standard', 'button-x']],
    ['DONE',    '✓',  ['jfk-button', 'jfk-button-default',  'button-d']],
    ['NOPE',    '✗',  ['jfk-button', 'jfk-button-standard', 'button-n']],
    ['OKAY',    '▣',  ['jfk-button', 'jfk-button-standard', 'button-o']],
    ['AWESOME', 'ツ',  ['jfk-button', 'jfk-button-standard', 'button-a']],
];

const BUTTON_SELECTORS = {
    '-': '.button-dash',
    '1': '.button-1',
    '2': '.button-2',
    '3': '.button-3',
    '4': '.button-4',
    '5': '.button-5',
    'x': '.button-x',
    'd': '.button-d',
    'n': '.button-n',
    'o': '.button-o',
    'a': '.button-a',
};

const KEYCODE = {
    'DASH': 189,
};

const COMPLETED_EVENT_TITLE_PREFIXES = ['✓', '✗', '▣', 'ツ'];
const PRIORITY_EVENT_TITLE_PREFIXES = ['-', '1.', '2.', '3.', '4.', '5.', null];

function dispatchEvent(obj, event) {
    var evt = new Event(
        event,
        {
            target: obj,
            bubbles: true,
        }
    );
    obj.dispatchEvent(evt);
}

function insertAfter(newNode, referenceNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

function insertButtons(referenceNode, onclickAction, where, alternateReferenceNode) {
    var insertTarget = referenceNode;
    for (var i in BUTTON_LABELS) {
        var buttonLabel = BUTTON_LABELS[i][0];
        DEBUG && console.log('buttonLabel:', buttonLabel);

        var eventTitlePrefix = BUTTON_LABELS[i][1];
        DEBUG && console.log('eventTitlePrefix:', eventTitlePrefix);

        var buttonClassNames = BUTTON_LABELS[i][2];
        DEBUG && console.log('buttonClassNames:', buttonClassNames);

        var button = document.createElement('button');
        button.innerHTML = '' + buttonLabel[0] + '' + buttonLabel.slice(1);
        button.classList.add(...buttonClassNames);

        (function(myButton, myEventTitlePrefix) {
            myButton.onclick = function() {
                DEBUG && console.log('myButton.onclick');
                onclickAction(myButton, myEventTitlePrefix);
            };
        }(button, eventTitlePrefix));

        DEBUG && console.log('inserting button', button, where, insertTarget);
        if (where === 'after') {
            insertAfter(button, insertTarget);
            insertTarget = button;
        } else if (where === 'inside') {
            if (alternateReferenceNode && !PRIORITY_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
                alternateReferenceNode.appendChild(button);
            } else {
                referenceNode.appendChild(button);
            }
        }
    }
}

function clickButton(clickedData) {
    DEBUG && console.group('clickButton');
    DEBUG && console.log('clickedData:', clickedData);
    DEBUG && console.log('button clicked');

    // Today.
    var date = new Date();
    var monthName = date.toLocaleString('en-us', { 'month': 'short' });
    var todayFormattedDate = monthName + ' ' + date.getDate() + ', ' + date.getFullYear();

    // Event date.
    var startDateInput = document.querySelector('[aria-label="Start date"]');
    var endDateInput = document.querySelector('[aria-label="End date"]');
    var eventDate = startDateInput.value;
    if (endDateInput.value !== startDateInput.value) {
        eventDate += ' - ' + endDateInput.value;
    }
    DEBUG && console.log('eventDate:', eventDate);

    var eventTitle = document.querySelector('[aria-label="Title"]');
    var calendarEventTitle = eventTitle.value;
    DEBUG && console.log('before calendarEventTitle:', calendarEventTitle);

    // Remove leading number (e.g. "1. " in "1. My Calendar Event").
    calendarEventTitle = calendarEventTitle.replace(/^\d+\. /, '');
    // Remove leading ! character.
    calendarEventTitle = calendarEventTitle.replace(/^! /, '');
    // Remove leading - character.
    calendarEventTitle = calendarEventTitle.replace(/^- /, '');
    // Remove leading ~ character.
    calendarEventTitle = calendarEventTitle.replace(/^~ /, '');

    var eventTitlePrefix = clickedData['eventTitlePrefix'];
    DEBUG && console.log('eventTitlePrefix:', eventTitlePrefix);
    if (eventTitlePrefix === '✓') {
        // Remove leading "Tentative: ".
        calendarEventTitle = calendarEventTitle.replace(/^Tentative: /, '');
    }

    DEBUG && console.log(' after calendarEventTitle:', calendarEventTitle);

    // "✓ My Event; Dec 31, 2015; event date: Jan 1, 2016"
    // "1. My Event"
    // "My Event"
    var newCalendarEventTitle = calendarEventTitle;
    if (eventTitlePrefix !== null) {
        newCalendarEventTitle = eventTitlePrefix + ' ' + calendarEventTitle;
        if (! PRIORITY_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
            newCalendarEventTitle += ';' +
                ' ' + todayFormattedDate + ';' +
                ' event date: ' + eventDate;
        }
    }
    DEBUG && console.log('newCalendarEventTitle:', newCalendarEventTitle);
    eventTitle.value = newCalendarEventTitle;

    // Move event to the current move-to date if marked completed.
    if (COMPLETED_EVENT_TITLE_PREFIXES.includes(eventTitlePrefix)) {
        DEBUG && console.log('event marked completed');
        var moveToDateInput = document.querySelector('._move-to-date-input');
        if (moveToDateInput.value) {
            DEBUG && console.log('moving event to', moveToDateInput.value);
            setTimeout(function() {
                startDateInput.focus();
                startDateInput.value = moveToDateInput.value;

                setTimeout(function() {
                    dispatchEvent(startDateInput, 'input');
                    endDateInput.focus();

                    setTimeout(function() {
                        dispatchEvent(endDateInput, 'input');
                    }, 100);
                }, 100);
            }, 100);
        }
    }

    setTimeout(function() {
        dispatchEvent(eventTitle, 'input');
    }, 200);

    var saveButton = document.querySelector('[aria-label="Save"]');
    setTimeout(function() {
        saveButton.click();
        setTimeout(updateMoveToDate, 2000);
    }, 700);

    DEBUG && console.groupEnd();
}

function updateMoveToDate(attempt) {
    DEBUG && console.info('updateMoveToDate');
    attempt = attempt || 1;
    if (attempt >= 5) {
        return;
    } else {
        attempt += 1;
    }
    DEBUG && console.log('attempt:', attempt);

    // Find oldest date with a non-full number of events on the calendar day.

    var calendarGridRows = document.querySelectorAll('[data-view-heading] [role="presentation"] [role="row"]');
    DEBUG && console.log('calendarGridRows found:', calendarGridRows.length);
    if (!calendarGridRows.length) {
        setTimeout(updateMoveToDate, 5000, attempt);
        return;
    }

    // Iterate rows top to bottom.
    var cellEventCount;
    for (var i = 0; i < calendarGridRows.length; i++) {
        var row = calendarGridRows[i];
        DEBUG && console.log('row', i, row);

        // Iterate cells right to left.
        var cells = row.querySelectorAll('[role="gridcell"]');
        DEBUG && console.log('cells', cells);
        for (var j = cells.length - 1; j >= 0; j--) {
            var cell = cells[j];
            DEBUG && console.log('cell', j, cell);

            var cellEvents = cell.querySelectorAll('[data-eventid]');
            var cellEventsFound = cellEvents.length
            DEBUG && console.log('cellEventsFound:', cellEventsFound);
            var moreCellEvents = cell.querySelector('[data-opens-day-overview]');
            if (moreCellEvents) {
                cellEventsFound += parseInt(moreCellEvents.innerText.match(/(\d+) more/)[1]);
                DEBUG && console.log('cellEventsFound:', cellEventsFound);
            }
            if (cellEventCount === undefined) {
                cellEventCount = cellEventsFound;
            } else if (cellEventsFound < cellEventCount) {
                DEBUG && console.log('move to date found');
                // "October 17".
                var monthDate = cell.querySelector('h2').innerText.match(/ events?, .*day, (.*?)$/)[1];
                DEBUG && console.log('monthDate:', monthDate);

                // "October 17, 2019".
                var monthDateYear = monthDate + ', ' + (new Date()).getFullYear();
                DEBUG && console.log('monthDateYear:', monthDateYear);

                var moveToDateInput = document.querySelector('._move-to-date-input');
                moveToDateInput.value = monthDateYear;
                return;
            }
        }
    }
}

function handleKeyEvent(event) {
    DEBUG && console.info('handleKeyEvent');
    var character = String.fromCharCode(event.which);
    if (!event.shiftKey) {
        character = character.toLowerCase();
    }
    if (event.which === KEYCODE.DASH) {
        character = '-';
    }
    DEBUG && console.log('character:', character);

    // Ignore events with modifiers.
    if (event.shiftKey) {
        return;
    }
    if (event.altKey) {
        return;
    }
    if (event.ctrlKey) {
        return;
    }
    if (event.metaKey) {
        return;
    }

    // Take requested action when event bubble is open and a keyboard shortcut matching the key pressed is found.
    var eventBubble = document.querySelector('#xDetDlg[data-eventid]')
    if (eventBubble && character in BUTTON_SELECTORS) {
        event.preventDefault();

        var buttonSelector = BUTTON_SELECTORS[character];
        var buttonToClick = document.querySelector(buttonSelector);
        if (buttonToClick) {
            buttonToClick.click();
        }
    } else if (character === 'j' || character === 'k') {
        setTimeout(updateMoveToDate, 3000);
    }
}

document.addEventListener('keydown', handleKeyEvent);

var buttonClickedData;
(function(){
    var pathname = window.location.pathname;
    setInterval(function() {
        if (pathname !== window.location.pathname) {
            DEBUG && console.log('pathname changed');
            pathname = window.location.pathname;
            if (pathname.match(/^\/calendar\/r\/eventedit\//)) {
                DEBUG && console.log('on event edit page');

                if (buttonClickedData) {
                    clickButton(buttonClickedData);
                    buttonClickedData = null;
                } else {
                    // Add action buttons to calendar event edit page.
                    DEBUG && console.log('adding buttons');
                    var onclickAction = function(myButton, myEventTitlePrefix) {
                        buttonClickedData = {
                            'button': myButton,
                            'eventTitlePrefix': myEventTitlePrefix,
                        };
                        clickButton(buttonClickedData);
                        buttonClickedData = null;
                    };

                    var saveButton = document.querySelector('[aria-label="Save"]');

                    var buttonWrapper = document.createElement('div');
                    buttonWrapper.style.left = '350px';
                    buttonWrapper.style.position = 'absolute';
                    buttonWrapper.style.top = '65px';
                    buttonWrapper.style.width = '350px';
                    buttonWrapper.style.zIndex = '1';

                    insertAfter(buttonWrapper, saveButton);
                    insertButtons(buttonWrapper, onclickAction, 'inside');
                }
            }
        }
    }, 1000);
})();

document.onclick = function(event) {
    DEBUG && console.group('element clicked');
    DEBUG && console.log('element clicked:', event.target);

    var target = event.target;
    if (!target) {
        return;
    }
    var eventIdFound = false;
    for (var i = 0; i < 5; i++) {
        if (!target) {
            break;
        // Ignore event bubble.
        } else if (target.getAttribute('id') === 'xDetDlg') {
            break;
        } else if (target.getAttribute('data-eventid')) {
            eventIdFound = true;
            break;
        }
        target = target.parentElement;
    }
    DEBUG && console.log('target:', target);
    if (eventIdFound) {
        var eventId = target.getAttribute('data-eventid');
        DEBUG && console.log('event id found:', eventId);

        function modifyEventBubble(attempt) {
            attempt = attempt || 1;
            if (attempt >= 3) {
                return;
            }
            attempt += 1;

            var eventBubble = document.querySelector('#xDetDlg[data-eventid="' + eventId + '"]');
            DEBUG && console.log('eventBubble:', eventBubble);
            if (!eventBubble) {
                setTimeout(modifyEventBubble, 500, attempt);
                return;
            }

            var editEventButton = eventBubble.querySelector('[data-tooltip="Edit event"]');
            var eventEditable = editEventButton ? true : false;
            if (!eventEditable) {
                return;
            }

            var lastEventBubbleMetaItem = document.querySelector('#xDtlDlgCt > div:last-child');

            var newEventBubbleMetaItem = document.createElement('div');
            newEventBubbleMetaItem.classList.add(...lastEventBubbleMetaItem.classList);
            insertAfter(newEventBubbleMetaItem, lastEventBubbleMetaItem);

            var lastNewEventBubbleMetaItem = document.createElement('div');
            lastNewEventBubbleMetaItem.classList.add(...lastEventBubbleMetaItem.classList);
            lastNewEventBubbleMetaItem.classList.add('_gcw-last-event-bubble-meta');
            insertAfter(lastNewEventBubbleMetaItem, newEventBubbleMetaItem);

            var onclickAction = function(myButton, myEventTitlePrefix) {
                buttonClickedData = {
                    'button': myButton,
                    'eventTitlePrefix': myEventTitlePrefix,
                };

                var editEventButton = document.querySelector('[aria-label="Edit event"]');
                DEBUG && console.log('editEventButton:', editEventButton);
                editEventButton.click();
            };
            insertButtons(newEventBubbleMetaItem, onclickAction, 'inside', lastNewEventBubbleMetaItem);
        }
        setTimeout(modifyEventBubble, 100);
    }
    DEBUG && console.groupEnd();
};

var moveToDateContainer = document.createElement('div');
moveToDateContainer.classList.add('_move-to-date-container');
moveToDateContainer.style.position = 'absolute';
moveToDateContainer.style.right = '165px';
moveToDateContainer.style.top = '50px';
moveToDateContainer.style.zIndex = '1000';

var moveToDateInput = document.createElement('input');
moveToDateInput.classList.add('_move-to-date-input');
moveToDateInput.placeholder = 'Move-to Date';
moveToDateInput.style.textAlign = 'center';

moveToDateContainer.appendChild(moveToDateInput);
document.body.appendChild(moveToDateContainer);
DEBUG && console.log('moveToDateContainer:', moveToDateContainer);

setTimeout(updateMoveToDate, 5000);
<|endoftext|>


<commit_before>if (Meteor.isClient) {

    var SessionChecker = {
        null: function (session) {
            if (Session.get(session) === null) {
                return true;
            }
        },
        boolean: function (session) {
            if (typeof Session.get(session) === "boolean") {
                return true;
            }
        },
        array: function(session) {

            var session  = Session.get(session),
                isArray  = $.isArray(session);

            if (isArray) {
                return true;
            }
        },
        number: function (session) {
            if (typeof Session.get(session) === "number") {
                return true;
            }
        },
        string: function (session) {
            if (typeof Session.get(session) === "string") {
                return true;
            }
        }
    }

    Template.body.helpers({
        SessionItems: function () {
            var SessionKeys = Object.getOwnPropertyNames(Session.keys)
            return SessionKeys;
        },
        value: function () {

            if (SessionChecker.null(this)) {
                return 'null';
            } else

            if (SessionChecker.boolean(this)) {
                return '' + Session.get(this) + '';
            } else

            if (SessionChecker.number(this)) {
                return Session.get(this);
            } else

            if (SessionChecker.string(this)) {
                return '"' + Session.get(this) + '"';
            } else

            if (SessionChecker.array(this)) {
                return "[" + Session.get(this) + "]";
            } else {

                return "Object";
            }
        }
    });

    Template.body.events({
        'click .SessionInspector_row': function () {
            var oldValue = Session.get(this);
            var newValue = prompt("What would you like to change the value of this Session variable to?");

            if (newValue === "" || newValue === null || newValue === undefined)  {
                console.log("SessionInspector: Prompt entry cannot be empty. For null value, enter null.");
            } else {
                // Detect user entry

                if (newValue === "false") {
                    Session.set(this, false);
                } else if (newValue === "true") {
                    Session.set(this, true);
                } else if (newValue === "null") {
                    Session.set(this, null);
                    newValue = "null";
                } else {
                    Session.set(this, newValue);
                }

                // Alert user
                console.log("SessionInspector: Session " + this + " changed from " + oldValue + " to " + newValue + ".");
            }
        },
        'click .SessionInspector_header': function () {
            $("#SessionInspector").hide();
        }
    });

}<commit_msg>Cutting down on use of sessions<commit_after>if (Meteor.isClient) {

    var SessionChecker = {
        null: function (value) {
            if (value === null) {
                return true;
            }
        },
        boolean: function (value) {
            if (typeof value === "boolean") {
                return true;
            }
        },
        array: function(value) {
            if ($.isArray(value)) {
                return true;
            }
        },
        number: function (value) {
            if (typeof value === "number") {
                return true;
            }
        },
        string: function (value) {
            if (typeof value === "string") {
                return true;
            }
        }
    }

    Template.body.helpers({
        SessionItems: function () {
            var SessionKeys = Object.getOwnPropertyNames(Session.keys)
            return SessionKeys;
        },
        value: function () {

            var value = Session.get(this);

            if (SessionChecker.null(value)) {
                return 'null';
            } else

            if (SessionChecker.boolean(value)) {
                return '' + value + '';
            } else

            if (SessionChecker.number(value)) {
                return value;
            } else

            if (SessionChecker.string(value)) {
                return '"' + value + '"';
            } else

            if (SessionChecker.array(value)) {
                return "[" + value + "]";
            } else {
                return "Object";
            }
        }
    });

    Template.body.events({
        'click .SessionInspector_row': function () {
            var oldValue = Session.get(this);
            var newValue = prompt("What would you like to change the value of this Session variable to?");

            if (newValue === "" || newValue === null || newValue === undefined)  {
                console.log("SessionInspector: Prompt entry cannot be empty. For null value, enter null.");
            } else {
                // Detect user entry

                if (newValue === "false") {
                    Session.set(this, false);
                } else if (newValue === "true") {
                    Session.set(this, true);
                } else if (newValue === "null") {
                    Session.set(this, null);
                    newValue = "null";
                } else {
                    Session.set(this, newValue);
                }

                // Alert user
                console.log("SessionInspector: Session " + this + " changed from " + oldValue + " to " + newValue + ".");
            }
        },
        'click .SessionInspector_header': function () {
            $("#SessionInspector").hide();
        }
    });

}<|endoftext|>


<commit_before>import React, { Component } from "react";

import { withStyles } from '@material-ui/core/styles';
import FormControlOrig from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import TextField from '@material-ui/core/TextField';
import InputLabel from '@material-ui/core/InputLabel';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
import Button from '@material-ui/core/Button';
import Grid from '@material-ui/core/Grid';

import FormComponent from "../../classes/FormComponent";
import Form from "../../components/Form";
import FormControl from "../../components/FormControl";
import AutocompleteSelect from "../../components/AutocompleteSelect";
import NetworkServerStore from "../../stores/NetworkServerStore";

import theme from "../../theme";

const styles = {
  a: {
    color: theme.palette.primary.main,
  },
  formLabel: {
    fontSize: 12,
  },
};

class ExtraChannel extends Component {
  constructor() {
    super();

    this.onChange = this.onChange.bind(this);
    this.onDelete = this.onDelete.bind(this);
  }

  onChange(e) {
    let channel = this.props.channel;
    let field = "";

    if (e.target.id === undefined) {
      field = e.target.name;
    } else {
      field = e.target.id;
    }

    if(field === "spreadingFactorsStr") {
      let sfStr = e.target.value.split(",");
      channel["spreadingFactors"] = sfStr.map((sf, i) => parseInt(sf, 10));
    }

    if (e.target.type === "number") {
      channel[field] = parseInt(e.target.value, 10);
    } else {
      channel[field] = e.target.value;
    }

    this.props.onChange(channel);
  }

  onDelete(e) {
    e.preventDefault();
    this.props.onDelete();
  }

  render() {
    let spreadingFactorsStr = "";
    if (this.props.channel.spreadingFactorsStr !== undefined) {
      spreadingFactorsStr = this.props.channel.spreadingFactorsStr;
    } else if (this.props.channel.spreadingFactors !== undefined) {
      spreadingFactorsStr = this.props.channel.spreadingFactors.join(", ");
    }

    return(
      <FormControl
        label={

            Extra channel {this.props.i + 1} (delete)

        }
      >
        <Grid container spacing={4}>
          <Grid item xs={6}>
            <FormControl>
              <InputLabel htmlFor="modulation" required>Modulation</InputLabel>
              <Select
                value={this.props.channel.modulation || ""}
                onChange={this.onChange}
                inputProps={{
                  name: "modulation",
                }}
              >
                <MenuItem value="LORA">LoRa</MenuItem>
                <MenuItem value="FSK">FSK</MenuItem>
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={6}>
            <FormControl>
              <InputLabel htmlFor="bandwidth" required>bandwidth (kHz)</InputLabel>
              <Select
                value={this.props.channel.bandwidth || ""}
                onChange={this.onChange}
                inputProps={{
                  name: "bandwidth",
                }}
              >
                <MenuItem value={125}>125 kHz</MenuItem>
                <MenuItem value={250}>250 kHz</MenuItem>
                <MenuItem value={500}>500 kHz</MenuItem>
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={6}>
            <TextField
              id="frequency"
              label="Frequency (Hz)"
              type="number"
              margin="normal"
              value={this.props.channel.frequency || ""}
              onChange={this.onChange}
              required
              fullWidth
            />
          </Grid>
          {this.props.channel.modulation === "LORA" && <Grid item xs={6}>
            <TextField
              id="spreadingFactorsStr"
              label="Spreading-factors"
              margin="normal"
              value={spreadingFactorsStr}
              onChange={this.onChange}
              placeholder="7, 8, 9, 10, 11, 12"
              helperText="When defining multiple spreading-factors, the channel will be configured as a multi-SF channel on the gateway."
              inputProps={{
                pattern: "[0-9]+(,[\\s]*[0-9]+)*",
              }}
              required
              fullWidth
            />
          </Grid>}
          {this.props.channel.modulation === "FSK" && <Grid item xs={6}>
            <TextField
              id="bitrate"
              label="Bitrate"
              type="number"
              margin="normal"
              value={this.props.channel.bitrate || ""}
              onChange={this.onChange}
              placeholder="50000"
              required
              fullWidth
            />
          </Grid>}
        </Grid>
      </FormControl>
    );
  }
}

ExtraChannel = withStyles(styles)(ExtraChannel);

class GatewayProfileForm extends FormComponent {
  constructor() {
    super();

    this.addExtraChannel = this.addExtraChannel.bind(this);
    this.getNetworkServerOption = this.getNetworkServerOption.bind(this);
    this.getNetworkServerOptions = this.getNetworkServerOptions.bind(this);
  }

  componentDidMount() {
    super.componentDidMount();

    if (this.props.object !== undefined && this.props.object.channels !== undefined && this.props.object.channelsStr === undefined) {
      let object = this.props.object;
      object.channelsStr = object.channels.join(", ");
      this.setState({
        object: object,
      });
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.object !== this.props.object) {
      let object = this.props.object;

      if (object !== undefined && object.channels !== undefined && object.channelsStr === undefined) {
        object.channelsStr = object.channels.join(", ");
      }

      this.setState({
        object: object || {},
      });
    }
  }

  onChange(e) {
    super.onChange(e);

    let object = this.state.object;

    if(e.target.id === "channelsStr") {
      let channelsStr = e.target.value.split(",");
      object["channels"] = channelsStr.map((c, i) => parseInt(c, 10));
    }

    this.setState({
      object: object,
    });
  }

  addExtraChannel() {
    let object = this.state.object;
    if (object.extraChannels === undefined) {
      object.extraChannels = [{modulation: "LORA"}];
    } else {
      object.extraChannels.push({modulation: "LORA"});
    }

    this.setState({
      object: object,
    });
  }

  deleteExtraChannel(i) {
    let object = this.state.object;
    object.extraChannels.splice(i, 1);
    this.setState({
      object: object,
    });
  }

  updateExtraChannel(i, ec) {
    let object = this.state.object;
    object.extraChannels[i] = ec;

    this.setState({
      object: object,
    });
  }

  getNetworkServerOption(id, callbackFunc) {
    NetworkServerStore.get(id, resp => {
      callbackFunc({label: resp.name, value: resp.id});
    });
  }

  getNetworkServerOptions(search, callbackFunc) {
    NetworkServerStore.list(0, 999, 0, resp => {
      const options = resp.result.map((ns, i) => {return {label: ns.name, value: ns.id}});
      callbackFunc(options);
    });
  }

  render() {
    if (this.state.object === undefined) {
      return(

);
    }

    let extraChannels = [];

    if (this.state.object.extraChannels !== undefined) {
      extraChannels = this.state.object.extraChannels.map((ec, i) => <ExtraChannel key={i} channel={ec} i={i} onDelete={() => this.deleteExtraChannel(i)} onChange={ec => this.updateExtraChannel(i, ec)} />);
    }

    return(
      <Form
        submitLabel={this.props.submitLabel}
        onSubmit={this.onSubmit}
        extraButtons={<Button onClick={this.addExtraChannel}>Add extra channel</Button>}
      >
        <TextField
          id="name"
          label="Name"
          margin="normal"
          value={this.state.object.name || ""}
          onChange={this.onChange}
          helperText="A short name identifying the gateway-profile."
          required
          fullWidth
        />

        <TextField
          id="channelsStr"
          label="Enabled channels"
          margin="normal"
          value={this.state.object.channelsStr || ""}
          onChange={this.onChange}
          helperText="The channels active in this gateway-profile as specified in the LoRaWAN Regional Parameters sepecification. Separate channels by comma, e.g. 0, 1, 2. Extra channels must not be included in this list."
          placeholder="0, 1, 2"
          inputProps={{
            pattern: "[0-9]+(,[\\s]*[0-9]+)*",
          }}
          required
          fullWidth
        />
        {!this.props.update && <FormControlOrig margin="normal" fullWidth>
          <FormLabel className={this.props.classes.formLabel} required>Network-server</FormLabel>
          <AutocompleteSelect
            id="networkServerID"
            label="Select network-server"
            value={this.state.object.networkServerID || ""}
            onChange={this.onChange}
            getOption={this.getNetworkServerOption}
            getOptions={this.getNetworkServerOptions}
          />
        </FormControlOrig>}
        {extraChannels}
      </Form>
    );
  }
}

export default withStyles(styles)(GatewayProfileForm);
<commit_msg>Fix typo in GatewayProfileForm.js (#352)<commit_after>import React, { Component } from "react";

import { withStyles } from '@material-ui/core/styles';
import FormControlOrig from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import TextField from '@material-ui/core/TextField';
import InputLabel from '@material-ui/core/InputLabel';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
import Button from '@material-ui/core/Button';
import Grid from '@material-ui/core/Grid';

import FormComponent from "../../classes/FormComponent";
import Form from "../../components/Form";
import FormControl from "../../components/FormControl";
import AutocompleteSelect from "../../components/AutocompleteSelect";
import NetworkServerStore from "../../stores/NetworkServerStore";

import theme from "../../theme";

const styles = {
  a: {
    color: theme.palette.primary.main,
  },
  formLabel: {
    fontSize: 12,
  },
};

class ExtraChannel extends Component {
  constructor() {
    super();

    this.onChange = this.onChange.bind(this);
    this.onDelete = this.onDelete.bind(this);
  }

  onChange(e) {
    let channel = this.props.channel;
    let field = "";

    if (e.target.id === undefined) {
      field = e.target.name;
    } else {
      field = e.target.id;
    }

    if(field === "spreadingFactorsStr") {
      let sfStr = e.target.value.split(",");
      channel["spreadingFactors"] = sfStr.map((sf, i) => parseInt(sf, 10));
    }

    if (e.target.type === "number") {
      channel[field] = parseInt(e.target.value, 10);
    } else {
      channel[field] = e.target.value;
    }

    this.props.onChange(channel);
  }

  onDelete(e) {
    e.preventDefault();
    this.props.onDelete();
  }

  render() {
    let spreadingFactorsStr = "";
    if (this.props.channel.spreadingFactorsStr !== undefined) {
      spreadingFactorsStr = this.props.channel.spreadingFactorsStr;
    } else if (this.props.channel.spreadingFactors !== undefined) {
      spreadingFactorsStr = this.props.channel.spreadingFactors.join(", ");
    }

    return(
      <FormControl
        label={

            Extra channel {this.props.i + 1} (delete)

        }
      >
        <Grid container spacing={4}>
          <Grid item xs={6}>
            <FormControl>
              <InputLabel htmlFor="modulation" required>Modulation</InputLabel>
              <Select
                value={this.props.channel.modulation || ""}
                onChange={this.onChange}
                inputProps={{
                  name: "modulation",
                }}
              >
                <MenuItem value="LORA">LoRa</MenuItem>
                <MenuItem value="FSK">FSK</MenuItem>
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={6}>
            <FormControl>
              <InputLabel htmlFor="bandwidth" required>bandwidth (kHz)</InputLabel>
              <Select
                value={this.props.channel.bandwidth || ""}
                onChange={this.onChange}
                inputProps={{
                  name: "bandwidth",
                }}
              >
                <MenuItem value={125}>125 kHz</MenuItem>
                <MenuItem value={250}>250 kHz</MenuItem>
                <MenuItem value={500}>500 kHz</MenuItem>
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={6}>
            <TextField
              id="frequency"
              label="Frequency (Hz)"
              type="number"
              margin="normal"
              value={this.props.channel.frequency || ""}
              onChange={this.onChange}
              required
              fullWidth
            />
          </Grid>
          {this.props.channel.modulation === "LORA" && <Grid item xs={6}>
            <TextField
              id="spreadingFactorsStr"
              label="Spreading-factors"
              margin="normal"
              value={spreadingFactorsStr}
              onChange={this.onChange}
              placeholder="7, 8, 9, 10, 11, 12"
              helperText="When defining multiple spreading-factors, the channel will be configured as a multi-SF channel on the gateway."
              inputProps={{
                pattern: "[0-9]+(,[\\s]*[0-9]+)*",
              }}
              required
              fullWidth
            />
          </Grid>}
          {this.props.channel.modulation === "FSK" && <Grid item xs={6}>
            <TextField
              id="bitrate"
              label="Bitrate"
              type="number"
              margin="normal"
              value={this.props.channel.bitrate || ""}
              onChange={this.onChange}
              placeholder="50000"
              required
              fullWidth
            />
          </Grid>}
        </Grid>
      </FormControl>
    );
  }
}

ExtraChannel = withStyles(styles)(ExtraChannel);

class GatewayProfileForm extends FormComponent {
  constructor() {
    super();

    this.addExtraChannel = this.addExtraChannel.bind(this);
    this.getNetworkServerOption = this.getNetworkServerOption.bind(this);
    this.getNetworkServerOptions = this.getNetworkServerOptions.bind(this);
  }

  componentDidMount() {
    super.componentDidMount();

    if (this.props.object !== undefined && this.props.object.channels !== undefined && this.props.object.channelsStr === undefined) {
      let object = this.props.object;
      object.channelsStr = object.channels.join(", ");
      this.setState({
        object: object,
      });
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.object !== this.props.object) {
      let object = this.props.object;

      if (object !== undefined && object.channels !== undefined && object.channelsStr === undefined) {
        object.channelsStr = object.channels.join(", ");
      }

      this.setState({
        object: object || {},
      });
    }
  }

  onChange(e) {
    super.onChange(e);

    let object = this.state.object;

    if(e.target.id === "channelsStr") {
      let channelsStr = e.target.value.split(",");
      object["channels"] = channelsStr.map((c, i) => parseInt(c, 10));
    }

    this.setState({
      object: object,
    });
  }

  addExtraChannel() {
    let object = this.state.object;
    if (object.extraChannels === undefined) {
      object.extraChannels = [{modulation: "LORA"}];
    } else {
      object.extraChannels.push({modulation: "LORA"});
    }

    this.setState({
      object: object,
    });
  }

  deleteExtraChannel(i) {
    let object = this.state.object;
    object.extraChannels.splice(i, 1);
    this.setState({
      object: object,
    });
  }

  updateExtraChannel(i, ec) {
    let object = this.state.object;
    object.extraChannels[i] = ec;

    this.setState({
      object: object,
    });
  }

  getNetworkServerOption(id, callbackFunc) {
    NetworkServerStore.get(id, resp => {
      callbackFunc({label: resp.name, value: resp.id});
    });
  }

  getNetworkServerOptions(search, callbackFunc) {
    NetworkServerStore.list(0, 999, 0, resp => {
      const options = resp.result.map((ns, i) => {return {label: ns.name, value: ns.id}});
      callbackFunc(options);
    });
  }

  render() {
    if (this.state.object === undefined) {
      return(

);
    }

    let extraChannels = [];

    if (this.state.object.extraChannels !== undefined) {
      extraChannels = this.state.object.extraChannels.map((ec, i) => <ExtraChannel key={i} channel={ec} i={i} onDelete={() => this.deleteExtraChannel(i)} onChange={ec => this.updateExtraChannel(i, ec)} />);
    }

    return(
      <Form
        submitLabel={this.props.submitLabel}
        onSubmit={this.onSubmit}
        extraButtons={<Button onClick={this.addExtraChannel}>Add extra channel</Button>}
      >
        <TextField
          id="name"
          label="Name"
          margin="normal"
          value={this.state.object.name || ""}
          onChange={this.onChange}
          helperText="A short name identifying the gateway-profile."
          required
          fullWidth
        />

        <TextField
          id="channelsStr"
          label="Enabled channels"
          margin="normal"
          value={this.state.object.channelsStr || ""}
          onChange={this.onChange}
          helperText="The channels active in this gateway-profile as specified in the LoRaWAN Regional Parameters specification. Separate channels by comma, e.g. 0, 1, 2. Extra channels must not be included in this list."
          placeholder="0, 1, 2"
          inputProps={{
            pattern: "[0-9]+(,[\\s]*[0-9]+)*",
          }}
          required
          fullWidth
        />
        {!this.props.update && <FormControlOrig margin="normal" fullWidth>
          <FormLabel className={this.props.classes.formLabel} required>Network-server</FormLabel>
          <AutocompleteSelect
            id="networkServerID"
            label="Select network-server"
            value={this.state.object.networkServerID || ""}
            onChange={this.onChange}
            getOption={this.getNetworkServerOption}
            getOptions={this.getNetworkServerOptions}
          />
        </FormControlOrig>}
        {extraChannels}
      </Form>
    );
  }
}

export default withStyles(styles)(GatewayProfileForm);
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import * as React from 'react';
import {createContext, Component, Fragment, useContext} from 'react';
import PropTypes from 'prop-types';

function someNamedFunction() {}

const contextData = {
  array: ['first', 'second', 'third'],
  bool: true,
  func: someNamedFunction,
  number: 123,
  object: {outer: {inner: {}}},
  string: 'abc',
  symbol: Symbol.for('symbol'),
  null: null,
  undefined: undefined,
};

class LegacyContextProvider extends Component<any> {
  static childContextTypes = {
    array: PropTypes.array,
    bool: PropTypes.bool,
    func: PropTypes.func,
    number: PropTypes.number,
    object: PropTypes.object,
    string: PropTypes.string,
    symbol: PropTypes.symbol,
    null: PropTypes.any,
    undefined: PropTypes.any,
  };

  getChildContext() {
    return contextData;
  }

  render() {
    return this.props.children;
  }
}

class LegacyContextConsumer extends Component<any> {
  static contextTypes = {
    array: PropTypes.array,
    bool: PropTypes.bool,
    func: PropTypes.func,
    number: PropTypes.number,
    object: PropTypes.object,
    string: PropTypes.string,
    symbol: PropTypes.symbol,
    null: PropTypes.any,
    undefined: PropTypes.any,
  };

  render() {
    return null;
  }
}

const ModernContext = createContext();
ModernContext.displayName = 'ModernContext';
const ArrayContext = createContext(contextData.array);
ArrayContext.displayName = 'ArrayContext';
const BoolContext = createContext(contextData.bool);
BoolContext.displayName = 'BoolContext';
const FuncContext = createContext(contextData.func);
FuncContext.displayName = 'FuncContext';
const NumberContext = createContext(contextData.number);
NumberContext.displayName = 'NumberContext';
const StringContext = createContext(contextData.string);
StringContext.displayName = 'StringContext';
const SymbolContext = createContext(contextData.symbol);
SymbolContext.displayName = 'SymbolContext';
const NullContext = createContext(null);
NullContext.displayName = 'NullContext';
const UndefinedContext = createContext(undefined);
UndefinedContext.displayName = 'UndefinedContext';

class ModernContextType extends Component<any> {
  static contextType = ModernContext;

  render() {
    return null;
  }
}

function FunctionalContextConsumer() {
  useContext(StringContext);
  return null;
}

export default function Contexts() {
  return (
    <Fragment>
      <LegacyContextProvider>
        <LegacyContextConsumer />
      </LegacyContextProvider>
      <ModernContext.Provider value={contextData}>
        <ModernContext.Consumer>{value => null}</ModernContext.Consumer>
        <ModernContextType />
      </ModernContext.Provider>
      <FunctionalContextConsumer />
      <ArrayContext.Consumer>{value => null}</ArrayContext.Consumer>
      <BoolContext.Consumer>{value => null}</BoolContext.Consumer>
      <FuncContext.Consumer>{value => null}</FuncContext.Consumer>
      <NumberContext.Consumer>{value => null}</NumberContext.Consumer>
      <StringContext.Consumer>{value => null}</StringContext.Consumer>
      <SymbolContext.Consumer>{value => null}</SymbolContext.Consumer>
      <NullContext.Consumer>{value => null}</NullContext.Consumer>
      <UndefinedContext.Consumer>{value => null}</UndefinedContext.Consumer>
    </Fragment>
  );
}
<commit_msg>Improved DevTools context test harness (#19878)<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import * as React from 'react';
import {createContext, Component, useContext} from 'react';
import PropTypes from 'prop-types';

function someNamedFunction() {}

function formatContextForDisplay(name, value) {
  return (

      {name}:
{JSON.stringify(value, null, 2)}

  );
}

const contextData = {
  array: ['first', 'second', 'third'],
  bool: true,
  func: someNamedFunction,
  number: 123,
  object: {outer: {inner: {}}},
  string: 'abc',
  symbol: Symbol.for('symbol'),
  null: null,
  undefined: undefined,
};

class LegacyContextProvider extends Component<any> {
  static childContextTypes = {
    array: PropTypes.array,
    bool: PropTypes.bool,
    func: PropTypes.func,
    number: PropTypes.number,
    object: PropTypes.object,
    string: PropTypes.string,
    symbol: PropTypes.symbol,
    null: PropTypes.any,
    undefined: PropTypes.any,
  };

  getChildContext() {
    return contextData;
  }

  render() {
    return this.props.children;
  }
}

class LegacyContextConsumer extends Component<any> {
  static contextTypes = {
    array: PropTypes.array,
    bool: PropTypes.bool,
    func: PropTypes.func,
    number: PropTypes.number,
    object: PropTypes.object,
    string: PropTypes.string,
    symbol: PropTypes.symbol,
    null: PropTypes.any,
    undefined: PropTypes.any,
  };

  render() {
    return formatContextForDisplay('LegacyContextConsumer', this.context);
  }
}

const ModernContext = createContext();
ModernContext.displayName = 'ModernContext';
const ArrayContext = createContext(contextData.array);
ArrayContext.displayName = 'ArrayContext';
const BoolContext = createContext(contextData.bool);
BoolContext.displayName = 'BoolContext';
const FuncContext = createContext(contextData.func);
FuncContext.displayName = 'FuncContext';
const NumberContext = createContext(contextData.number);
NumberContext.displayName = 'NumberContext';
const StringContext = createContext(contextData.string);
StringContext.displayName = 'StringContext';
const SymbolContext = createContext(contextData.symbol);
SymbolContext.displayName = 'SymbolContext';
const NullContext = createContext(null);
NullContext.displayName = 'NullContext';
const UndefinedContext = createContext(undefined);
UndefinedContext.displayName = 'UndefinedContext';

class ModernContextType extends Component<any> {
  static contextType = ModernContext;

  render() {
    return formatContextForDisplay('ModernContextType', this.context);
  }
}

function FunctionalContextConsumer() {
  const value = useContext(StringContext);
  return formatContextForDisplay('FunctionalContextConsumer', value);
}

export default function Contexts() {
  return (

Contexts

        <LegacyContextProvider>
          <LegacyContextConsumer />
        </LegacyContextProvider>
        <ModernContext.Provider value={contextData}>
          <ModernContext.Consumer>
            {value => formatContextForDisplay('ModernContext.Consumer', value)}
          </ModernContext.Consumer>
          <ModernContextType />
        </ModernContext.Provider>
        <FunctionalContextConsumer />
        <ArrayContext.Consumer>
          {value => formatContextForDisplay('ArrayContext.Consumer', value)}
        </ArrayContext.Consumer>
        <BoolContext.Consumer>
          {value => formatContextForDisplay('BoolContext.Consumer', value)}
        </BoolContext.Consumer>
        <FuncContext.Consumer>
          {value => formatContextForDisplay('FuncContext.Consumer', value)}
        </FuncContext.Consumer>
        <NumberContext.Consumer>
          {value => formatContextForDisplay('NumberContext.Consumer', value)}
        </NumberContext.Consumer>
        <StringContext.Consumer>
          {value => formatContextForDisplay('StringContext.Consumer', value)}
        </StringContext.Consumer>
        <SymbolContext.Consumer>
          {value => formatContextForDisplay('SymbolContext.Consumer', value)}
        </SymbolContext.Consumer>
        <NullContext.Consumer>
          {value => formatContextForDisplay('NullContext.Consumer', value)}
        </NullContext.Consumer>
        <UndefinedContext.Consumer>
          {value => formatContextForDisplay('UndefinedContext.Consumer', value)}
        </UndefinedContext.Consumer>

  );
}
<|endoftext|>


<commit_before>/* eslint-disable react/no-danger */
import { graphql, Link } from 'gatsby'
import React from 'react'
import Utterance from 'react-utterances'
import Bio from '../components/Bio'
import Layout from '../components/Layout'
import SEO from '../components/SEO'
import { formatReadingTime } from '../utils/helpers'
import { rhythm, scale } from '../utils/typography'

function BlogPostTemplate({ data, location, pageContext }) {
  const post = data.markdownRemark
  const siteTitle = data.site.siteMetadata.title
  const siteDescription = post.excerpt
  const { previous, next } = pageContext

  return (
    <Layout location={location} title={siteTitle}>
      <SEO
        lang="en"
        title={post.frontmatter.title}
        description={siteDescription}
        slug={post.fields.slug}
      />

{post.frontmatter.title}

        {post.frontmatter.date}
        {` • ${formatReadingTime(post.timeToRead)}`}
        {post.frontmatter.tags.map(tag => (
          <Link to={`/tags/${tag}`}>
            {` • 🏷 ${tag}`}
          </Link>
        ))}

Comments Should Load Here😜

      <Utterance repo="Oluwasetemi/oluwasetemi.dev" type="url" />
      <Bio />

          {previous && (
            <Link to={previous.fields.slug} rel="prev">
              ← {previous.frontmatter.title}
            </Link>
          )}

          {next && (
            <Link to={next.fields.slug} rel="next">
              {next.frontmatter.title} →
            </Link>
          )}

    </Layout>
  )
}

export default BlogPostTemplate

export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    site {
      siteMetadata {
        title
        author
      }
    }
    markdownRemark(fields: { slug: { eq: $slug } }) {
      id
      excerpt
      html
      timeToRead
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
        tags
      }
      fields {
        slug
      }
    }
  }
`
<commit_msg>📦 NEW: add the edit blogpost button<commit_after>/* eslint-disable react/no-danger */
import { graphql, Link } from 'gatsby'
import React from 'react'
import Utterance from 'react-utterances'
import Bio from '../components/Bio'
import Layout from '../components/Layout'
import SEO from '../components/SEO'
import { formatReadingTime } from '../utils/helpers'
import { rhythm, scale } from '../utils/typography'

function BlogPostTemplate({ data, location, pageContext }) {
  const post = data.markdownRemark
  const siteTitle = data.site.siteMetadata.title
  const siteDescription = post.excerpt
  const { previous, next } = pageContext

  function getGitMarkdownUrl() {
    const GITHUB_USERNAME = 'Oluwasetemi'
    const GITHUB_REPO_NAME = 'oluwasetemi.dev'
    const editUrl = `https://github.com/${GITHUB_USERNAME}/${GITHUB_REPO_NAME}/edit/master/src/pages/${post.fields.slug.replace(
      /\//g,
      ''
    )}/index.md`
    return editUrl
  }

  const gitMarkdownUrl = getGitMarkdownUrl()

  return (
    <Layout location={location} title={siteTitle}>
      <SEO
        lang="en"
        title={post.frontmatter.title}
        description={siteDescription}
        slug={post.fields.slug}
      />

{post.frontmatter.title}

        {post.frontmatter.date}
        {` • ${formatReadingTime(post.timeToRead)}`}
        {post.frontmatter.tags.map(tag => (
          <Link to={`/tags/${tag}`} key={tag}>
            {` • 🏷 ${tag}`}
          </Link>
        ))}
        {` • ✏ `}

          EDIT THIS POST

Comments Should Load Here😜

      <Utterance repo="Oluwasetemi/oluwasetemi.dev" type="url" />
      <Bio />

          {previous && (
            <Link to={previous.fields.slug} rel="prev">
              ← {previous.frontmatter.title}
            </Link>
          )}

          {next && (
            <Link to={next.fields.slug} rel="next">
              {next.frontmatter.title} →
            </Link>
          )}

    </Layout>
  )
}

export default BlogPostTemplate

export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    site {
      siteMetadata {
        title
        author
      }
    }
    markdownRemark(fields: { slug: { eq: $slug } }) {
      id
      excerpt
      html
      timeToRead
      fileAbsolutePath
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
        tags
      }
      fields {
        slug
      }
    }
  }
`
<|endoftext|>


<commit_before>#!/usr/bin/env phantomjs
/***********************************************************
 *
 *  Run with:
 *
 *  $ phantomjs ./external.js ./urls.txt
 *
 *  or
 *
 *  $ phantomjs ./external.js \
 *     "http://foo.com, http://foo.com/bar"
 *
 *  Note: As a bonus, I left the page timing as well from
 *  the example script I started this from.
 *
 ***********************************************************/

var webpage   = require('webpage');
var system    = require('system');
var fs        = require('fs');
var finished  = 0;
var addresses = [];

/***********************************************************
 * Add any domains you wish to exclude to this array.
 *
 * TODO: Support populating this array from an external
 * source.
 ***********************************************************/
var local_domains = [
    // domains to be excluded, e.g.:
    // "www.example.com"
];

function usage() {
    console.log('Usage: external.js <some URL>|<some file>');
    phantom.exit();
}

if (system.args.length === 1) {
    usage();
}

if (fs.exists(system.args[1])) {
    fs.read(system.args[1])
        .split('\n')
        .forEach(function(line) {
            if (line !== '') {
                addresses.push(line);
            }
        });
} else {
    system.args[1].split(',').forEach(function(item) {
        addresses.push(item.replace(/^\s+/,'').replace(/\s+$/,''));
    });
}

if (!addresses || addresses.length === 0) {
    usage();
}

function isLocal(path) {
    var matched = false;
    local_domains.forEach(function(domain) {
        if (path.match('^https://'+domain) || path.match('^http://'+domain) || path.match('^//'+domain)) {
            matched = true;
        }
    });
    return matched;
}

function domain(url) {
    return url.match("^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)")[1];
}

function flattenAndTally(urls) {
    var ret = [];
    urls.forEach(function(url) {
        url = domain(url);
        var exists = false;
        var index = 0;
        ret.forEach(function(u) {
            if (u.url === url) {
                exists = true;
                ret[index].count++;
            }
            index++;
        });
        if (!exists) {
            ret.push({ url: url, count: 1 });
        }
    });
    return ret;
}

addresses.forEach(function(address) {
    local_domains.push(domain(address));

    var t = Date.now();
    var page = webpage.create();
    var urls = [];
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('FAIL to load the address');
            finished++;
            return;
        }
        t = Date.now() - t;

        console.log('Regarding: ' + address);
        console.log('> took ' + t + ' msec');
        console.log(' ');
        console.log('External Requests:');

        var results = flattenAndTally(urls)
                        .sort(function(a,b) {
                            if (a.count < b.count) {
                                return 1;
                            }
                            if (a.count > b.count) {
                                return -1;
                            }
                            return 0;
                        });

        results.forEach(function(url) {
            console.log(' - ' + url.url + ' [' + url.count + ']');
        });
        console.log(' ');

        finished++;

        if (finished === addresses.length) {
            phantom.exit();
        }
        return;
    });
    page.onResourceReceived = function(response) {
        if (!isLocal(response.url)) {
            urls.push(response.url);
        }
    };
});
<commit_msg>Report successful and failed reports.<commit_after>#!/usr/bin/env phantomjs
/***********************************************************
 *
 *  Run with:
 *
 *  $ phantomjs ./external.js ./urls.txt
 *
 *  or
 *
 *  $ phantomjs ./external.js \
 *     "http://foo.com, http://foo.com/bar"
 *
 *  Note: As a bonus, I left the page timing as well from
 *  the example script I started this from.
 *
 ***********************************************************/

var webpage   = require('webpage');
var system    = require('system');
var fs        = require('fs');
var finished  = 0;
var addresses = [];

/***********************************************************
 * Add any domains you wish to exclude to this array.
 *
 * TODO: Support populating this array from an external
 * source.
 ***********************************************************/
var local_domains = [
    // domains to be excluded, e.g.:
    // "www.example.com"
];

function usage() {
    console.log('Usage: external.js <some URL>|<some file>');
    phantom.exit();
}

if (system.args.length === 1) {
    usage();
}

if (fs.exists(system.args[1])) {
    fs.read(system.args[1])
        .split('\n')
        .forEach(function(line) {
            if (line !== '') {
                addresses.push(line);
            }
        });
} else {
    system.args[1].split(',').forEach(function(item) {
        addresses.push(item.replace(/^\s+/,'').replace(/\s+$/,''));
    });
}

if (!addresses || addresses.length === 0) {
    usage();
}

function isLocal(path) {
    var matched = false;
    local_domains.forEach(function(domain) {
        if (path.match('^https://'+domain) || path.match('^http://'+domain) || path.match('^//'+domain)) {
            matched = true;
        }
    });
    return matched;
}

function domain(url) {
    return url.match("^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)")[1];
}

function flattenAndTallySuccesses(reqs) {
    var ret = [];
    reqs.forEach(function(req) {
        if (req.responded) {
            url = domain(req.url);
            var exists = false;
            var index = 0;
            ret.forEach(function(u) {
                if (u.url === url) {
                    exists = true;
                    ret[index].count++;
                }
                index++;
            });
            if (!exists) {
                ret.push({ url: url, count: 1 });
            }
        }
    });
    return ret;
}

function flattenAndTallyFailures(reqs) {
    var ret = [];
    reqs.forEach(function(req) {
        if (!req.responded) {
            url = domain(req.url);
            var exists = false;
            var index = 0;
            ret.forEach(function(u) {
                if (u.url === url) {
                    exists = true;
                    ret[index].count++;
                }
                index++;
            });
            if (!exists) {
                ret.push({ url: url, count: 1 });
            }
        }
    });
    return ret;
}

addresses.forEach(function(address) {
    local_domains.push(domain(address));

    var t = Date.now();
    var page = webpage.create();
    var requests = [];
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('FAIL to load the address');
            finished++;
            return;
        }
        t = Date.now() - t;

        console.log('Regarding: ' + address);
        console.log('> took ' + t + ' msec');
        console.log(' ');
        console.log('External Requests:');

        var successes = flattenAndTallySuccesses(requests)
                        .sort(function(a,b) {
                            if (a.count < b.count) {
                                return 1;
                            }
                            if (a.count > b.count) {
                                return -1;
                            }
                            return 0;
                        });

        successes.forEach(function(url) {
            console.log(' - ' + url.url + ' [' + url.count + ']');
        });
        console.log(' ');

        var failures = flattenAndTallyFailures(requests)
                        .sort(function(a,b) {
                            if (a.count < b.count) {
                                return 1;
                            }
                            if (a.count > b.count) {
                                return -1;
                            }
                            return 0;
                        });

        if (failures.length > 0) {
            console.log('Failed Requests:');
            failures.forEach(function(url) {
                console.log(' - ' + url.url + ' [' + url.count + ']');
            });
            console.log(' ');
        }

        finished++;

        if (finished === addresses.length) {
            phantom.exit();
        }
        return;
    });

    page.onResourceRequested = function(data, request) {
        if (!isLocal(data.url)) {
            requests.push({ url: data.url, id: data.id });
        }
    };

    page.onResourceReceived = function(response) {
        if (!isLocal(response.url)) {
            var index = 0;
            requests.forEach(function(request) {
                if (request.url === response.url && request.id === response.id) {
                    requests[index].responded = true;
                }
                index++;
            });
        }
    };
});

<|endoftext|>


<commit_before><commit_msg>Remove weird symlink<commit_after><|endoftext|>