HKU moodle helper

This userscript allows HKU students to show your current courses (in a semester) in a separate entry in HKU Moodle. By: Andrew Z, converted to userscript by q234rty

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         HKU moodle helper
// @include      http://moodle.hku.hk/*
// @include      https://moodle.hku.hk/*
// @version      1.4.7
// @description  This userscript allows HKU students to show your current courses (in a semester) in a separate entry in HKU Moodle. By: Andrew Z, converted to userscript by q234rty
// @author       AENeuro, q234rty, taogoddd
// @resource     mystyle https://cdn.jsdelivr.net/gh/AENeuro/HKU-Moodle-Helper@ede423d/myStyle.css
// @resource     fontawesome https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css
// @license      CC BY-NC 4.0
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @namespace https://greasyfork.org/users/78076
// ==/UserScript==
globalThis.addFeedbackBox = function() {
    function showTextArea() {
      document.getElementById("helperFeedbackForm").classList.add("helper-shown")
      document.getElementById("helperFeedbackButton").insertAdjacentHTML("beforebegin", `
        <div>
          <p id="helperFeedbackButton2" style="color: #AAAAAA;">Check the
            <a href="https://github.com/AENeuro/HKU-Moodle-Helper" target="_blank">
              <span style="color: #AAAAAA;"><u>FAQ</u></span>
            </a>
          or submit an issue or PR on
            <a href="https://github.com/AENeuro/HKU-Moodle-Helper" target="_blank">
              <span style="color: #AAAAAA;"><u>Github</u></span>
            </a>
          </p>
        </div>
      `)
      document.getElementById("helperFeedbackButton").remove()
    }

    async function sendFeedback() {
      if (!document.getElementById("helperFeedbackInput").value) {
        return 0
      }
      document.getElementById("helperFeedbackSend").disabled = true
      try{
        await request({
          url: "	https://j8n6ydl8hd.execute-api.ap-southeast-1.amazonaws.com/create",
          method: "POST",
          body: document.getElementById("helperFeedbackInput").value
        })
      } catch(e) {
        alert("Network error")
      }
      document.getElementById("helperFeedbackForm").classList.remove("helper-shown")
      document.getElementById("helperFeedbackForm").insertAdjacentHTML("beforebegin", `
        <p style="color: #AAAAAA">Thank you for your feedback!</p>
      `)
      document.getElementById("helperFeedbackButton2").remove()
    }


    // initialization

    document.getElementsByClassName("course-of-sem-wrapper")[0].insertAdjacentHTML("beforeend",`
      <div class="helper-feedback">
        <p>Powered by HKU Moodle Helper ver. 1.4.7</p>
        <p id="helperFeedbackButton">Feedback</p>
        <div id="helperFeedbackForm" class="helper-hidden">
          <input id="helperFeedbackInput" type="text" placeholder="Email [Optional] + issue"/><br/>
          <button id="helperFeedbackSend">Send</button>
        </div>
      </div>
    `)
    document.getElementById("helperFeedbackButton").addEventListener("click", showTextArea)
    document.getElementById("helperFeedbackSend").addEventListener("click", sendFeedback)
  }
