BKeL(old) Improve Courses Display In Dashboard

Add ability to collapse/expand each courses section. Automatically expand the first section while collapsing everything else. Also sort courses in each section by name.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         BKeL(old) Improve Courses Display In Dashboard
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Add ability to collapse/expand each courses section. Automatically expand the first section while collapsing everything else. Also sort courses in each section by name.
// @author       ntpt7921
// @match        http://e-learning.hcmut.edu.vn/my/
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license      MIT
// ==/UserScript==

/* I create this script for my personnal use.
 * Javascript is not my forte. So not sure if this script is optimized.
 * Anyone may use it freely.
 * No warranty, may break, use with caution
 */

(function() {
    'use strict';

/*
 * This script will attempt to add section visibility tooggle, performing:
 * 1. Read section header (<li> elements) and its content (<ul> of courses)
 * 2. Add display='block' to each section content, also change bottom-margin=1.8em
 * 3. Add event for each section header so that clicking it toggles its content visibility
 * 4. It will by default show the first section and hide the rest
 * 5. It will also sort the course by name for each section
 */

    function generate_section_objects() {
        let sectionHeaderList = document.getElementsByClassName('category-course')
        let dict = { sectionHeader: [],
                    sectionContent: [] }
        for (let sectionHeader of sectionHeaderList) {
            let sectionContent = sectionHeader.nextElementSibling
            dict['sectionHeader'].push(sectionHeader)
            dict['sectionContent'].push(sectionContent)
        }

        return dict
    }

    function modify_sections(sectionMap) {
        let sectionNumber = sectionMap['sectionHeader'].length
        for (let i = 0; i < sectionNumber; i++) {
            let sectionHeader = sectionMap['sectionHeader'][i]
            let sectionContent = sectionMap['sectionContent'][i]
            set_section_content_style(sectionContent)
            add_event_toggle_visibility(sectionHeader, sectionContent)
        }
    }

    function display_first_section_only(sectionMap) {
        let sectionNumber = sectionMap['sectionHeader'].length
        if (sectionNumber > 0) {
            // show the first section
            sectionMap['sectionContent'][0].style.display = 'block'
            // hide all other section
            for (let i = 1; i < sectionNumber; i++)
                sectionMap['sectionContent'][i].style.display = 'none'
        }
    }

    function set_section_content_style(sectionCont) {
        sectionCont.style.display = 'block'
        sectionCont.style.marginBottom = '1.8em'
    }

    function add_event_toggle_visibility(sectHeader, sectContent) {
        function toggle_visibility(sectionContent) {
            if (sectionContent.style.display === 'block')
                sectionContent.style.display = 'none'
            else
                sectionContent.style.display = 'block'
        }

        sectHeader.onclick = function () { toggle_visibility(sectContent) }
        sectHeader.onmouseover = function () { sectHeader.style.color = '#111111' }
        sectHeader.onmouseout = function () { sectHeader.style.color = '#545353' }
        sectHeader.style.cursor = 'pointer'
    }

    function sort_section_courses(sectMap) {
        function extractText(liElem) {
            let target = liElem.querySelector('.aalink.coursename')
            return target.innerText
                .replace(/[\s\n]+/gm, ' ')
                .trim()
        }

        for (let section of sectMap['sectionContent']) {
            const frag = document.createDocumentFragment()
            const items = section.querySelectorAll('li')
            const sortedItems = Array.from(items).sort(function(a, b){
                const aData = extractText(a), bData = extractText(b)
                return (aData < bData) ?
                    -1 : (aData > bData) ?
                    1 : 0
            })

            for (let item of sortedItems) {
                frag.appendChild(item)
            }
            section.appendChild(frag)
        }
    }

/*
 * The part below will refresh (rerun the command incase of content of section changes)
 * It uses MutationObserver (https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
 * See also https://stackoverflow.com/questions/12897446/userscript-to-wait-for-page-to-load-before-executing-code-techniques
 */

    (new MutationObserver(check)).observe(document.getElementById('region-main'),
                                          {childList: true, subtree: true}
                                         );

    function check(changes, observer) {
        if (document.getElementById('page-container-2')) {
            // disconect since we also change the content, which will trigger observer again recursively
            observer.disconnect()

            // the magic happens here
            let sectList = generate_section_objects()
            modify_sections(sectList)
            display_first_section_only(sectList)
            sort_section_courses(sectList)

            // reconnect observer
            observer.observe(document.getElementById('region-main'),
                             {childList: true, subtree: true}
                            );
        }
    }


})();