BootCampSpot v2 Improvements

Streamlined login experience, autosaving of comments, and better attendance colors.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         BootCampSpot v2 Improvements
// @namespace    https://jonas.ninja
// @version      1.3.0
// @description  Streamlined login experience, autosaving of comments, and better attendance colors.
// @author       @iv_njonas
// @match        https://bootcampspot-v2.com/*
// @grant        GM_addStyle
// ==/UserScript==
/* jshint multistr: true */

(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.
        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 verticalResizeOnly() {
    GM_addStyle('#homework-panel #commentEntry{resize:vertical}');
})();

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

    var loginWithUsernameIntervalId;
    var loginFormIntervalId;
    var autofillIntervalId;

    var autofillWaitingPeriodInMS = 1000;
    var autofillWaitingPeriodStartTime;

    var autofillHackCSS = '\
input:-webkit-autofill {\
    animation-name: autofillHack;\
}\
@keyframes autofillHack {\
    from {} to {}\
}';

    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() {
        /// There are two strategies to detect autofill, targeted toward Firefox and Chrome.
        /// For Chrome: use a CSS animation hack to detect autofill.
        /// For Firefox: read the un/pw field and check for content.

        var usernameField = document.querySelector('#email');
        var passwordField = document.querySelector('#password');

        if (autofillWaitingPeriodStartTime === undefined) {
            // This function may run multiple times. Initialize the startTime and add our hack styles only once.
            autofillWaitingPeriodStartTime = new Date();

            // setup the Chrome autofill detection
            GM_addStyle(autofillHackCSS);
            $(passwordField).on('animationstart', handleAutofill); // assume that both pw and un fields are filled...
        }

        var now = new Date();

        if (now.getTime() > autofillWaitingPeriodStartTime.getTime() + autofillWaitingPeriodInMS) {
            // timeout triggered
            window.clearInterval(autofillIntervalId);
            // end of program.
        } else if (usernameField.value.length > 0 && passwordField.value.length > 0) {
            // detected Firefox autofill
            window.clearInterval(autofillIntervalId);
            makeLoginButtonEasyToClick();
        }
    }

    function handleAutofill(e) {
        if (e.originalEvent.animationName === 'autofillHack') {
            // autofill kicked in, as evidenced by the autofillHack animation.
            $('.landing input[type=password]').off('animationstart', handleAutofill);
            makeLoginButtonEasyToClick();
        }
    }

    function makeLoginButtonEasyToClick() {
        var loginButton = document.querySelector('.landing button[type="submit"]');

        var style = loginButton.style;
        style.position = 'fixed';
        style.width = style.height = '100%';
        style.top = style.left = 0;
        style.fontSize = '30vmin';

        // attempt a click right away. It may or may not work.
        loginButton.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true}));

        // if it does not work, try to do it as soon as possible. That probably means the cursor moving across the screen.
        loginButton.onmousemove = function() {
            // this only works on Firefox because of a Chrome security "feature"
            loginButton.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true}));
        };
        // end of program.
    }
})();

(function colorizeAttendanceDropdowns() {
    /// The tiny color bullets are hard to see. Make them easier to see.

    var styles = '\
.dropdown-toggle.attendance-dropdown {\
    position: relative;\
    padding-left: 26px;\
    overflow: hidden;\
}\
\
.fa-circle {\
    width: 18px;\
    height: 35px;\
    top: -1px;\
    left: -2px;\
    position: absolute;\
    background-color: currentColor;\
}\
\
.fa-circle.Pending {\
    background-color: white;\
    color: white;\
}';
    GM_addStyle(styles);
})();