globalThis.addMessageBox = function () {
    const messageBox = `
      <section class="helper-extension-persistent helper-message-box block_html block card mb-3" role="complementary" data-block="html" aria-labelledby="instance-330654-header">
        <div class="card-body p-3">
          <h5 class="card-title d-inline">Message from HKU Moodle Helper</h5>
          <div class="card-text content mt-3">
            <div class="no-overflow">
              <p><span style="font-size:11.0pt;font-family:&quot;Calibri&quot;,sans-serif;color:black">
                This is a message generated by the chrome extension <i>HKU Moodle Helper</i> that you installed.
              </span></p>
              <p><span style="font-size:11.0pt;font-family:&quot;Calibri&quot;,sans-serif;color:black">
                As many of you have noticed, moodle underwent renovation, and it's unclear just how it would affect the extension yet.
              </span></p>
              <p><span style="font-size:11.0pt;font-family:&quot;Calibri&quot;,sans-serif;color:black">
                The extension will still be maintained, provided it's still relevant in new semesters to come.
                In the meantime, please condider becoming a dev in <a href="https://github.com/AENeuro/HKU-Moodle-Helper" target="_blank">HKU Moodle Helper</a>.
                Any PR or suggestions are welcomed of course.
              </span></p>
            </div>
            <div class="footer"></div>
          </div>
        </div>
      </section>
    `

    document.getElementById("block-region-side-post").firstChild.insertAdjacentHTML("beforebegin", messageBox);
  }
  const request = obj => {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open(obj.method || "GET", obj.url);
        if (obj.headers) {
            Object.keys(obj.headers).forEach(key => {
                xhr.setRequestHeader(key, obj.headers[key]);
            });
        }
        xhr.onload = () => {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.response);
            } else {
                reject(xhr.statusText);
            }
        };
        xhr.onerror = () => reject(xhr.statusText);
        xhr.send(JSON.stringify(obj.body));
    });
  };
