// ==UserScript==
// @name BlackboardBb 增强 | Blackboard Enhanced
// @namespace npm/vite-plugin-monkey
// @version 2.0.4
// @author sitdownkevin
// @license MIT
// @match https://pibb.scu.edu.cn/*
// @require https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js
// @require https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.production.min.js
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @description 2023/4/3 10:27:00
// ==/UserScript==
(e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const t=document.createElement("style");t.textContent=e,document.head.append(t)})(' .calendar-container{position:fixed;z-index:10000;width:280px;opacity:.8}.calendar-list{width:100%;height:80%;overflow-y:scroll;pointer-events:auto;border-radius:20px;max-height:300px}.calendar-list ::-webkit-scrollbar{width:0;background:transparent}.calendar-item{width:95%;height:auto;margin-top:8px;border-radius:18px;margin-left:5px;pointer-events:auto;display:flex;flex-direction:column;align-items:left;cursor:move;-webkit-user-select:none;user-select:none}.calendar-item .assignment{margin-top:10px;margin-left:12px;margin-right:12px;font-size:17px;font-weight:700;color:#f5f5f6}.calendar-item .course-name{display:inline;margin-top:5px;margin-left:12px;margin-right:8px;font-weight:700;font-size:10px;color:#e0e0ef;cursor:pointer}.calendar-item .count-down{margin-left:12px;margin-bottom:10px;font-weight:700;font-size:22px;color:#e0e0ce}.memo-container{width:100%;height:auto;background-color:#dff0f4}.memo-container .memo-box{width:100%;height:288px;display:flex;flex-direction:column;align-items:center}.memo-container .memo-box .memo-list{border:1px solid #ccc;margin-top:20px;width:calc(100% - 32px);height:100%;overflow-y:scroll;pointer-events:auto}.memo-container .memo-box .memo-list .memo-input{height:300%;width:calc(100% - 10px);background-color:#fff;padding:5px;outline:none}.memo-container .memo-box .btn-box{margin-top:10px;width:100%;height:47.6px;position:relative}.memo-container .memo-box .btn-box .btn-save{background-color:#dadada;border:0 solid;width:46px;height:27.6px;position:absolute;right:70px;display:flex;justify-content:center;align-items:center;cursor:pointer}.memo-container .memo-box .btn-box .btn-save:hover{background-color:#fff}.memo-container .memo-box .btn-box .btn-clear{background-color:#333;border:0 solid;width:46px;height:27.6px;position:absolute;right:16px;display:flex;justify-content:center;align-items:center;color:#fff;cursor:pointer}#currentAttempt_gradeDataPanel{display:""!important} ');
(function (require$$0, require$$0$1) {
'use strict';
var jsxRuntime = { exports: {} };
var reactJsxRuntime_production_min = {};
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var f = require$$0, k = Symbol.for("react.element"), l = Symbol.for("react.fragment"), m$1 = Object.prototype.hasOwnProperty, n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p = { key: true, ref: true, __self: true, __source: true };
function q(c, a, g) {
var b, d = {}, e = null, h = null;
void 0 !== g && (e = "" + g);
void 0 !== a.key && (e = "" + a.key);
void 0 !== a.ref && (h = a.ref);
for (b in a)
m$1.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]);
if (c && c.defaultProps)
for (b in a = c.defaultProps, a)
void 0 === d[b] && (d[b] = a[b]);
return { $$typeof: k, type: c, key: e, ref: h, props: d, _owner: n.current };
}
reactJsxRuntime_production_min.Fragment = l;
reactJsxRuntime_production_min.jsx = q;
reactJsxRuntime_production_min.jsxs = q;
{
jsxRuntime.exports = reactJsxRuntime_production_min;
}
var jsxRuntimeExports = jsxRuntime.exports;
async function courseInfoCatch() {
try {
var orig_course_info = await get_course_id();
var course_db = store_course_id(orig_course_info);
console.log("fetchCourse.js Success");
return course_db;
} catch (err) {
console.log("fetchCourse.js Error: ", err);
}
}
async function get_course_id() {
const url = "/webapps/portal/execute/tabs/tabAction?action=refreshAjaxModule&modId=_2_1&tabId=_1_1&tab_tab_group_id=_1_1";
return await fetch(url, {
method: "POST"
}).then((res) => res.text()).then((data) => {
return data;
}).catch(console.log);
}
function store_course_id(_orig_course_info) {
var _course_db = {};
const pattern = /<li>[\s\S]*?<\/li>/g;
const regArr = _orig_course_info.match(pattern);
for (let i = 0; i < regArr.length; i++) {
const pattern2 = {
"course_name": /<a.*?>(.*?)<\/a>/,
// 课程名称
"course_id": /id=(_\d+_\d+)/i,
// 课程ID
"course_href": /href=['"](.*?)['"]/
// 课程主页链接
};
const course_name = regArr[i].match(pattern2["course_name"])[1];
const course_id = regArr[i].match(pattern2["course_id"])[1];
const course_href = regArr[i].match(pattern2["course_href"])[1];
_course_db[course_name] = {
"id": course_id,
"href": course_href
};
}
return _course_db;
}
async function calendarInfoCatch() {
try {
var orig_todo_items = await get_calendar();
var todo_items = await extractItems(orig_todo_items);
todo_items = await setColor(todo_items);
console.log("fetchCalendar.js Success");
return todo_items;
} catch (err) {
console.log("fetchCalendar.js Error: ", err);
}
}
async function get_calendar() {
const url = "/webapps/calendar/calendarData/selectedCalendarEvents";
const start_date = /* @__PURE__ */ new Date();
const end_date = /* @__PURE__ */ new Date();
end_date.setDate(end_date.getDate() + 28);
const params = "?start=" + start_date.getTime() + "&end=" + end_date.getTime() + "&course_id=&mode=personal";
return fetch(url + params, {
method: "GET"
}).then((res) => res.json()).then((data) => {
return data;
}).catch(console.log);
}
async function extractItems(_orig_todo_items) {
let course_db;
try {
course_db = await courseInfoCatch();
} catch (err) {
}
var _todo_items = [];
for (let i = 0; i < _orig_todo_items.length; i++) {
_todo_items.push({
"id": i,
"course": _orig_todo_items[i]["calendarName"],
"todoItem": _orig_todo_items[i]["title"],
"deadline": _orig_todo_items[i]["end"],
"href": course_db[_orig_todo_items[i]["calendarName"]] ? course_db[_orig_todo_items[i]["calendarName"]]["href"] : "#"
});
}
if (_todo_items.length === 0) {
_todo_items.push({
"id": 0,
"course": "No DDL Currently",
"todoItem": "HAVE A NICE DAY",
// "deadline": _tmp_ddl,
"href": "#"
});
} else {
_todo_items.sort((a, b) => {
return Date.parse(a.deadline) - Date.parse(b.deadline);
});
}
console.log(_todo_items);
return _todo_items;
}
async function setColor(_todo_items) {
const generateGradientColors = (color1, color2, steps) => {
const rgb1 = hexToRgb(color1);
const rgb2 = hexToRgb(color2);
const colors = [];
for (let i = 0; i <= steps; i++) {
const r = interpolate(rgb1.r, rgb2.r, i, steps);
const g = interpolate(rgb1.g, rgb2.g, i, steps);
const b = interpolate(rgb1.b, rgb2.b, i, steps);
const hex = rgbToHex(r, g, b);
colors.push(hex);
}
return colors;
};
const hexToRgb = (hex) => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return { r, g, b };
};
const rgbToHex = (r, g, b) => {
const hex = (r << 16 | g << 8 | b).toString(16);
return "#" + hex.padStart(6, "0");
};
const interpolate = (start, end, step, totalSteps) => {
return start + (end - start) * step / totalSteps;
};
const colorChoices = [
["#ff4e4f", "#ff9d81"],
["#032e71", "#b8e9fc"],
["#ff2121", "#d14631"]
];
const colorArr = generateGradientColors(colorChoices[1][0], colorChoices[1][1], _todo_items.length);
for (let i = 0; i < _todo_items.length; i++) {
_todo_items[i]["color"] = colorArr[i];
}
return _todo_items;
}
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
const formatDuration = (ms) => {
const second = 1e3;
const minute = second * 60;
const hour = minute * 60;
const day = hour * 24;
const days = Math.floor(ms / day);
const hours = Math.floor(ms % day / hour);
const minutes = Math.floor(ms % hour / minute);
const seconds = Math.floor(ms % minute / second);
let result = "";
if (days > 0) {
result += days + "天";
}
if (hours > 0) {
result += hours + "小时";
}
if (minutes > 0) {
result += minutes + "分钟";
}
if (seconds > 0) {
result += seconds + "秒";
}
return result;
};
function Calendar(props) {
const [dragging, setDragging] = require$$0.useState(false);
const [dragOffset, setDragOffset] = require$$0.useState([0, 0]);
const [loc, setLoc] = require$$0.useState(_GM_getValue("loc", [150, 150]));
const handleMouseDown = (e) => {
setDragging(true);
setDragOffset([e.clientX - loc[0], e.clientY - loc[1]]);
};
const handleMouseMove = (e) => {
if (dragging) {
setLoc([e.clientX - dragOffset[0], e.clientY - dragOffset[1]]);
_GM_setValue("loc", [e.clientX - dragOffset[0], e.clientY - dragOffset[1]]);
}
};
const handleMouseUp = () => {
setDragging(false);
};
const TodoItems = props.todo_items.map((todo) => {
const [cnt, setCnt] = require$$0.useState(formatDuration(Date.parse(todo["deadline"]) - Date.now()));
require$$0.useEffect(() => {
const it = setInterval(() => {
setCnt(formatDuration(Date.parse(todo["deadline"]) - Date.now()));
}, 1e3);
return () => {
clearInterval(it);
};
}, []);
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
"div",
{
className: "calendar-item",
style: { backgroundColor: todo["color"] },
onMouseDown: handleMouseDown,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "assignment", children: todo["todoItem"] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("a", { className: "course-name", href: todo["href"], children: todo["course"] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "count-down", children: cnt })
]
},
todo.id
);
});
require$$0.useEffect(() => {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [dragging]);
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
className: "calendar-container",
style: { top: loc[1], left: loc[0] },
children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "calendar-list", children: TodoItems })
}
) });
}
var client = {};
var m = require$$0$1;
{
client.createRoot = m.createRoot;
client.hydrateRoot = m.hydrateRoot;
}
function Memo({ props }) {
const handleSave = () => {
props.setEnv({
...props.env,
assignment: {
...props.env.assignment,
memo: document.querySelector(".memo-input").innerText
}
});
};
const handleClear = () => {
props.setEnv({
...props.env,
assignment: {
...props.env.assignment,
memo: ""
}
});
document.querySelector(".memo-input").innerText = "";
};
return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "memo-container", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "memo-box", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "memo-list", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "memo-input", contentEditable: true, children: props.env.assignment.memo }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "btn-box", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "btn-save", onClick: handleSave, children: "Save" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "btn-clear", onClick: handleClear, children: "Clear" })
] })
] }) }) });
}
class PrettierPage {
constructor() {
this.expand;
this.fb;
this.checkDOMReady();
}
checkDOMReady() {
try {
const checkInterval = setInterval(() => {
this.expand = document.querySelector("#currentAttempt_gradeDataPanel");
this.fb = document.querySelector("#feedbacktext_tbl > tbody > tr > td > span");
if (this.expand && this.fb) {
this.expand.style.display = "";
this.fb.remove();
clearInterval(checkInterval);
}
}, 500);
} catch (err) {
}
}
}
class AutoCalculator {
constructor() {
this.textArea;
this.fillSpace;
this.totolGrade;
this.lastGrade;
this.checkDOMReady();
}
checkDOMReady() {
const checkInterval = setInterval(() => {
try {
this.textArea = this.return_textArea();
this.fillSpace = this.return_fillSpace();
this.totolGrade = this.return_totalGrade();
this.lastGrade = this.return_lastGrade();
if (this.textArea && this.fillSpace && this.totolGrade && this.lastGrade) {
clearInterval(checkInterval);
this.setupEventListeners();
}
} catch (err) {
}
}, 500);
}
setupEventListeners() {
this.textArea.addEventListener("input", this.handleInput.bind(this));
if (this.lastGrade != "-") {
this.fillSpace.value = this.lastGrade;
} else {
this.fillSpace.value = this.totolGrade;
}
}
return_textArea() {
return document.querySelector("#feedbacktext_ifr").contentDocument.documentElement.querySelector("body");
}
return_fillSpace() {
return document.querySelector("#currentAttempt_grade");
}
return_totalGrade() {
return parseFloat(document.querySelector("#currentAttempt_pointsPossible").innerHTML.split("/")[1]);
}
return_lastGrade() {
return document.querySelector("#aggregateGrade").value;
}
handleInput(e) {
const numsArr = e.target.innerHTML.match(/-\d+(\.\d+)?/g);
if (!numsArr) {
this.fillSpace.value = this.totolGrade;
} else {
let grade = this.totolGrade;
numsArr.forEach((num) => {
grade += parseFloat(num);
});
this.fillSpace.value = grade;
}
}
remove() {
if (this.textArea) {
this.textArea.removeEventListener("input", this.handleInput);
}
}
}
function GradeAssignment(props) {
require$$0.useEffect(() => {
new PrettierPage();
const AC = new AutoCalculator();
const bro = document.querySelector("#currentAttempt_submission");
const app = document.createElement("div");
bro.parentNode.style.height = "auto";
bro.parentNode.insertBefore(app, bro);
const root = client.createRoot(app);
root.render(/* @__PURE__ */ jsxRuntimeExports.jsx(Memo, { props }));
return () => {
if (root) {
root.unmount();
}
AC.remove();
};
}, []);
return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, {});
}
function App() {
const [env, setEnv] = require$$0.useState(
_GM_getValue("env", {
calendar: {
display: true
},
assignment: {
display: true,
memo: ""
}
})
);
const [todoItems, setTodoItems] = require$$0.useState(null);
require$$0.useEffect(() => {
const fetchTodoItems = async () => {
const items = await calendarInfoCatch();
setTodoItems(items);
};
fetchTodoItems();
}, []);
require$$0.useEffect(() => {
_GM_setValue("env", env);
console.log("Setting Saved");
}, [env]);
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
window.location.href.startsWith("https://pibb.scu.edu.cn/webapps/portal") && todoItems && env.calendar.display ? /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { todo_items: todoItems }) : null,
window.location.href.startsWith("https://pibb.scu.edu.cn/webapps/assignment") && env.assignment.display ? /* @__PURE__ */ jsxRuntimeExports.jsx(GradeAssignment, { env, setEnv }) : null
] });
}
client.createRoot(
(() => {
const app = document.createElement("div");
document.body.append(app);
return app;
})()
).render(
/* @__PURE__ */ jsxRuntimeExports.jsx(require$$0.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) }) })
);
})(React, ReactDOM);