BootCampSpot v2 Improvements

Streamlined login experience and localStorage-based preservation of comments.

当前为 2018-03-12 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         BootCampSpot v2 Improvements
// @namespace    https://jonas.ninja
// @version      1.1.2
// @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.
        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}));
    }
})();