(function(){
        // Note: every element that is to be removed during a clearing session
    // should be marked with a "helper-extension" classname
    // Otherwise it should be marked with "helper-extension-persistent"

    // Code splitting was done through globalThis (which was confined within ContentScript. Thus no pollutions were made)
    const my_css = GM_getResourceText("mystyle");
    GM_addStyle(my_css);
    const fontawesome = GM_getResourceText("fontawesome");
    GM_addStyle(fontawesome)
    mainFunction();

    async function mainFunction() {
      //addCssByLink("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css");
      await addCourseOfSem();
      globalThis.addFeedbackBox();
      globalThis.addMessageBox();
    }

    async function addCourseOfSem() {
    courseElements = new Array();
    courseList = JSON.parse(GM_getValue("courselist", "[]"))
    clearAll();
    var courses = document.getElementsByClassName("coursebox");
    pagePath = window.location.pathname;
    for (var i = 0; i < courses.length; i++) {
        currentCourseID = courses[i].dataset.courseid;
        var included = false;
        if (courseList) {
        included = courseList
            .map((value, index, array) => {
            return value.courseID;
            })
            .includes(currentCourseID);
        }
        if (included) {
        //如果在列表中
        //复制element,存入数组
        if (pagePath != "/course/search.php") {
            courseElements.push({
            courseID: currentCourseID,
            courseHTML: courses[i].cloneNode(true),
            });
        }

        // Applies to all courses on the page that is in the list (in "my courses" section)
        courses[i].lastChild.lastChild.insertAdjacentHTML(
            "beforebegin",
            `
            <button class="helper-extension helper-remove-button" id="removeCourse${currentCourseID}">
            Remove from this semester
            </button>
        `
        );
        document
            .getElementById("removeCourse" + currentCourseID)
            .addEventListener("click", function (e) {
            removeCourse(e.target.id.slice(12), courseList);
            });
        } else {
        // Applies to all courses on the page that is not in the list (in "my courses" section)
        courses[i].lastChild.lastChild.insertAdjacentHTML(
            "beforebegin",
            `
            <button class="helper-extension helper-add-button" id="addCourse${currentCourseID}" >
            Add to this semester
            </button>
        `
        );
        document
            .getElementById("addCourse" + currentCourseID)
            .addEventListener("click", function (e) {
            courseInfo = extractInfo(LocateCourse(currentCourseID, courses));
            if (pagePath == "/course/search.php")
                addCourse(pagePath, e.target.id.slice(9), courseList, courseInfo);
            else addCourse(pagePath, e.target.id.slice(9), courseList);
            });
        }
    }
    // addcourses buttons for side bars

    var sidebarlist = document.getElementsByClassName("column c1");
    for (var i = 0; i < sidebarlist.length; i++) {
        //  id comes from the ref: https://moodle.hku.hk/course/view.php?id=xxx
        var included = false;
        const id = sidebarlist[i].firstChild.href.slice(41);
        if (courseList) {
        included = courseList
            .map((value, index, array) => {
            return value.courseID;
            })
            .includes(id);
        }
        const text = sidebarlist[i].removeChild(sidebarlist[i].lastChild);
        const anchor = document.createElement("span");
        sidebarlist[i].appendChild(anchor);
        sidebarlist[i].firstChild.insertAdjacentHTML(
        "afterEnd",
        `<div class="helper-extension helper-sidebar-wrapper">
            <div class="helper-extension helper-sidebar-button-${
                included ? "minus" : "plus"
            }" id="sidebarbtn${id}" title='${
            included ? "remove from" : "add to"
        } this semester' >${included ? "×" : "+"}</div>
            </div>`
        );
        sidebarlist[i].removeChild(anchor);
        sidebarlist[i].lastChild.insertAdjacentElement("beforeBegin", text);

        if (included) {
        document
            .getElementById("sidebarbtn" + id)
            .addEventListener("click", function (e) {
            removeCourse(e.target.id.slice(10), courseList);
            });
        } else {
        document
            .getElementById("sidebarbtn" + id)
            .addEventListener("click", function (e) {
            addCourse("/course/search.php", e.target.id.slice(10), courseList, {
                title: text.innerText,
                teachers: "",
            });
            });
        }
    }

    var outerContainer = document.getElementById("frontpage-course-list");

    if (courseList && courseList.length && outerContainer) {
        //如果有课程
        outerContainer.insertAdjacentHTML(
        "afterBegin",
        `
        <div class="helper-extension course-of-sem-wrapper">
            <h2>
            Course of this semester
            <div id="removeAll">×</button>
            </h2>
            <div id="courseOfSem" class="courses frontpage-course-list-enrolled has-pre has-post course-of-sem"></div>
        </div>
        `
        );

        document.getElementById("removeAll").addEventListener("click", function () {
        if (confirm("Do you wish to remove all courses from this semester?")) {
            removeAll();
        }
        });
    } else {
        //没有课程
        outerContainer.insertAdjacentHTML(
        "afterbegin",
        `
        <div class="helper-extension course-of-sem-wrapper">
            <h2>Course of this semester</h2>
            <p><i>Please click 'Add to this semester' on a course to bring it here.</i></p>
        </div>
        `
        );
    }

    var innerContainer = document.getElementById("courseOfSem");
    for (var i = 0; i < courseList.length; i++) {
        /* if (i % 2) {
        //注意这里是偶数 => 这里是不能整除2(i是奇数),但是在显示顺序上是“偶数”
        courseHTML[i].className = "coursebox clearfix even";
        } else {
        courseHTML[i].className = "coursebox clearfix odd";
        } */
        // applies to all courses in this semester (in "course of this semester" section)
        currentCourseID = courseList[i].courseID;
        if (
        courseElements
            .map((value, index, array) => {
            return value.courseID;
            })
            .includes(courseList[i].courseID)
        ) {
        currentCourseHTML = courseElements.filter((value, index) => {
            return value.courseID == currentCourseID;
        })[0].courseHTML;
        currentCourseHTML.insertAdjacentHTML(
            "afterbegin",
            `
        <a id="removeCourseA${currentCourseID}" style="position: absolute; top: 5px; right: 5px; font-size: 25px; color: darkgrey; cursor: pointer">
            ×
        </a>
        `
        );
        innerContainer.appendChild(currentCourseHTML);
        document
            .getElementById("removeCourseA" + currentCourseID)
            .addEventListener("click", function (e) {
            removeCourse(e.target.id.slice(13), courseList);
            });
        } else {
        let courseDoc = new DOMParser().parseFromString(
            createCard(courseList[i]),
            "text/html"
        );
        var courseElement = courseDoc.querySelector("div");
        courseElement.insertAdjacentHTML(
            "afterbegin",
            `
        <a id="removeCourseA${currentCourseID}" style="position: absolute; top: 5px; right: 5px; font-size: 25px; color: darkgrey; cursor: pointer">
            ×
        </a>
        `
        );
        innerContainer.appendChild(courseElement);
        document
            .getElementById("removeCourseA" + currentCourseID)
            .addEventListener("click", function (e) {
            removeCourse(e.target.id.slice(13), courseList);
            });
        }
    }
    }
    // ======================================
    // Helper functions
    function addCssByLink(url) {
    var doc = document;

    var link = doc.createElement("link");

    link.setAttribute("rel", "stylesheet");

    link.setAttribute("href", url);

    var heads = doc.getElementsByTagName("head");

    if (heads.length) heads[0].appendChild(link);
    else doc.documentElement.appendChild(link);
    }
    function clearAll() {
    var clearElements = document.getElementsByClassName("helper-extension");
    //必须倒序删除,因为HTMLCollection会因为remove方法动态变化
    for (var i = clearElements.length - 1; i >= 0; --i) {
        clearElements[i].remove();
    }
    }

    function createCard(course) {
    courseID = course.courseID;
    courseInfo = course.courseInfo;
    return `<div class="coursebox clearfix odd first" data-courseid=${courseID} data-type="1">
        <div class="info">
        <h3 class="coursename">
            <a class="aalink" href="https://moodle.hku.hk/course/view.php?id=${courseID}">
            <span class="highlight">${courseInfo.title}</span>
            </a>
        </h3>
        <div class="moreinfo"></div>
        </div>
        <div class="content">
        <div class="summary">
        <h3 class="coursename">
            <a style="display: inline" href="https://moodle.hku.hk/course/view.php?id=${courseID}">${courseInfo.title}</a>
            <div class='history'>
            <div class="bubble" style="background-color: #332d2d;">
                <i style="width: 0px;height: 0px;color: #332d2d;border-width: 14px 15px 8px 0px;border-style: solid;border-color: currentcolor transparent transparent;top: 95%;left: 0px;margin-bottom: 4px;">
                </i>
                <div class="text">This course card was constructed based on your last visit to the search page or the sidebar</div>
            </div>
            <i class="fa fa-history history-icon" ></i>
            </div>
        </h3>
        <div></div>
        </div>
        <div class="teachers" >
        Teachers:
        <a style="color: #966b00;">${courseInfo.teachers}</a>
        </div>
        <div class="course-btn">
            <p>
            <a
                class="btn btn-primary"
                href="https://moodle.hku.hk/course/view.php?id=${courseID}"
            >
                Click to enter this course
            </a>
            </p>
        </div>
        </div>
    </div>`;
    }

    function LocateCourse(courseID, courses) {
    for (let i = 0; i < courses.length; i++) {
        if (courses[i].dataset.courseid == courseID) return courses[i];
    }
    return;
    }

    function extractInfo(courseElement) {
    title = courseElement.querySelector(".aalink").innerText;
    teachers = courseElement.querySelector(".teachers").innerText.slice(9);
    return { title: title, teachers: teachers };
    }

    async function addCourse(pageURL, courseID, courseList, courseInfo = {}) {
    if (pageURL == "/course/search.php") {
        if (courseList && courseList.length) {
        courseList.push({ courseID: courseID, courseInfo: courseInfo });
        } else {
        courseList = [{ courseID: courseID, courseInfo: courseInfo }];
        }
        GM_setValue("courselist", JSON.stringify(courseList));
    } else {
        if (courseList && courseList.length) {
        courseList.push({ courseID: courseID });
        } else {
        courseList = [{ courseID: courseID }];
        }
        GM_setValue("courselist", JSON.stringify(courseList));
    }
    mainFunction();
    }

    async function removeCourse(courseCode, courseList) {
    courseList = courseList.filter(function (value, index, arr) {
        return value.courseID !== courseCode;
    });
    GM_setValue("courselist", JSON.stringify(courseList));

    mainFunction();
    }

    async function removeAll() {
        GM_setValue("courselist", "[]")
    mainFunction();
    }

})();