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

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

})();