BootCampSpot v2 Improvements

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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);
})();