中原iLearning 2.0 頁面體驗增強

在中原iLearning 2.0 課程頁依種類分類課程段落,提供新分頁開啟PDF教材的功能,提供下載影片的功能

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         中原iLearning 2.0 頁面體驗增強
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  在中原iLearning 2.0 課程頁依種類分類課程段落,提供新分頁開啟PDF教材的功能,提供下載影片的功能
// @icon         data:image/png;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKHzzACd78xkoffM3LIH0MD+N9Qcjf/QALof1ICuH9TguivUrOpL2A4nA+gE0k/YnLI/2ODOT9ywAAAAAAAAAACh88wAne/NuKH3z9iyB9NQ/jfUdI4D0AC6H9Y4rh/X5Lor1vjqS9g2JwPoFNZP2rCyP9vczk/fEZJb1AmOV9QYqfvMAJ3zzdCh98/8sgfTgP431HySA9AAuiPWWK4f1/y6K9ck6kvYNicD6BTWT9rYsj/b/M5T3zzt68yI0dfIxLXrzACd883QoffP/LIH04D+N9R8kgPQAL4j1liuH9f8uivXJOpL1DYnA+gU1k/a2LY/2/zOU9880dfJNMnPySSp38gAnfPN0KH7z/yyC9eBAjvYfJID1AC+I9ZYrh/X/Lov1yTuT9g2JwPoFNZP2ti2P9v8zlPfPO3rzPDt68y8uevIAKHzzdCl98P8ugO7iQovtIRt57wAviPWYK4f1/y6L9cs8k/YOf7v6BTST9rcsj/b/M5T3zzt69BU/fPQQRWyrAFV0oHpTa4//R1l1+EJFTKlET2FXMIj0xCuH9f8ui/XnPpX2QUCY9y8wkPbWLI/2/zOU975mjdgedoy3LpKNiB9hXl2ZT0xM/0VCQ/80MjT/Ky00+S1foP0th/P9LIn1/y6M9eoujvbmLI72/y+Q9vQ7mPdpkJ++G3mEnnxqb33QWlxl9UtLUP9BQEP/NTQ3/ygnKf8qMT7TOYHZczKN960vjPXcL4723jCQ9r84lfZhV6b4CHR8jwB9hpwGZmx8PFVaaZlFS1rfOzxF+y8uMf8rKS3/NDI1nVBCNhBMqv8GRZj2GUaa9xtLnvcLxOH8AH+6+QAAAAAAAAAAAG1mXQB+c2MDV1VWIUVER1g3NjmNLSwwtSkoLM00MzaVVFNUHzc2OQDHxcIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAClpaYA4uLiAFZVWAY6Oj0TODY6JFVUVhUFBAkAw8LCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AADhAAAA4QAAACEAAAAhAAAAIQAAACEAAAAgAAAAAAAAAAAAAACAAQAA4B8AAPwfAAD//wAA//8AAA==
// @match        *://ilearning.cycu.edu.tw/*
// @grant        none
// @run-at       document-end
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    const config = {
        "討論區": {
            "title": "討論區",
            "logo": "https://ilearning.cycu.edu.tw/theme/image.php/boost_union/forum/1744246650/monologo?filtericon=1"
        },
        "作業": {
            "title": "作業",
            "logo": "https://ilearning.cycu.edu.tw/theme/image.php/boost_union/assign/1744246650/monologo?filtericon=1"
        },
        "檔案": {
            "title": "檔案",
            "logo": "https://ilearning.cycu.edu.tw/theme/image.php/boost_union/resource/1757814017/monologo"
        },
        "PDF Annotation": {
            "title": "PDF檔",
            "logo": "https://ilearning.cycu.edu.tw/theme/image.php/boost_union/pdfannotator/1744246650/monologo?filtericon=1"
        },
        "超級影片": {
            "title": "影片檔",
            "logo": "https://ilearning.cycu.edu.tw/theme/image.php/boost_union/supervideo/1744246650/monologo?filtericon=1"
        },
        "網址": {
            "title": "網址",
            "logo": "https://ilearning.cycu.edu.tw/theme/image.php/boost_union/url/1757814017/monologo?filtericon=1"
        },
        "回饋單": {
            "title": "問卷",
            "logo": "https://ilearning.cycu.edu.tw/theme/image.php/boost_union/feedback/1744246650/monologo?filtericon=1"
        },
        "頁面": {
            "title": "文章",
            "logo": "https://ilearning.cycu.edu.tw/theme/image.php/boost_union/page/1757814017/monologo?filtericon=1"
        }
    };

    const order = ["頁面", "討論區", "作業", "PDF Annotation", "檔案", "超級影片", "網址", "回饋單"];

    function extractFullUrl() {
        const scripts = document.scripts;
        for (let script of scripts) {
            const match = script.textContent.match(/"fullurl":\s*"([^"]+)"/);
            if (match) {
                return match[1].replace(/\\/g, ''); // 去除反斜線
            }
        }
        return null;
    }

    function createDownloadButton(fullUrl) {
        const button = document.createElement('button');
        button.id = 'pdf-download-btn';
        button.title = '下載 PDF';
        button.innerHTML = '➜'; // 下載符號
        button.onclick = function () {
            window.open(fullUrl, '_blank');
        };

        Object.assign(button.style, {
            position: 'fixed',
            bottom: '8rem',
            right: '2rem',
            width: '36px',
            height: '36px',
            backgroundColor: 'orange',
            color: 'white',
            border: 'none',
            borderRadius: '50%',
            fontSize: '26px',
            padding: '0 0 2px 0',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            cursor: 'pointer',
            boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.1)',
            transform: 'rotate(90deg)'
        });

        document.body.appendChild(button);
    }

    function getCookie(name) {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) return parts.pop().split(';').shift();
        return null;
    }

    function setCookie(name, value, days = 30) {
        const date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/`;
    }

    function getMoremenu() {
        const ul = document.querySelector('ul.nav.more-nav.nav-tabs');
        if (ul) {
            const li = document.createElement('li');
            li.className = 'nav-item';

            const a = document.createElement('a');
            a.className = 'nav-link';
            a.setAttribute('role', 'menuitem');
            a.setAttribute('style', 'color:#FF359A !important;');
            a.textContent = '★精簡化';
            a.onclick = showMenu;

            li.appendChild(a);
            ul.appendChild(li);
        }
    }

    function getItems() {
        const result = {};

        // 取得課程 ID(從網址中抓取 ?id= 後的數字)
        const courseIdMatch = window.location.href.match(/course\/view\.php\?id=(\d+)/);
        const courseId = courseIdMatch ? courseIdMatch[1] : null;

        if (!courseId) return result;

        let sections = [];
        let main_section = {};

        for (let i = 0; i < sessionStorage.length; i++) {
            const key = sessionStorage.key(i);
            if (key.includes(`${courseId}/staticState`)) {
                try {
                    const json = JSON.parse(sessionStorage.getItem(key));
                    if (json) {
                        if (Array.isArray(json.section)) {
                            sections = json.section;
                            for (const sec of json.section) {
                                main_section[sec.id] = sec.parentsectionid || sec.id;
                            }
                        }
                        console.log(main_section);
                        if (Array.isArray(json.cm)) {
                            for (const item of json.cm) {

                                // 跳過子單元
                                if (item.modname === "子單元") continue;

                                // 處理子單元歸位
                                item.sectionid = main_section[item.sectionid];
                                item.sectionnumber = item.sectionnumber = sections.findIndex(section => section.id === item.sectionid);

                                const mod = item.modname;
                                if (!result[mod]) result[mod] = [];
                                result[mod].push(item);
                            }
                        }

                        console.log(result);

                    }
                } catch (e) {
                    console.error('JSON 解析錯誤:', e);
                }
            }
        }

        // 按照指定順序排序
        const sortedResult = {};
        for (const mod of order) {
            if (result[mod]) {
                sortedResult[mod] = result[mod];
            }
        }

        // 添加未在order中定義的類型
        for (const mod in result) {
            if (!sortedResult[mod]) {
                sortedResult[mod] = result[mod];
            }
        }

        // 按照周次排序
        for (const mod in sortedResult) {
            if (mod === "討論區") {
                sortedResult[mod].sort((a, b) => {
                    if (a.sectionnumber === b.sectionnumber) {
                        return b.id - a.id;
                    }
                    if (a.sectionnumber === Math.min(...sortedResult[mod].map(i => i.sectionnumber))) return -1;
                    if (b.sectionnumber === Math.min(...sortedResult[mod].map(i => i.sectionnumber))) return 1;
                    return b.sectionnumber - a.sectionnumber;
                });
            } else {
                sortedResult[mod].sort((a, b) => b.sectionnumber - a.sectionnumber);
            }
        }

        return { items: sortedResult, sections };
    }

    function showMenu() {
        const original = document.querySelector('ul.weeks');
        const side = document.querySelector('#menuside');

        if (original) {
            original.style.display = 'none';

            const data = getItems();
            const items = data.items;
            const sections = data.sections;
            let container = null;
            if (side) {
                container = side;
                container.innerHTML = "";
            }
            else {

                container = document.createElement('ul');
                container.className = 'weeks';
                container.id = 'menuside';
                container.setAttribute('data-for', 'course_sectionlist');

            }

            // 添加排序選擇器
            const sortContainer = document.createElement('div');
            sortContainer.className = 'mb-3';
            sortContainer.innerHTML = `
                <select class="form-select" id="week-sort-order">
                    <option value="desc">降序</option>
                    <option value="asc">升序</option>
                </select>
            `;

            // 設置默認值
            const savedOrder = getCookie('weekSortOrder') || 'desc';
            sortContainer.querySelector('#week-sort-order').value = savedOrder;

            // 監聽變化
            sortContainer.querySelector('#week-sort-order').addEventListener('change', function () {
                setCookie('weekSortOrder', this.value);
                sortContainer.innerHTML = "";
                showMenu(); // 重新渲染
            });


            let sectionNum = 1;

            // 獲取當前周次
            const currentWeekSection = sections.find(s => s.current);

            for (const modname in items) {
                const section = document.createElement('li');
                section.className = 'section course-section main clearfix';
                section.id = `side-section-${sectionNum}`;

                // 按照選擇的排序方式處理周次
                const weekItems = {};
                for (const item of items[modname]) {
                    if (!weekItems[item.sectionnumber]) {
                        weekItems[item.sectionnumber] = [];
                    }
                    weekItems[item.sectionnumber].push(item);
                }
                console.log(items)
                console.log(weekItems)



                // 獲取所有周次並排序
                let weekNumbers = Object.keys(weekItems).map(Number);

                // 第0周始終置頂
                const week0 = weekNumbers.includes(0) ? [0] : [];
                const otherWeeks = weekNumbers.filter(w => w !== 0);

                // 按照選擇的排序方式排序
                const sortOrder = getCookie('weekSortOrder') || 'asc';
                if (sortOrder === 'asc') {
                    otherWeeks.sort((a, b) => a - b);
                } else {
                    otherWeeks.sort((a, b) => b - a);
                }

                // 將當前周次提前
                let sortedWeeks = week0;
                if (currentWeekSection && otherWeeks.includes(currentWeekSection.sectionnumber)) {
                    sortedWeeks.push(currentWeekSection.sectionnumber);
                    sortedWeeks = sortedWeeks.concat(otherWeeks.filter(w => w !== currentWeekSection.sectionnumber));
                } else {
                    sortedWeeks = sortedWeeks.concat(otherWeeks);
                }

                let sectionHTML = `
                <div class="section-item">
                    <div class="course-section-header d-flex">
                        <div class="d-flex align-items-center position-relative">
                            <a role="button" class="btn btn-icon me-3 icons-collapse-expand justify-content-center collapsed" aria-expanded="false" href="#side-coursecontentcollapse${sectionNum}" data-for="sectiontoggler" data-toggle="collapse">
                                <span class="expanded-icon icon-no-margin p-2" title="展延">
                                    <i class="icon fa fa-chevron-down fa-fw" aria-hidden="true"></i>
                                    <span class="sr-only">展延</span>
                                </span>
                                <span class="collapsed-icon icon-no-margin p-2" title="展延">
                                    <span class="dir-rtl-hide"><i class="icon fa fa-chevron-right fa-fw" aria-hidden="true"></i></span>
                                    <span class="dir-ltr-hide"><i class="icon fa fa-chevron-left fa-fw" aria-hidden="true"></i></span>
                                    <span class="sr-only">展延</span>
                                </span>
                            </a>
                            <h3 class="h4 sectionname course-content-item d-flex align-self-stretch align-items-center mb-0" id="side-sectionid-${sectionNum}-title">
                                ${config[modname]?.title || modname}
                            </h3>
                        </div>
                    </div>
                    <div id="side-coursecontentcollapse${sectionNum}" class="content course-content-item-content collapse">
                        <div class="my-3" data-for="sectioninfo">
                            <div class="section_availability"></div>
                        </div>
                        <ul class="section m-0 p-0 img-text d-block" data-for="cmlist">`;

                for (const week of sortedWeeks) {
                    const weekItemsList = weekItems[week];
                    if (!weekItemsList) continue;

                    let weekTitle = `第${week}週`;
                    if (week === 0) {
                        weekTitle = "公告";
                    }

                    // 添加當前周標記
                    let currentBadge = "";
                    let currentCSS = "";
                    if (currentWeekSection.id == weekItemsList[0].sectionid) {
                        currentBadge = '<span class="badge rounded-pill bg-primary text-white order-1 ms-2">本週</span>';
                        currentCSS = "course-content current";
                    }

                    sectionHTML += `
                    <li class="activity activity-wrapper pdfannotator modtype_pdfannotator hasinfo" data-indexed="true">
                        <div class="week-title fw-bold fs-5 mb-2">${weekTitle}${currentBadge}</div><div class="${currentCSS}">`;

                    for (const item of weekItemsList) {
                        // 確定圖標
                        let logoUrl = config[modname]?.logo;
                        if (modname === "PDF Annotation") {
                            logoUrl = config["PDF Annotation"].logo;
                        }

                        sectionHTML += `<div class="activity-item focus-control mb-2">
                                        <div class="activity-grid">
                                            <div class="activity-icon activityiconcontainer smaller communication courseicon align-self-start me-2">
                                                ${logoUrl ? `<img src="${logoUrl}" class="activityicon">` : ''}
                                            </div>
                                            <div class="activity-name-area activity-instance d-flex flex-column me-2">
                                                <div class="activitytitle modtype_pdfannotator position-relative align-self-start">
                                                    <div class="activityname">
                                                        <a href="${item.url}" class="aalink stretched-link">
                                                            <span class="instancename">${item.name}</span>
                                                        </a>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>`;
                    }

                    sectionHTML += `</div></li>`;
                }

                sectionHTML += `</ul>
                    </div>
                </div>`;

                section.innerHTML = sectionHTML;
                container.appendChild(section);
                sectionNum++;
            }

            original.parentNode.insertBefore(container, original.nextSibling);
            original.parentNode.insertBefore(sortContainer, original.nextSibling);
        }
    }

    function handleVideoPage() {
        // 查找MP4連結
        const mp4Regex = /https:\/\/[^"]+\.mp4/g;
        let mp4Url = null;
        let doc = document.documentElement.innerHTML;
        const match = doc.match(mp4Regex);
        if (match && match.length > 0) {
            mp4Url = match[0];
        }

        if (mp4Url) {
            // 創建下載按鈕
            const button = document.createElement('button');
            button.id = 'video-download-btn';
            button.title = '下載影片';
            button.innerHTML = '➜';
            button.onclick = function () {
                const link = document.createElement('a');
                link.href = mp4Url;
                link.download = '';
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                if (confirm('停留此頁面將降低下載速率,是否離開此頁面?')) {
                    window.location.href = 'https://ilearning.cycu.edu.tw/my/courses.php';
                } else {
                }
            };

            Object.assign(button.style, {
                position: 'fixed',
                bottom: '8rem',
                right: '2rem',
                width: '36px',
                height: '36px',
                backgroundColor: 'blue',
                color: 'white',
                border: 'none',
                borderRadius: '50%',
                fontSize: '26px',
                padding: '0 0 2px 0',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                cursor: 'pointer',
                boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.1)',
                transform: 'rotate(90deg)'
            });

            document.body.appendChild(button);
        }
    }

    function init() {
        const url = window.location.href;

        if (/^https:\/\/ilearning\.cycu\.edu\.tw\/mod\/pdfannotator\//.test(url)) {
            const fullUrl = extractFullUrl();
            if (fullUrl) {
                createDownloadButton(fullUrl);
            }
        } else if (/^https:\/\/ilearning\.cycu\.edu\.tw\/(mod\/supervideo\/|mod\/resource\/)/.test(url)) {
            console.log("找尋影片網址中...");
            handleVideoPage();
        } else if (/^https:\/\/ilearning\.cycu\.edu\.tw\/course\//.test(url)) {
            console.log("找尋影片網址中...");
            getMoremenu();
        }
    }

    window.addEventListener('load', init);
})();