// ==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:"Calibri",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:"Calibri",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:"Calibri",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();
}
})();