BootCampSpot v2 Improvements

Streamlined login experience and localStorage-based preservation of comments.

Verzia zo dňa 12.03.2018. Pozri najnovšiu verziu.

// ==UserScript==
// @name         BootCampSpot v2 Improvements
// @namespace    https://jonas.ninja
// @version      1.1.1
// @description  Streamlined login experience and localStorage-based preservation of comments.
// @author       @iv_njonas
// @match        https://bootcampspot-v2.com/*
// @grant        none
// ==/UserScript==

(function preserveCommentsInLocalStorage() {
    /// BCS has a nasty habit of kicking out users after a certain time, usually a few moments
    /// before they submit a long and thoughtful comment on a student's homework. This has been
    /// a great source of workplace stress and psychological harm. This code prevents such
    /// heartache by auto-saving your comments at every keystroke, and presents a button to
    /// easily load a saved comment after BCS kicks you out.

    var localStorageKey = 'bootcampspotv2-comment';
    var commentBoxSelector = 'textarea#commentEntry';
    var loadButton = createLoadButton();
    var infoAlert = createInfoAlert();
    var noSavedComment = createNoSavedCommentAlert();
    var oldText;

    $('body').on('keyup', commentBoxSelector, function(e) {
        // save the entered text if it has changed significantly. If the button happens to be visible, hide it.
        // give the user some feedback that their comments are saved.
        alert('keyup handler');
        var text = e.target.value;
        if (text.length < 10) {
            return;
        } else {
            // read oldText from cache or from localStorage, and if it has not changed, don't do anything.
            oldText = oldText || window.localStorage.getItem(localStorageKey);
            if (oldText === text) {
                return;
            }
        }

        window.localStorage.setItem(localStorageKey, e.target.value);

        loadButton.remove();
        if (e.target.value === '') {
            infoAlert.remove();
            e.target.insertAdjacentElement('afterend', noSavedComment);
        } else {
            noSavedComment.remove();
            e.target.insertAdjacentElement('afterend', infoAlert);
        }
    });

    // inject a button that will load the comment
    window.setInterval(function() {
        var commentBox = document.querySelector(commentBoxSelector);
        if (!commentBox || commentBox.dataset.ijgDecorated) {
            return;
        } else {
            commentBox.dataset.ijgDecorated = true;
            // insert the load button if the comment box is empty
            if (commentBox.value === '' && window.localStorage.getItem(localStorageKey)) {
                commentBox.insertAdjacentElement('afterend', loadButton);
            }
        }
    }, 1000);

    function createLoadButton() {
        var loadButton = document.createElement('div');
        loadButton.classList = 'btn btn-lg bcs-button ijg-bcs-loadButton';
        loadButton.style.position = 'absolute';
        loadButton.style.top = '20px';
        loadButton.style.left = '50%';
        loadButton.style.transform = 'translateX(-50%)';
        loadButton.onclick = loadButtonClickHandler;
        loadButton.textContent = "Load comment from localStorage";
        return loadButton;
    }

    function createInfoAlert() {
        var alertDiv = document.createElement('div');
        alertDiv.classList = 'alert alert-info pull-left';
        alertDiv.style.marginTop = '10px';
        alertDiv.style.marginBottom = '-7px';
        alertDiv.textContent = 'Your comment is saved in localStorage!';
        return alertDiv;
    }

    function createNoSavedCommentAlert() {
        var infoDiv = createInfoAlert();
        infoDiv.classList = 'alert alert-warning pull-left';
        infoDiv.textContent = 'No comments in localStorage...';
        return infoDiv;
    }

    function loadButtonClickHandler() {
        // when the load button is clicked, the contents of the comment box are replaced
        // and the button disappears permanently
        document.querySelector(commentBoxSelector).value = window.localStorage.getItem(localStorageKey);
        loadButton.remove();
    }
})();

(function streamlineLogin() {
    /// There is a pointless mandatory "click to login" button. Click that right away.
    /// Then wait for the browser's autofill and

    var loginWithUsernameIntervalId;
    var loginFormIntervalId;
    var autofillIntervalId;

    var autofillWaitingPeriodInMS = 1000;
    var autofillWaitingPeriodStartTime;

    loginWithUsernameIntervalId = window.setInterval(attemptClickLoginWithUsername, 100);

    /// Phase 1: wait for the first form to appear, or confirm that it will never appear.
    function attemptClickLoginWithUsername() {
        var loginWithUsernameButton = document.querySelector('.landing .btn-login');

        if (loginWithUsernameButton === null) {
            // Either the page isn't loaded yet, or we're already logged in.
            // If already logged in, clear all intervals and exit.
            if ($('.header-menu .logout').length) {
                window.clearInterval(loginWithUsernameIntervalId);
            }
        } else {
            // The button is there. This function is done. Onward to the next phase.
            window.clearInterval(loginWithUsernameIntervalId);
            loginWithUsernameButton.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true}));
            loginFormIntervalId = window.setInterval(waitForLoginForm, 100);
        }
    }

    /// Phase 2: wait for the second form to appear.
    function waitForLoginForm() {
        var loginButton = document.querySelector('.landing button[type="submit"]');
        var usernameField = document.getElementById('email');
        var passwordField = document.getElementById('password');

        if (loginButton !== null && usernameField !== null && passwordField !== null) {
            // Onward to next phase.
            window.clearInterval(loginFormIntervalId);
            autofillIntervalId = window.setInterval(waitForAutofill, 100);
        }

        if ($('.header-menu .logout').length) {
            window.clearInterval(loginFormIntervalId);
        }
        // if this function is fired once, it is guaranteed that it will hit its success
        // condition and clear its own interval.
    }

    /// Phase 3: wait for autofill to kick in, or timeout and exit.
    function waitForAutofill() {
        var loginButton = document.querySelector('.landing button[type="submit"]');
        var usernameField = document.querySelector('#email');
        var passwordField = document.querySelector('#password');

        autofillWaitingPeriodStartTime = autofillWaitingPeriodStartTime || new Date();
        var now = new Date();

        if (now.getTime() > autofillWaitingPeriodStartTime.getTime() + autofillWaitingPeriodInMS) {
            // timeout triggered. Exit program.
            window.clearInterval(autofillIntervalId);
        } else if (usernameField.value.length > 0) {
            // autofill worked. Program completed successfully.
            window.clearInterval(autofillIntervalId);
            var style = loginButton.style;
            style.position = 'fixed';
            style.width = style.height = '100%';
            style.top = style.left = 0;
            style.fontSize = '30vmin';
            loginButton.onmousemove = hoverHandler;
        }
    }

    function hoverHandler() {
        // this only works on Firefox because of a Chrome security "feature"
        var loginButton = document.querySelector('.landing button[type="submit"]');
        loginButton.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true}));
    }
})();