const domain = window.location.origin; const current_page = window.location.pathname; let assignments = null; let grades = null; let announcements = []; let completed = []; let assignmentsDue = []; let options = {}; let timeCheck = null; let reminderCheck = null; //let assignmentData = null; /* Start */ /* // only works if a course has no quizzes... function getClassAverages() { if (true) { // check if option is enabled let match = current_page.match(/courses\/(?\d*)\/grades/); if (match) { let course_grades = getData(`${domain}/api/v1/courses/${match.groups.id}/assignments?include[]=score_statistics&include[]=submission`); let course_quizzes = getData(`${domain}/api/v1/courses/${match.groups.id}/quizzes`); let course_groups = getData(`${domain}/api/v1/courses/${match.groups.id}/assignment_groups`); course_grades.then(grades => { course_groups.then(groups => { course_quizzes.then(quizzes => { let total_weight = 0; let total_points = 0; let weights = {}; groups.forEach(group => { weights[group.id] = group.group_weight; total_weight += group.group_weight; }); groups.forEach(group => { weights[group.id] = total_weight === 0 ? 1 : weights[group.id] / total_weight; }); let min = 0, lowq = 0, mean = 0, median = 0, upq = 0, max = 0, earned = 0; grades.forEach(grade => { if (!grade.score_statistics) return; console.log("\nthis:", grade.name, grade.score_statistics.lower_q, grade.score_statistics.mean, grade.score_statistics.upper_q); console.log("totals:", lowq, upq, total_points); min += grade.score_statistics.min * weights[grade.assignment_group_id]; lowq += grade.score_statistics.lower_q * weights[grade.assignment_group_id]; mean += grade.score_statistics.mean * weights[grade.assignment_group_id]; median += grade.score_statistics.median * weights[grade.assignment_group_id]; upq += grade.score_statistics.upper_q * weights[grade.assignment_group_id]; max += grade.score_statistics.max * weights[grade.assignment_group_id]; total_points += grade.points_possible * weights[grade.assignment_group_id]; earned += grade.submission.score * weights[grade.assignment_group_id]; }); course_quizzes.forEach(quiz => { // is it even possible to get quiz statistics? }); // absolute minimum is if the same student got the lowest score on every assignment // absolute maximum is if the same student got the highest score on every assignment // it doesn't really tell you much because both are unlikely console.log("\nabsolute minimum:", min / total_points, "\nabsolute maximum:", max / total_points, "\nlower quartile:", lowq / total_points, "\nmean:", mean / total_points, "\nupper quartile:", upq / total_points); min = (min / total_points); lowq = (lowq / total_points); mean = (mean / total_points); upq = (upq / total_points); max = (max / total_points); earned = (earned / total_points); console.log(weights); const width = 150; let inner = `
Class Averages
Mean: ${(mean * 100).toFixed(2)}Upper Quartile: ${(upq * 100).toFixed(2)}Lower Quartile: ${(lowq * 100).toFixed(2)}
`; makeElement("tr", document.querySelector("#grades_summary tbody"), { "innerHTML": inner }); }); }); }); } } } */ /* Todo Reminders */ const canvas_svg = ` `; async function insertReminders(reminders) { const toAdd = []; const storage = await chrome.storage.sync.get("reminders"); // overrides = if theres a item that needs to update, but already exists let overrides = false; for (const insert of reminders) { let found = false; for (let i = 0; i < storage["reminders"].length; i++) { // check if item was recently submitted if (insert.c === -1 && insert.h === storage["reminders"][i].h) { overrides = true; storage["reminders"][i] = insert; } else if (insert.h === storage["reminders"][i].h) { found = true; } } if (found === false) toAdd.push(insert); } if (toAdd.length > 0 || overrides === true) chrome.storage.sync.set({ "reminders": [...storage["reminders"], ...toAdd] }); } async function hideReminder(href) { const storage = await chrome.storage.sync.get("reminders"); for (let i = 0; i < storage["reminders"].length; i++) { if (storage["reminders"][i]["h"] === href) { storage["reminders"][i]["c"]++; chrome.storage.sync.set({ "reminders": storage["reminders"] }); break; } } } function createReminder(reminder, location) { const remaining = getRelativeDate(new Date(reminder.d)); const wrapper = makeElement("div", location, { "className": "bettercanvas-reminder-wrapper" }); const container = makeElement("div", wrapper, { "className": "bettercanvas-reminder-container" }); const svg = makeElement("div", container, { "innerHTML": canvas_svg }); const content = makeElement("a", container, { "className": "bettercanvas-reminder-content", "href": reminder.h, "target": "_blank" }); const title = makeElement("h2", content, { "className": "bettercanvas-reminder-title", "textContent": reminder.t }); const due = makeElement("p", content, { "className": "bettercanvas-reminder-due", "textContent": `Assignment due in ${remaining.time}` }); const hidebtn = makeElement("btn", wrapper, { "className": "bettercanvas-reminder-hide", "textContent": "x" }); hidebtn.addEventListener("click", () => { hideReminder(reminder.h); wrapper.remove(); }); return container; } async function reminderWatch() { const sync = await chrome.storage.sync.get("remind"); if (sync["remind"] !== true) { if (document.getElementById("bettercanvas-reminders")) document.getElementById("bettercanvas-reminders").style.display = "none"; return; } const container = document.getElementById("bettercanvas-reminders") || makeElement("div", document.body, { "id": "bettercanvas-reminders" }); container.style.display = "flex"; container.textContent = ""; const alertPeriod = 1000 * 60 * 60 * 6; // 6 hours const alertPeriod2 = 1000 * 60 * 60 * 2; // 2 hours const storage = await chrome.storage.sync.get(["reminders", "reminder_count"]); const now = (new Date()).getTime(); storage["reminders"].forEach((reminder, index) => { if (reminder.d < now) { storage["reminders"].splice(index, 1); } else if ((reminder.c == 0 && reminder.d < now + alertPeriod) || (reminder.c == 1 && reminder.d < now + alertPeriod2)) { createReminder(reminder, container); } }); chrome.storage.sync.set({ "reminders": storage["reminders"] }); } function updateReminders() { const fiveDays = 1000 * 60 * 60 * 24 * 5; const now = (new Date()).getTime(); const list = []; assignments.then(data => { data.forEach(item => { const due = (new Date(item.plannable_date)).getTime(); if (item.plannable_type === "announcement") return; if (due < now) return; if (due > now + fiveDays * 2) return; // { due, title, href, hide count } // hide count of -1 indicates the item has a submission list.push({ "d": due, "t": item.plannable.title, "h": domain + item.html_url, "c": item?.submissions?.submitted || false ? -1 : 0 }); }); insertReminders(list); }); } function showExampleReminder() { const location = document.getElementById("bettercanvas-reminders") || makeElement("div", document.body, { "id": "bettercanvas-reminders" }); if (options.remind !== true) { location.remove(); return; } location.textContent = ""; const example = createReminder({ "d": new Date(), "t": "This is an example reminder", }, location); example.querySelector(".bettercanvas-reminder-due").textContent = "This notification will pop up in other pages to remind you of incomplete assignments that are due in less than 6 hours." /*It will notify again at 2 hours if the 'Remind 2x' option is on."*/; } async function ScheduledReminderCheck() { let date = new Date(); let currentHour = date.getHours(); let currentMinute = date.getMinutes(); if (options.scheduledReminderTime) { let [hour, minute] = options.scheduledReminderTime.split(":"); if (parseInt(hour) == currentHour && parseInt(minute) == currentMinute) { const container = document.getElementById("bettercanvas-reminders") || makeElement("div", document.body, { "id": "bettercanvas-reminders" }); container.style.display = "flex"; container.textContent = ""; const storage = await chrome.storage.sync.get("reminders"); const now = (new Date()).getTime(); storage["reminders"].forEach(reminder => { if (reminder.d >= now) { createReminder(reminder, container); } }); } } } function toggleScheduledReminders() { clearInterval(reminderCheck); if (options.scheduledReminder !== true) return; ScheduledReminderCheck(); reminderCheck = setInterval(ScheduledReminderCheck, 60000); } isDomainCanvasPage(); function isDomainCanvasPage() { chrome.storage.sync.get(['custom_domain', 'dark_mode', 'dark_preset', 'device_dark', 'remind', 'scheduledReminder', 'scheduledReminderTime'], result => { options = result; if (result.custom_domain.length && result.custom_domain[0] !== "") { for (let i = 0; i < result.custom_domain.length; i++) { if (domain.includes(result.custom_domain[i])) { startExtension(); return; } } // if the code reaches this point, its not a canvas page so run the reminders setTimeout(reminderWatch, 2000); setInterval(reminderWatch, 60000); toggleScheduledReminders(); // turn the reminders on/off if the option is changed chrome.storage.onChanged.addListener((changes) => { Object.keys(changes).forEach(key => { if (key === "remind") reminderWatch(); if (key === "scheduledReminder" || key === "scheduledReminderTime") { options[key] = changes[key].newValue; toggleScheduledReminders(); } }) }) } else { setupCustomURL(); } }); } function startExtension() { toggleDarkMode(); chrome.storage.sync.get(null, result => { options = { ...options, ...result }; toggleAutoDarkMode(); toggleScheduledReminders(); getApiData(); checkDashboardReady(); loadCustomFont(); applyAestheticChanges(); changeFavicon(); updateReminders(); applyCustomBackground(); //getClassAverages(); setTimeout(() => document.getElementById("footer").remove(), 800); setTimeout(() => runDarkModeFixer(false), 800); setTimeout(() => runDarkModeFixer(false), 4500); }); chrome.runtime.onMessage.addListener(recieveMessage); chrome.storage.onChanged.addListener(applyOptionsChanges); console.log("Better Canvas - running"); } function applyOptionsChanges(changes) { let rewrite = {}; Object.keys(changes).forEach(key => { rewrite[key] = changes[key].newValue; }); options = { ...options, ...rewrite }; // when an option is updated it will call the necessary functions again // so any changes made in the menu no longer require a refresh to apply Object.keys(changes).forEach(key => { console.log(key + " changed"); switch (key) { case ("dark_mode"): case ("dark_preset"): case ("device_dark"): toggleDarkMode(); break; case ("auto_dark"): case ("auto_dark_start"): case ("auto_dark_end"): toggleAutoDarkMode(); break; case ("gradient_cards"): changeGradientCards(); break; case ("dashboard_notes"): loadDashboardNotes(); break; case ("dashboard_grades"): case ("grade_hover"): if (!grades) getGrades(); insertGrades(); break; case ("assignments_due"): case ("num_assignments"): if (!assignments) getAssignments(); if (document.querySelectorAll(".bettercanvas-card-assignment").length === 0) setupCardAssignments(); loadCardAssignments(); break; case ("custom_assignments"): case ("assignment_date_format"): case ("card_overdues"): case ("relative_dues"): cardAssignments = preloadAssignmentEls(); loadCardAssignments(); break; case ("custom_cards"): case ("custom_cards_2"): case ("custom_cards_3"): customizeCards(); break; case ("todo_hr24"): case ("num_todo_items"): case ("hover_preview"): case ("todo_overdues"): case ("todo_colors"): case ("custom_cards_3"): moreAnnouncementCount = 0; moreAssignmentCount = 0; loadBetterTodo(); break; case ("gpa_calc"): case ("gpa_calc_prepend"): case ("gpa_calc_weighted"): case ("gpa_calc_cumulative"): if (!grades) getGrades(); setupGPACalc(); break; case ("gpa_calc_bounds"): calculateGPA2(); break; case ("custom_font"): loadCustomFont(); break; case ("remlogo"): case ("disable_color_overlay"): case ("condensed_cards"): case ("hide_feedback"): case ("full_width"): case ("custom_styles"): applyAestheticChanges(); break; case ("show_updates"): showUpdateMsg(); break; case ("remind"): showExampleReminder(); break; case ("scheduledReminder"): case ("scheduledReminderTime"): toggleScheduledReminders(); break; case ("imageSize"): case ("cardRoundness"): case ("cardSpacing"): case ("cardWidth"): case ("cardHeight"): case ("customCardStyles"): applyAestheticChanges(); break; case ("customBackgroundLink"): applyCustomBackground(); break; } }); } function applyCustomBackground() { // let style = document.querySelector("#DashboardCard_Container") let style = document.querySelector("#bettercanvas-background") || document.createElement('style'); style.id = "bettercanvas-background"; if (options.customBackgroundLink && options.customBackgroundLink !== "") { style.textContent = ` #wrapper { background-image: url('${options.customBackgroundLink}') !important; background-size: cover !important; background-attachment: fixed !important; } .ic-Dashboard-header__layout { background: none !important; backdrop-filter: blur(10px) !important; border-radius: 5px; } #right-side-wrapper { background: color-mix(in srgb, var(--bcbackground-0) 45%, transparent) !important; backdrop-filter: blur(10px) !important; border-radius: 5px; } .bettercanvas-gpa-card {background: var(--bcbackground-0) !important;} .bettercanvas-gpa {background: var(--bcbackground-0) !important;} .ic-DashboardCard {background: var(--bcbackground-0) !important;}`; // todo: liquid glass? } document.documentElement.appendChild(style); } function clearCustomBackground() { let style = document.querySelector("#bettercanvas-background"); if (style) style.remove(); } let insertTimer; function resetTimer() { clearTimeout(insertTimer); insertTimer = setTimeout(() => { if (document.querySelectorAll(".ic-DashboardCard__link").length > 0) { loadCardAssignments(); loadBetterTodo(); } else { resetTimer(); } }, 1); } function checkDashboardReady() { if (current_page !== "/" && current_page !== "") return; const callback = (mutationList) => { for (const mutation of mutationList) { if (mutation.type === "childList") { if (mutation.target == document.querySelector("#DashboardCard_Container")) { let cards = document.querySelectorAll('.ic-DashboardCard'); changeGradientCards(); setupCardAssignments(); loadCardAssignments(); customizeCards(cards); insertGrades(); loadDashboardNotes(); setupGPACalc(); showUpdateMsg(); } else if (mutation.target == document.querySelector('#right-side')) { if (!mutation.target.querySelector(".bettercanvas-todosidebar")) { setupBetterTodo(); // loadBetterTodo(); } } } } }; const observer = new MutationObserver(callback); observer.observe(document.querySelector('html'), { childList: true, subtree: true }); } function recieveMessage(request, sender, sendResponse) { switch (request.message) { case ("getCards"): if (options["card_method_dashboard"] === true) { getCardsFromDashboard(); } else { getCards(); } sendResponse(true); break; case ("setcolors"): changeColorPreset(request.options); sendResponse(true); break; case ("getcolors"): sendResponse(getCardColors()); break; case ("inspect"): sendResponse(inspectDarkMode(true)); break; case ("fixdm"): sendResponse(runDarkModeFixer(true)); break; case ("updateBackground"): clearCustomBackground(); sendResponse(true); break; default: sendResponse(true); } } function hexToRgb(hex) { let match = (/#(.{2})(.{2})(.{2})/).exec(hex); if (match) { return { "r": parseInt(match[1], 16), "g": parseInt(match[2], 16), "b": parseInt(match[3], 16) }; } } function inspectDarkMode(withOutput = false) { let output = ""; let bgcount = 0, textcount = 0, time = performance.now(); let bg0 = hexToRgb(options.dark_preset["background-0"]); let bg1 = hexToRgb(options.dark_preset["background-1"]); let txt = hexToRgb(options.dark_preset["text-0"]); let bdr = hexToRgb(options.dark_preset["borders"]); let lnk = hexToRgb(options.dark_preset["links"]); document.querySelectorAll("*").forEach(el => { let style = getComputedStyle(el); let bgcolor = style.getPropertyValue("background").match(/rgb\((?\d*)\, ?(?\d*)\, ?(?\d*)\) none/); let selector = "class=." + el.className + ",id=#" + el.id; if (bgcolor) { const r = parseInt(bgcolor.groups["r"]); const g = parseInt(bgcolor.groups["g"]); const b = parseInt(bgcolor.groups["b"]); /* if (el.classList.contains("no-touch")) { console.log({ "r": r, "g": g, "b": b }, { "r": r === bg0.r, "g": g === bg0.g, "b": b === bg0.b }); } */ if (r > 245 && g > 245 && b > 245 && !(r === bg0.r && g === bg0.g && b === bg0.b) && !(r === lnk.r && g === lnk.g && b === lnk.b)) { el.style.cssText = (";background:" + options.dark_preset["background-0"] + "!important;color" + options.dark_preset["text-0"] + "!important;") + el.style.cssText; if (withOutput === true) output += selector + "{background: background-0, color: text-0}\n"; bgcount++; } else if (r > 225 && r < 245 && g > 225 && g < 245 && b > 225 && b < 245 && !(r === bg1.r && g === bg1.g && b === bg1.b) && !(r === lnk.r && g === lnk.g && b === lnk.b)) { el.style.cssText = (";background:" + options.dark_preset["background-1"] + "!important;color" + options.dark_preset["text-0"] + "!important;") + el.style.cssText; if (withOutput === true) output += selector + "{background: background-1, color: text-0}"; bgcount++; } } let bordercolor = style.getPropertyValue("border-color").match(/rgb\((?\d*)\, ?(?\d*)\, ?(?\d*)/); if (bordercolor) { const r = parseInt(bordercolor.groups["r"]); const g = parseInt(bordercolor.groups["g"]); const b = parseInt(bordercolor.groups["b"]); if (r > 195 && g > 195 && b > 195 && !(r === bdr.r && g === bdr.g && b === bdr.b) && !(r === lnk.r && g === lnk.g && b === lnk.b)) { el.style.cssText = "border-color:" + options.dark_preset["borders"] + "!important;" + el.style.cssText; if (withOutput === true) output += selector + "{border: borders}"; } } let text = style.getPropertyValue("color").match(/rgb\((?\d*)\, ?(?\d*)\, ?(?\d*)/); if (text) { const r = parseInt(text.groups["r"]); const g = parseInt(text.groups["g"]); const b = parseInt(text.groups["b"]); if (r <= 70 && g <= 70 && b <= 70 && !(r === txt.r && g === txt.g && b === txt.b)) { el.style.cssText = "color:" + options.dark_preset["text-0"] + "!important;" + el.style.cssText; if (withOutput === true) output += selector + "{text: text-0}"; textcount++; } } }); console.log("done fixing dark mode - time:", performance.now() - time, "total backgrounds changed: ", bgcount, ", total colors changed: ", textcount); return { "selectors": output === "" ? "no gaps determined" : output, "time": performance.now() - time }; } function getCardColors() { let cards = document.querySelectorAll(".ic-DashboardCard__header"); let colors = []; cards.forEach(card => { let rgbColor = card.querySelector(".ic-DashboardCard__header_hero").style.backgroundColor; colors.push({ "href": card.querySelector(".ic-DashboardCard__link").href, "color": rgbToHex(rgbColor) }); }); colors.sort((a, b) => a.href > b.href ? 1 : -1); colors = colors.map(x => x.color); return colors; } function getCardsFromDashboard() { console.log("getting cards from dashboard") const dashboard_cards = document.querySelectorAll(".ic-DashboardCard"); chrome.storage.sync.get(["custom_cards", "custom_cards_2", "custom_cards_3"], storage => { let cards = storage["custom_cards"] || {}; let cards_2 = storage["custom_cards_2"] || {}; let cards_3 = storage["custom_cards_3"] || {}; let newCards = false; let count = 0; try { dashboard_cards.forEach(card => { const id = card.querySelector(".ic-DashboardCard__link").href.split("courses/")[1]; if (count >= (options["card_limit"] || 25)) return; if (!cards[id]) { newCards = true; cards[id] = { "default": card.querySelector(".ic-DashboardCard__header-subtitle").textContent.substring(0, 20), "name": "", "code": "", "img": "", "hidden": false, "weight": "regular", "credits": 1, "eid": 100000 - count, "gr": null }; let links = []; for (let i = 0; i < 4; i++) { links.push({ "path": "default", "is_default": true }); } cards_2[id] = { "links": links }; cards_3[id] = { "url": domain }; } count++; }); // there shouldn't be 0 cards if (count === 0) return; //delete cards that aren't on the dashboard anymore Object.keys(cards).forEach(key => { let found = false; // ignore cards that are not for the current url if (cards_3[key] && cards_3[key].url !== domain) { found = true; } else { dashboard_cards.forEach(card => { const id = card.querySelector(".ic-DashboardCard__link").href.split("courses/")[1]; if (parseInt(key) === parseInt(id)) found = true; }); } if (found === false) { console.log("Deleting " + key); cards[key] && delete cards[key]; cards_2[key] && delete cards_2[key]; cards_3[key] && delete cards_3[key]; newCards = true; } }); } catch (e) { console.log("Error getting dashboard cards\n", e); logError(e); } finally { if(newCards !== true) return; console.log(newCards ? "new cards found" : ""); chrome.storage.sync.set({ "custom_cards": cards, "custom_cards_2": cards_2, "custom_cards_3": cards_3 }); } }); } async function getCards(api = null) { let dashboard_cards = api ? api : await getData(`${domain}/api/v1/courses?${/*enrollment_state=active&*/""}per_page=100`); chrome.storage.sync.get(["custom_cards", "custom_cards_2", "custom_cards_3"], storage => { let cards = storage["custom_cards"] || {}; let cards_2 = storage["custom_cards_2"] || {}; let cards_3 = storage["custom_cards_3"] || {}; let newCards = false; let count = 0; // sort cards by enrollment id (i think the higher the id, the more recent it is) if (options["card_method_date"] === true) { dashboard_cards.sort((a, b) => (b?.created_at) > (a?.created_at) ? 1 : -1); } else { dashboard_cards.sort((a, b) => (b?.enrollment_term_id || 0) - (a?.enrollment_term_id || 0)); } try { dashboard_cards.forEach(card => { if (!card.course_code || count >= (options["card_limit"] || 25)) return; let id = card.id; if (!cards || !cards[id]) { newCards = true; cards[id] = { "default": card.course_code.substring(0, 20), "name": "", "code": "", "img": "", "hidden": false, "weight": "regular", "credits": 1, "eid": card.enrollment_term_id || 0, "gr": null }; } else if (cards && cards[id]) { newCards = true; cards[id].default = card.course_code.substring(0, 20); cards[id].eid = card.enrollment_term_id || 0; if (!cards[id].code) cards[id].code = ""; } if (!cards_2 || !cards_2[id]) { newCards = true; let links = []; for (let i = 0; i < 4; i++) { links.push({ "path": "default", "is_default": true }); } cards_2[id] = { "links": links }; } if (!cards_3 || !cards_3[id]) { newCards = true; cards_3[id] = { "url": domain }; } count++; }); //delete cards that aren't on the dashboard anymore Object.keys(cards).forEach(key => { let found = false; // ignore cards that are not for the current url if (cards_3[key] && cards_3[key].url !== domain) { found = true; } else { dashboard_cards.forEach(card => { if (parseInt(key) === card.id) found = true; }); } if (found === false) { console.log("Deleting " + key + " from custom_cards...", cards[key]); cards[key] && delete cards[key]; cards_2[key] && delete cards_2[key]; cards_3[key] && delete cards_3[key]; newCards = true; } }); } catch (e) { console.log(e); } finally { return chrome.storage.sync.set(newCards ? { "custom_cards": cards, "custom_cards_2": cards_2, "custom_cards_3": cards_3 } : {}); } }); } /* Better todo list */ // function setAssignmentState(id, updates) { // let states = options.assignment_states; // let length = JSON.stringify(states).length; // // remove the oldest states if the size is approaching the storage limit // if (length > 7400) { // let keys = Object.keys(states).sort((a, b) => states[b].expire - states[a].expire); // keys.splice(-5); // let newStates = {}; // keys.forEach(key => { // newStates[key] = states[key]; // }); // states = newStates; // } // states[id] = states[id] ? { ...states[id], ...updates } : updates; // chrome.storage.sync.set({ assignment_states: states }).then(() => { cardAssignments = preloadAssignmentEls(); loadBetterTodo(); loadCardAssignments(); }); // } function createTodoCreateBtn(location) { let confirmButton = makeElement("button", location, { "className": "bettercanvas-custom-btn", "textContent": "Create" }); confirmButton.addEventListener("click", () => { chrome.storage.sync.get("custom_assignments_overflow", overflow => { chrome.storage.sync.get(overflow["custom_assignments_overflow"], storage => { let course_id = parseInt(location.querySelector("#bettercanvas-custom-course").value); const assignment = { "plannable_id": new Date().getTime(), "context_name": options.custom_cards[location.querySelector("#bettercanvas-custom-course").value].default, "plannable": { "title": location.querySelector("#bettercanvas-custom-name").value }, "plannable_date": location.querySelector("#bettercanvas-custom-date").value + "T" + location.querySelector("#bettercanvas-custom-time").value + ":00", "planner_override": { "marked_complete": false, "custom": true }, "plannable_type": "assignment", "submissions": { "submitted": false }, "course_id": course_id, "html_url": `/courses/${course_id}/assignments` }; /* handling overflow since the limit is 8kb per key */ let found = false; let reload = () => { location.classList.toggle("bettercanvas-custom-open"); loadBetterTodo(); loadCardAssignments(); } /* find the first available overflow with space */ /* or create a new one if all are full */ let findOpenOverflow = (num) => { let current_overflow = overflow["custom_assignments_overflow"][num]; storage[current_overflow].push(assignment); chrome.storage.sync.set({ [current_overflow]: storage[current_overflow] }, () => { /* assuming any error is because the limit is exceeded */ if (chrome.runtime.lastError) { if (num === overflow["custom_assignments_overflow"].length - 1) { console.log("all overflows are full! creating new overflow " + (overflow["custom_assignments_overflow"].length + 1)); let new_overflow = "custom_assignments_" + (overflow["custom_assignments_overflow"].length + 1); overflow["custom_assignments_overflow"].push(new_overflow); chrome.storage.sync.set({ [new_overflow]: [assignment], "custom_assignments_overflow": overflow["custom_assignments_overflow"] }).then(reload); } else { console.log("overflow " + (num + 1) + " full..."); findOpenOverflow(num + 1); } } else { console.log("overflow " + (num + 1) + " has space!"); reload(); } }); } findOpenOverflow(0); }); }) }); } // better todo html layer 1 // function createTodoHeader(location) { // let todoHeader = makeElement("h2", location, { "className": "todo-list-header", "style": "display: flex; align-items:center; justify-content:space-between;" }); // //todoHeader.style = "display: flex; align-items:center; justify-content:space-between;"; // if (!options.custom_cards || Object.keys(options.custom_cards).length === 0) return; // let addFillout = makeElement("div", location, { "className": "bettercanvas-add-assignment" }); // let now = new Date(); // let year = now.getFullYear(); // let month = now.getMonth() + 1; // let day = now.getDate(); // month = month < 10 ? "0" + month : month; // day = day < 10 ? "0" + day : day; // addFillout.innerHTML = '
'; // addFillout.querySelector("#bettercanvas-custom-date").value = year + "-" + month + "-" + day; // let selectCourse = document.querySelector("#bettercanvas-custom-course"); // Object.keys(options.custom_cards).forEach(id => { // let card = options.custom_cards[id]; // let courseName = makeElement("option", selectCourse, { "className": "bettercanvas-select-course-option", "textContent": card.default }); // courseName.value = id; // }); // createTodoCreateBtn(addFillout); // let headerText = makeElement("span", todoHeader, { "className": "bettercanvas-todo-header", "textContent": "To Do" }); // let addButton = makeElement("button", todoHeader, { "className": "bettercanvas-custom-btn", "textContent": "+ Add" }); // addButton.addEventListener("click", () => { // addFillout.classList.toggle("bettercanvas-custom-open"); // }); // headerText.addEventListener("click", () => { // if (filter === "todo") { // filter = "done"; // headerText.textContent = "Done"; // } else { // filter = "todo"; // headerText.textContent = "To Do"; // } // moreAssignmentCount = 0; // moreAnnouncementCount = 0; // loadBetterTodo(); // }); // } function convertToDueDate(dueAt) { final = "due "; let date = new Date(dueAt); final += date.toLocaleString("en-US", { month: "short", day: "numeric" }); final += " at " + date.toLocaleString("en-US", { hour: "numeric", minute: "numeric", hour12: true }); return final; } function updateIndicator(element) { const indicator = document.getElementById("better-todo-indicator"); indicator.style.width = `${element.offsetWidth*2}px`; indicator.style.left = `${element.offsetLeft - (element.offsetWidth * .5)}px`; const buttons = ["announcement", "assignments", "completed"]; buttons.forEach(button => { const btn = document.getElementById(`better-todo-${button}`); if (btn == element) { btn.firstElementChild.style.opacity = "1"; // btn.style.filter = "none"; } else { btn.firstElementChild.style.opacity = ".3"; // btn.style.filter = "grayscale(100%)"; } }) } // better todo html betterTodoFilter = "tasks"; let domContainers = {}; async function createTodoSections(location) { if (!location.querySelector("#better-todo-header")) { let header = makeElement("div", location, { id: "better-todo-header" }); header.style = "display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--bcbackground-1);padding-bottom:-2px;"; let today = new Date(); today.setHours(0,0,0,0); const todayString = today.toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" }); header.innerHTML = `

Tasks

${todayString}

`; let filterControl = makeElement("div", location, { "id": "better-todo-filter" }); filterControl.innerHTML = `
`; setTimeout(() => updateIndicator(document.getElementById("better-todo-assignments")), 10); document.getElementById("better-todo-announcement").addEventListener("click", (e) => { betterTodoFilter = "announcements"; moreAnnouncementCount = 0; updateIndicator(e.currentTarget); clearTodoList(); createTodoSections(location); }); document.getElementById("better-todo-assignments").addEventListener("click", (e) => { betterTodoFilter = "tasks"; moreAssignmentCount = 0; updateIndicator(e.currentTarget); clearTodoList(); createTodoSections(location); }); document.getElementById("better-todo-completed").addEventListener("click", (e) => { betterTodoFilter = "completed"; moreCompletedCount = 0; updateIndicator(e.currentTarget); clearTodoList(); createTodoSections(location); }); let mainSection = makeElement("div", location, { id: "better-todo-main", }); mainSection.style = "display:flex;flex-direction:column;"; } let mainSection = location.querySelector("#better-todo-main"); assignments.then(data => { // console.log(data); data.forEach(item => { announcements = data.filter(item => item.plannable_type == "announcement"); assignmentsDue = data.filter((item) => (item.plannable_type == "assignment" || item.plannable_type == "planner_note") && !item.submissions?.submitted && !item.planner_override?.marked_complete && !item.submissions.graded); completed = data.filter(item => (item.plannable_type == "assignment" || item.plannable_type == "planner_note") && (item.submissions.submitted || item.planner_override?.marked_complete || item.submissions.graded)); }); console.log("assignments", assignmentsDue); console.log("announcements", announcements); console.log("completed", completed); if (!document.getElementById("better-todo-announcement-badge")) { let isAnnoucementBadge = 0; announcements.forEach(item => { if (item.plannable.read_state == "unread") { isAnnoucementBadge++; return; } }) if (isAnnoucementBadge > 0) { makeElement("div", document.getElementById("better-todo-announcement"), { id: "better-todo-announcement-badge", style: "background-color:#ff0000;width:15px;height:15px;border-radius:50%;font-size:12px;position:absolute;top:-7px;left:16px;display:flex;justify-content:center;align-items:center;", // TODO: theme compatibility innerHTML: `${isAnnoucementBadge}` }) } } domContainers = {}; const groupKeys = ["-1", "0", "1", "2", "3", "4", "5", "6", "7", "14", "21", "30", "Later", "New", "Seen", "Ungraded", "Graded"]; for (const key of groupKeys) { let wrapper = makeElement("div", mainSection, { style: "display:none;margin-top:10px;", className: "better-todo-dueheader", }); let label = ""; if (key == "Later") label = "Due Later"; if (key == "-1") label = "Overdue"; else if (key == "0") label = "Due Today"; else if (key == "1") label = "Due Tommorow"; else if (key >= 2 && key < 7) label = "Due " + key + " days"; else if (key >= 7 && key < 30) label = "Due " + key/7 + " weeks"; else if (key == "30") label = "Due 1 month"; else label = "" + key + ""; makeElement("div", wrapper, { innerHTML: "" + label + "", style: "display:flex;flex-direction:column;gap:10px;font-size:12px;" // TODO: might not be theme compatible }) let listContainer = makeElement("div", wrapper, { className: "todo-group-list" }); listContainer.style = "display:flex;flex-direction:column;gap:10px;"; domContainers[key] = { wrapper, listContainer }; } if (betterTodoFilter == "tasks") { populateAssignments(); } if (betterTodoFilter == "announcements") { populateAnnouncements(); } if (betterTodoFilter == "completed") { populateAssignments(true); } }); } function clearTodoList() { document.getElementById("better-todo-main").querySelectorAll(".todo-group-list").forEach(list => { list.innerHTML = ""; }); document.querySelectorAll(".better-todo-dueheader").forEach(header => { header.remove(); }); } function populateAssignments(iscompleted = false) { const today = new Date(); today.setHours(0,0,0,0); let assignments = iscompleted ? completed : assignmentsDue; assignments.forEach((item) => { let dueGroup = -1; if (!iscompleted) { let dueDate = new Date(item.plannable_date); dueDate.setHours(0,0,0,0); const diffDays = Math.round((dueDate - today) / (1000 * 60 * 60 * 24)); if (diffDays < 0) {dueGroup = 0;} else if (diffDays <= 1) { dueGroup = diffDays.toString(); } else if (diffDays <= 7) { dueGroup = diffDays.toString(); } else if (diffDays <= 14) {dueGroup = 14;} else if (diffDays <= 21) {dueGroup = 21;} else if (diffDays <= 30) {dueGroup = 30;} else {dueGroup = "Later"}; } else { dueGroup = item.submissions?.graded ? "Graded" : "Ungraded"; } let assignment const targetContainer = domContainers[dueGroup]; if (targetContainer) { targetContainer.wrapper.style.display = "block"; assignment = makeElement("div", targetContainer.listContainer, { class: "better-todo-assignment", }); } const courseColor = options.custom_cards_3?.[String(item.course_id)]?.color ?? options.custom_cards_3?.[item.course_id]?.color ?? options.custom_cards_3?.[item.plannable.course_id]?.color ?? "#cccccc"; assignment.innerHTML = `
${item.context_name} ${item.plannable.title} ${convertToDueDate(item.plannable_date)}
`; assignment.querySelector(".better-todo-assignment-checkmark").addEventListener("click", () => { console.log("marking ", item.plannable.title, " as complete"); markAs(item) }); }); } function populateAnnouncements() { const today = new Date(); today.setHours(0,0,0,0); announcements.forEach((item) => { let dueGroup = item.plannable.read_state == "read" ? "Seen" : "New"; let announcement; // console.log(domContainers) const targetContainer = domContainers[dueGroup]; if (targetContainer) { targetContainer.wrapper.style.display = "block"; announcement = makeElement("div", targetContainer.listContainer, { class: "better-todo-announcement", }); } const courseColor = options.custom_cards_3?.[String(item.course_id)]?.color ?? options.custom_cards_3?.[item.course_id]?.color ?? options.custom_cards_3?.[item.plannable.course_id]?.color ?? "#cccccc"; let filter = ""; if (item.plannable.read_state == "read") { filter = "filter: grayscale(40%);" } announcement.innerHTML = `
${item.context_name} ${item.plannable.title} ${convertToDueDate(item.plannable_date)}
`; }); } function markAs(item) { const csrfToken = CSRFtoken(); const completeState = item.planner_override ? !item.planner_override.marked_complete : true; fetch(domain + "/api/v1/planner/overrides/" + (item.planner_override ? "/" + item.planner_override.id : ""), { method: item.planner_override ? "PUT" : "POST", headers: { "content-type":"application/json", "accept":"application/json", "X-CSRF-Token": csrfToken }, body: JSON.stringify({ id: item.planner_override ? item.planner_override.id : null, marked_complete: completeState, plannable_id: item.plannable_id, plannable_type: item.plannable_type }) }) .then(resp => { if (resp.status == 200 || resp.status == 201) { console.log("marked as complete"); item.planner_override = item.planner_override || {}; item.planner_override.marked_complete = completeState; clearTodoList(); createTodoSections(document.querySelector("#bettercanvas-todo-list")); } }) .catch(err => console.error("error marking as complete", err)); } function createTodoViewMore(location, type) { let viewMoreButton = makeElement("button", location, { "className": "bettercanvas-custom-btn bettercanvas-viewmore-btn", "textContent": "View More" }); //viewMoreButton.classList.add("bettercanvas-viewmore-btn"); const showMoreCount = 3; viewMoreButton.addEventListener("click", function (e) { if (type === "announcement") { moreAnnouncementCount += showMoreCount; } else { moreAssignmentCount += showMoreCount; } loadBetterTodo(); }); } // better todo init function setupBetterTodo() { if (options.better_todo !== true) return; if (document.querySelector('#bettercanvas-todo-list')) return; let list = document.querySelector("#right-side"); if (!list) return; //if (!list || list.childElementCount === 0 || list.children[0].id === "bettercanvas-todo-list") return; try { /* save the feedback to append it later */ const feedback = list.querySelector(".events_list.recent_feedback"); list.textContent = ""; list = makeElement("div", list, { "className": "bettercanvas-todosidebar","id": "bettercanvas-todo-list"}); createTodoSections(list); if (feedback) list.append(feedback); } catch (e) { logError(e); } } let delay; let moreAssignmentCount = 0; let moreAnnouncementCount = 0; let filter = "todo"; function loadBetterTodo() { if (options.better_todo !== true) return; try { const discussion_svg = ''; const quiz_svg = ''; const announcement_svg = ''; const assignment_svg = ''; const x_svg = ''; const check_svg = ''; const tag_svg = ''; // end of SVGs const maxAssignmentCount = parseInt(options.num_todo_items) + moreAssignmentCount; const maxAnnouncementCount = parseInt(options.num_todo_items) + moreAnnouncementCount; const hr24 = options.todo_hr24; const now = new Date(); //const csrfToken = CSRFtoken(); let todoAnnouncements = document.querySelector("#bettercanvas-announcement-list"); let todoAssignments = document.querySelector("#bettercanvas-todo-list"); let assignmentsToInsert = []; let announcementsToInsert = []; assignments.then(data => { chrome.storage.sync.get(options.custom_assignments_overflow, storage => { //assignmentData = assignmentData === null ? data : assignmentData; let items = combineAssignments(data); items.forEach((item, index) => { let date = new Date(item.plannable_date); let itemState = options.assignment_states[item.plannable_id]; let svg; switch (item.plannable_type) { case "assignment": svg = assignment_svg; break; case "discussion_topic": svg = discussion_svg; break; case "quiz": svg = quiz_svg; break; case "announcement": svg = announcement_svg; break; default: return; } // if (item.plannable_type === "announcement") { //if (announcementsToInsert.length >= maxAnnouncementCount + 1) return; if (item.plannable_type !== "announcement") { // leaving one extra assignment in the array to indicate there are more and the "view more" button should be created if (assignmentsToInsert.length >= maxAssignmentCount + 1) return; if (filter === "todo" && options.hide_completed === true && item.submissions.submitted === true) return; if (filter === "todo" && ((options.todo_overdues !== true && now >= date) || (options.todo_overdues === true && item.submissions.submitted === true))) return; if (filter === "done" && now <= date && !(itemState?.["rem"] === true || item?.submissions?.submitted === true)) return; //if (item.plannable_type !== "assignment" && item.plannable_type !== "quiz" && item.plannable_type !== "discussion_topic") return; } if (filter === "todo" && ((itemState && itemState["rem"] === true) || (item.planner_override && item.planner_override.marked_complete === true))) return; let listItemContainer = document.createElement("div"); listItemContainer.classList.add("bettercanvas-todo-container"); listItemContainer.innerHTML = '

'; listItemContainer.querySelector(".bettercanvas-todo-item").href = item.html_url; listItemContainer.dataset.id = item.plannable_id; listItemContainer.querySelector('.bettercanvas-todo-icon').innerHTML += svg; let listItem = listItemContainer.querySelector(".bettercanvas-todo-item"); if (itemState?.["lbl"] && itemState["lbl"] !== "") { makeElement("span", listItem.querySelector(".bettercanvas-todo-item-header"), { "className": "bettercanvas-todo-label", "textContent": itemState["lbl"] }); } if (itemState?.["crs"] === true) { listItemContainer.querySelector(".bettercanvas-todo-item").style.textDecoration = "line-through"; } let title = makeElement("a", listItem.querySelector(".bettercanvas-todo-item-header"), { "className": "bettercanvas-todoitem-title", "textContent": item.plannable.title }); if (options.todo_colors === true) title.style = "color:" + (options.custom_cards_3?.[item.course_id]?.color || "inherit") + "!important;"; makeElement("p", listItem, { "className": "bettercanvas-todoitem-course", "textContent": item.context_name }); let format = formatTodoDate(date, item.submissions, hr24); let todoDate = makeElement("p", listItem, { "className": "bettercanvas-todoitem-date", "textContent": format.date }); if (format.dueSoon) todoDate.classList.add("bettercanvas-due-soon"); if (options.hover_preview === true) { const customItem = item.planner_override && item.planner_override.custom && item.planner_override.custom === true; listItem.addEventListener("mouseover", () => { listItem.classList.add("bettercanvas-todo-hover"); let preview = listItemContainer.querySelector(".bettercanvas-hover-preview"); let previewTitle = preview.querySelector(".bettercanvas-preview-title"); let previewText = preview.querySelector(".bettercanvas-preview-text"); clearTimeout(delay); delay = setTimeout(async () => { if (listItem.classList.contains("bettercanvas-todo-hover")) { previewTitle.textContent = item.plannable.title; // custom assignment if (customItem) { previewText.textContent = "Custom assignment"; } else { console.log(item); let found = false; let searchCount = 1; while (searchCount < 5 && found === false) { for (let i = 0; i < announcements.length; i++) { if (announcements[i].id === item.plannable_id) { found = true; if (previewText.textContent === "") { let description = item.plannable_type === "announcement" ? announcements[i].message : announcements[i].description; previewText.textContent = description === "" ? "No details given" : description.replace(/<\/?[^>]+(>|$)/g, " "); } break; } } if (found === false) { let apiLink = domain + "/api/v1/"; if (item.plannable_type === "assignment") { apiLink += `courses/${item.course_id}/assignments/${item.plannable_id}`; } else if (item.plannable_type === "announcement") { apiLink += `announcements?context_codes[]=course_${item.course_id}&per_page=3&page=${searchCount}`; } let data = await getData(apiLink); item.plannable_type === "announcement" ? announcements.push(...data) : announcements.push(data); searchCount++; } } if (found === false) { previewText.textContent = "Couldn't load preview"; } } preview.style.display = "block"; } }, 250); }); listItem.addEventListener("mouseleave", () => { listItem.classList.remove("bettercanvas-todo-hover"); listItemContainer.querySelector(".bettercanvas-hover-preview").style.display = "none"; }); } const actions = listItemContainer.querySelector(".bettercanvas-todo-actions"); let clickOutActions = (e) => { if (e.target.className.includes("bettercanvas")) return; document.body.removeEventListener("click", clickOutActions); actions.style.display = "none"; } listItemContainer.querySelector(".bettercanvas-todo-actions-btn").addEventListener("click", () => { actions.style.display = "block"; setTimeout(() => { document.body.addEventListener("click", clickOutActions); }, 100); }); let removeBtn = makeElement("div", actions, { "className": "bettercanvas-todo-action", "textContent": "Remove" }); removeBtn.innerHTML += x_svg; const dueAt = new Date(item.plannable_date).getTime(); let crossOffBtn = makeElement("div", actions, { "className": "bettercanvas-todo-action", "textContent": "Cross off" }); crossOffBtn.innerHTML += check_svg; crossOffBtn.addEventListener("click", () => { setAssignmentState(item.plannable_id, { "crs": listItemContainer.querySelector(".bettercanvas-todo-item").style.textDecoration === "line-through" ? false : true, "expire": dueAt }); }); let label = makeElement("span", actions, { "className": "bettercanvas-todo-action-tag", "textContent": "Label:" }); label.innerHTML += tag_svg; let labelInput = makeElement("input", actions, { "className": "bettercanvas-todo-input", "type": "text", "placeholder": "Label", "value": itemState && itemState["lbl"] ? itemState["lbl"] : "" }); labelInput.addEventListener("change", (e) => { setAssignmentState(item.plannable_id, { "lbl": e.target.value, "expire": dueAt }); }); removeBtn.addEventListener('click', function () { setAssignmentState(item.plannable_id, { "rem": filter === "todo", "expire": dueAt }); if (item.planner_override && item.planner_override.custom && item.planner_override.custom === true) { // set item as complete locally chrome.storage.sync.get("custom_assignments_overflow", overflow => { chrome.storage.sync.get(overflow["custom_assignments_overflow"], storage => { overflow["custom_assignments_overflow"].forEach(overflow => { for (let i = 0; i < storage[overflow].length; i++) { if (storage[overflow][i].plannable_id === item.plannable_id) { storage[overflow].splice(i, 1); chrome.storage.sync.set({ [overflow]: storage[overflow] }).then(() => { }); break; } } }); }); }); } /*else { // set the item as complete through api fetch(domain + '/api/v1/planner/overrides' + (item.planner_override ? "/" + item.planner_override.id : ""), { method: item.planner_override ? "PUT" : "POST", headers: { "content-type": "application/json", 'accept': 'application/json', 'X-CSRF-Token': csrfToken, }, body: JSON.stringify({ id: item.planner_override ? item.planner_override.id : null, marked_complete: true, plannable_id: item.plannable_id, plannable_type: item.plannable_type }) }).then(resp => { if (resp.status === 200 || resp.status === 201) { let container = listItemContainer.parentElement; container.removeChild(listItemContainer); assignments.forEach(assignment => { if (assignment.plannable_id === item.plannable_id) { item.planner_override = { "marked_complete": true }; } }); loadBetterTodo(); loadCardAssignments(); } }); }*/ }); /* // remove item button listItemContainer.querySelector(".bettercanvas-todo-complete-btn").addEventListener('click', function () { if (item.planner_override && item.planner_override.custom && item.planner_override.custom === true) { // set item as complete locally chrome.storage.sync.get("custom_assignments_overflow", overflow => { chrome.storage.sync.get(overflow["custom_assignments_overflow"], storage => { overflow["custom_assignments_overflow"].forEach(overflow => { for (let i = 0; i < storage[overflow].length; i++) { if (storage[overflow][i].plannable_id === item.plannable_id) { storage[overflow].splice(i, 1); chrome.storage.sync.set({ [overflow]: storage[overflow] }).then(() => { let container = listItemContainer.parentElement; container.removeChild(listItemContainer); loadBetterTodo(); loadCardAssignments(); }); break; } } }); }); }); } else { // set the item as complete through api fetch(domain + '/api/v1/planner/overrides' + (item.planner_override ? "/" + item.planner_override.id : ""), { method: item.planner_override ? "PUT" : "POST", headers: { "content-type": "application/json", 'accept': 'application/json', 'X-CSRF-Token': csrfToken, }, body: JSON.stringify({ id: item.planner_override ? item.planner_override.id : null, marked_complete: true, plannable_id: item.plannable_id, plannable_type: item.plannable_type }) }).then(resp => { if (resp.status === 200 || resp.status === 201) { let container = listItemContainer.parentElement; container.removeChild(listItemContainer); assignmentData.forEach(assignment => { if (assignment.plannable_id === item.plannable_id) { item.planner_override = { "marked_complete": true }; } }); loadBetterTodo(); loadCardAssignments(); } }); } }); */ if (item.plannable_type === "announcement") { announcementsToInsert.push(listItemContainer); } else { assignmentsToInsert.push(listItemContainer); if (item.submissions && item.submissions.submitted) { listItemContainer.classList.add("bettercanvas-todo-item-completed"); } } //} //} }); // appending assignments all at once todoAssignments.textContent = ""; if (assignmentsToInsert.length > 0) { let i; for (i = 0; i < (assignmentsToInsert.length > maxAssignmentCount ? maxAssignmentCount : assignmentsToInsert.length); i++) { todoAssignments.append(assignmentsToInsert[i]); } if (i !== assignmentsToInsert.length) createTodoViewMore(todoAssignments, "assignment"); } else { makeElement("p", todoAssignments, { "className": "bettercanvas-none-due", "textContent": "None" }); } // appending announcements all at once todoAnnouncements.textContent = ""; if (announcementsToInsert.length > 0) { let i; for (i = announcementsToInsert.length - 1; i >= (announcementsToInsert.length - maxAnnouncementCount < 0 ? 0 : announcementsToInsert.length - maxAnnouncementCount); i--) { todoAnnouncements.append(announcementsToInsert[i]); } if (i !== -1) createTodoViewMore(todoAnnouncements, "announcement"); } else { makeElement("p", todoAnnouncements, { "className": "bettercanvas-none-due", "textContent": "None" }); } cleanCustomAssignments(); }); }); } catch (e) { logError(e); } } /* Card color palettes */ let changeColorInterval = null; let colorChanges = []; async function changeColorPreset(colors) { if (colors.length === 0) return; // reset everything //let res = await getData(`${domain}/api/v1/users/self/colors`); clearInterval(changeColorInterval); const csrfToken = CSRFtoken(); const delay = 250; previous = [] colorChanges = []; // sort cards let cards = document.querySelectorAll(".ic-DashboardCard__header"); let sortedCards = []; cards.forEach(card => { sortedCards.push({ "href": card.querySelector(".ic-DashboardCard__link").href, "el": card }); }); sortedCards.sort((a, b) => a.href > b.href ? 1 : -1); // push each color change into a queue try { sortedCards.forEach((card, i) => { let previousColor = rgbToHex(card.el.querySelector(".ic-DashboardCard__header_hero").style.backgroundColor); previous.push(previousColor); // Object.keys(res.custom_colors).forEach(item => { //let item_id = item.split("_")[1]; let course_id = card.href.split("courses/")[1]; //if (card.href.includes(item_id)) { let cnum = i % colors.length; let changeCardColor = () => { fetch(domain + "/api/v1/users/self/colors/courses_" + course_id, { method: "PUT", headers: { "content-type": "application/json", 'accept': 'application/json', 'X-CSRF-Token': csrfToken, }, body: JSON.stringify({ "hexcode": colors[cnum] }) }).then(() => { card.el.querySelector(".ic-DashboardCard__header_hero").style.backgroundColor = colors[cnum]; card.el.querySelector(".ic-DashboardCard__header-title span").style.color = colors[cnum]; card.el.querySelector(".ic-DashboardCard__header-button-bg").style.backgroundColor = colors[cnum]; }); } colorChanges.push(changeCardColor); card.el.querySelector(".ic-DashboardCard__header_hero").style.backgroundColor = colors[cnum]; card.el.querySelector(".ic-DashboardCard__header-title span").style.color = colors[cnum]; card.el.querySelector(".ic-DashboardCard__header-button-bg").style.backgroundColor = colors[cnum]; //} // }); }); } catch (e) { logError(e); colorChanges = []; } changeGradientCards(); // go through the queue until empty changeColorInterval = setInterval(() => { if (colorChanges.length > 0) { let current = colorChanges.shift(); current(); } else { clearInterval(changeColorInterval); } }, delay); // set colors to revert back to chrome.storage.local.get("previous_colors", local => { const now = Date.now(); if (local["previous_colors"] === null || now >= local["previous_colors"].expire) { chrome.storage.local.set({ "previous_colors": { "colors": previous, "expire": now + 86400000 } }); } }); } /* Dark mode */ function generateDarkModeCSS() { const darkmode_css = "#announcementWrapper>div>div,#breadcrumbs,#calendar-app .fc-agendaWeek-view .fc-body,#calendar-app .fc-event,#calendar-app .fc-month-view .fc-body,#calendar-drag-and-drop-container .fc-agendaWeek-view .fc-body,#calendar-drag-and-drop-container .fc-event,#calendar-drag-and-drop-container .fc-month-view .fc-body,#content-wrapper .user_content.not_design_tools h3,#context-list-holder,.bettercanvas-course-credit,#kl_banner,#kl_banner_left,#kl_banner_right,#kl_content_block_0,#kl_custom_block_0,#kl_custom_block_1,#kl_custom_block_2,#kl_readings p,#kl_wrapper_3,#kl_wrapper_3 .ic-Table,#kl_wrapper_3 .table,#kl_wrapper_3.kl_colored_headings #kl_banner #kl_banner_left,#kl_wrapper_3.kl_colored_headings #kl_banner .kl_subtitle,#kl_wrapper_3.kl_colored_headings>div,#kl_wrapper_3.kl_colored_headings_box_left>div,#media_comment_maybe,#minical,#nav-tray-portal>span>span,#questions .group_top,#questions.assessing,#syllabus tr.date.date_passed td,#syllabus tr.date.date_passed th,#undated-events,#undated-events .event,.Day-styles__root,.EmptyDays-styles__root,.Grouping-styles__title,.Grouping-styles__title::after,.PlannerHeader-styles__root,.ac-result-container,.agenda-wrapper,.al-options,.bettercanvas-assignment-container,.bjXfh_daKB,.bjXfh_daKB span,.bottom-reply-with-box,.canvas-rce__skins--root,.ccWIh_bGBk,.closed-for-comments-discussions-v2__wrapper,.conversations .panel,.dCppM_ddES,.discussion-section h4,.discussion-section p,.discussion-section ul,.discussion_entry,.discussions-v2__container-image,.discussions-v2__placeholder,.dpCPB_caGd,.dropdown-menu,.dropdown-menu .divider,.even .slick-cell,.event-details,.fLzZc_bGBk,.form,.form-dialog .form-controls,.header-bar,.ic-Dashboard-header__layout,.ic-Dashboard-header__title,.ic-DashboardCard,.ic-DashboardCard__header_content,.ic-discussion-row,.ic-notification__content,.ig-list .ig-row.ig-row-empty,.instructure_file_link,.item-group-condensed .ig-header,.item-group-condensed .ig-row,.item-group-condensed .item-group-expandable,.item-group-container,.item-group-expandable .emptyMessage,.kl_image_round_white_border,.kl_image_white_border,.kl_mod_text,.message-list .messages>li,.module-sequence-footer .module-sequence-footer-content,.nav-icon,.outcomes-browser .outcomes-content,.outcomes-browser .outcomes-main,.outcomes-browser .outcomes-sidebar,.pages.show .page-title,.pagination ul>li>a,.pagination ul>li>span,.pinned-discussions-v2__wrapper,.popover,.question,.question_editing,.quiz-submission,.rubric_container .rubric_title,.submission-details-comments .comments,.submission-late-pill span,.submission-missing-pill span,.toolbarView .headerBar,.tox .tox-menubar,.tox .tox-split-button .tox-tbtn.tox-split-button__chevron,.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary,.tox:not(.tox-tinymce-inline) .tox-editor-header,.ui-datepicker .ui-datepicker-time,.ui-datepicker .ui-dialog .ui-datepicker-time,.ui-datepicker .ui-dialog .ui-widget-header.ui-datepicker-header,.ui-dialog .ui-datepicker .ui-datepicker-time,.ui-dialog .ui-datepicker .ui-widget-header.ui-datepicker-header,.ui-dialog .ui-dialog-buttonpane,.ui-dialog .ui-dialog-titlebar.ui-widget-header,.ui-kyle-menu,.ui-tabs .ui-tabs-nav .kl_panel_heading.ui-state-default:not(.ui-tabs-active),.ui-tabs .ui-tabs-nav li.ui-state-hover,.ui-tabs .ui-tabs-nav li.ui-tabs-active,.ui-tabs .ui-tabs-nav li:hover,.ui-tabs .ui-tabs-panel,.ui-widget-content,.unpinned-discussions-v2__wrapper,.unpublished_courses_redesign .ic-DashboardCard__box__header,body,code,img.kl_image_round_white_border,img.kl_image_white_border,.bettercanvas-course-percent,pre,table.summary tbody th,table.summary td,.erWSf_bGBk,.fdyuz_bGBk,.eHzxc_bGBk,.dNoYT_bGBk,.fOyUs_fZwI, .fOyUs_kXoP,.tox .tox-edit-area__iframe,.dLyYq_bGBk,.quiz_comment,.discussion-entries .entry,.file-upload-submission,.ftPBL_bGBk:not(.ftPBL_bGiS),.ColorPicker__Container,#right_side .content_box,.jumbotron,.card,.ac-token,.error_box .error_text,table.seas-homepage-table,.with-left-side #left-side, .assignment-student-header,#calendar-list-holder, #other-calendars-list-holder, #undated-events,#left-side,.ic-app-course-menu.with-left-side #left-side.XOwIb_eLeB:not([aria-selected]):not([aria-disabled]):hover, .XOwIb_eLeB[aria-selected],span.fOyUs_bGBk.fOyUs_desw.bDzpk_bGBk.bDzpk_busO.bDzpk_cQFX.bDzpk_bZNM,.bettercanvas-todo-complete-btn,.bettercanvas-card-grade,div[style*='background-color: #fff'],div[style*='background: #fff'],div[style*='background-color: #ffffff'],div[style*='background: #ffffff'],span[style*='background-color: #fff'],span[style*='background: #fff'],#right_side div.comment,.fOyUs_dUgE, .fOyUs_bvKN,.css-1fwux0x-view--block,.css-1v8v5q1-optionItem,#comments-tray,.css-vxe90h-view--inlineBlock,.bettercanvas-todo-actions,.css-sg1rn7-view{background:var(--bcbackground-0)!important}#minical .fc-widget-content{border:1px solid var(--bcbackground-0)!important}#kl_wrapper_3.kl_colored_headings #kl_banner .kl_subtitle{border-top:3px solid var(--bcbackground-0)!important;border-bottom:3px solid var(--bcbackground-0)!important}#submit_file_button,span[style*='background-color: #fbeeb8'],.bettercanvas-todo-label{color:var(--bcbackground-0)!important}.eHQDY_dTxv{stroke:var(--bcbackground-0)!important}#calendar-app .fc-agendaWeek-view .fc-event,#calendar-drag-and-drop-container .fc-agendaWeek-view .fc-event,#context-list .context_list_context:hover,#google_docs_tree li.file:hover,#planner-today-btn,#questions.assessment_results .question .header,#syllabus tr.date.related td,#syllabus tr.date.related th,#syllabus tr.date.selected td,#syllabus tr.date.selected th,.Button,.ac-input-box,.agenda-day.agenda-today,.bettercanvas-assignment-container:hover,.btn,.discussion-reply-box,.discussions-v2__wrapper>span>span>span>span>button>span,.dropdown-menu li>a:focus,.dropdown-menu li>a:hover,.dropdown-submenu:hover>a,.ef-item-row:hover,.extension-linkpreview,.fOyUs_bGBk.fOyUs_desw.bDzpk_bGBk.bDzpk_busO.bDzpk_fZWR.bDzpk_qOas,.fc-event .fc-bg,.hypodivcalc,.ic-Table.ic-Table--striped tbody tr:nth-child(odd),.mini_calendar .day.has_event,.odd .slick-cell,.outcomes-browser .outcomes-toolbar,.question .header,.slick-header-column,.stream-details tr:hover,.stream_header:hover,.submission_attachment button>span,.tox .tox-menu,.tray-with-space-for-global-nav>div>span>form>button>span,.ui-button,.ui-tabs .ui-tabs-nav li.ui-tabs-active,.uneditable-input,.yyQPt_cSXm,div.checkbox,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea,thead th,ul.outcome-level li.selected a,.eMdva_bgqc,.fQfxa_dqAF.fQfxa_buuG,div.form-column-right label:hover, div.overrides-column-right label:hover,.ic-tokeninput-input,.ic-tokens,.ic-tokeninput-list,.DyQTK_ddES,#gradebook_header,table.seas-homepage-table tr:nth-child(odd),#assignments-student-footer,.muted-notice,.kl_panels_wrapper .ui-accordion-header, .kl_wrapper .ui-accordion-header,.list-view a.active,#calendars-context-list .context_list_context:hover, #other-calendars-context-list .context_list_context:hover,.bettercanvas-todo-complete-btn:hover,.bettercanvas-custom-btn,.bettercanvas-skeleton-text,.bettercanvas-hover-preview,.bettercanvas-gpa-edit-btn,div[style*='background-color: rgb(229, 242, 248)'],div[style*='background-color: rgb(245, 245, 245)'],.css-7naoe-textInp,.css-7naoe-textInput__facade,#assignment_sort_order_select_menu,#course_select_menu,.css-1dn3ise-textInput__facade,.css-1veueey-textInput__facade,.bettercanvas-todo-action:hover{background:var(--bcbackground-1)!important}.ic-DashboardCard__placeholder-svg .ic-DashboardCard__placeholder-animates>*{fill:var(--bcbackground-1)!important}.bettercanvas-hover-preview::after{background:linear-gradient(0deg, var(--bcbackground-1) 50%, transparent)}#calendar-app .fc-month-view .fc-today,#calendar-drag-and-drop-container .fc-month-view .fc-today,#content-wrapper .user_content.not_design_tools table tbody tr:nth-child(even) td,#kl_content_block_0 h3:nth-child(1) i,#kl_custom_block_0 h3:nth-child(1) i,#kl_custom_block_1 h3:nth-child(1) i,#kl_custom_block_2 h3:nth-child(1) i,.ajas-search-widget__btn--search,.alert-info,.discussion-section.alert .discussion-points,.discussion-section.alert .discussion-title,.extension-linkpreview:hover,.ic-Table.ic-Table--hover-row tbody tr.ic-Table__row--bg-alert:hover,.ic-Table.ic-Table--hover-row tbody tr.ic-Table__row--bg-danger:hover,.ic-Table.ic-Table--hover-row tbody tr.ic-Table__row--bg-neutral:hover,.ic-Table.ic-Table--hover-row tbody tr.ic-Table__row--bg-success:hover,.ic-Table.ic-Table--hover-row tbody tr:hover,.ic-flash-error,.ic-flash-info,.ic-flash-success,.ic-flash-warning,.ig-list .ig-row:hover,.context_module_item.context_module_item_hover,.tox .tox-mbtn--active,.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active),.tox .tox-split-button .tox-tbtn.tox-split-button__chevron:hover,.tox .tox-split-button:hover,.tox .tox-tbtn.tox-tbtn--enabled:hover,.tox .tox-tbtn:hover,.ui-menu .ui-menu-item .ui-progressbar a.ui-widget-header,.ui-menu .ui-menu-item a.ui-state-active,.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-hover,.ui-progressbar .ui-menu .ui-menu-item a.ui-widget-header,::-webkit-scrollbar-track,div.checkbox:hover,.gradebook-cell.grayed-out,.baylor-table tr:nth-of-type(2n + 1){background:var(--bcbuttons)!important}#kl_content_block_0 h3:nth-child(1),#kl_content_block_0 h3:nth-child(1) i,#kl_custom_block_0 h3:nth-child(1),#kl_custom_block_0 h3:nth-child(1) i,#kl_custom_block_1 h3:nth-child(1),#kl_custom_block_1 h3:nth-child(1) i,#kl_custom_block_2 h3:nth-child(1),#kl_custom_block_2 h3:nth-child(1) i,#kl_wrapper_3.kl_colored_headings #kl_modules h3,#kl_wrapper_3.kl_colored_headings #kl_modules h3:not(.ui-state-default) i,#kl_wrapper_3.kl_colored_headings>div>h3:not(.ui-state-default),#kl_wrapper_3.kl_colored_headings>div>h3:not(.ui-state-default) i,#kl_wrapper_3.kl_colored_headings_box_left #kl_modules h3,#kl_wrapper_3.kl_colored_headings_box_left #kl_modules h3 i,#kl_wrapper_3.kl_colored_headings_box_left>div>h3 i,#kl_wrapper_3.kl_colored_headings_box_left>div>h3:not(.ui-state-default),#kl_wrapper_3.kl_emta h3:not(.ui-state-default),.ic-app-header__menu-list-link:focus,.kl_flex_column h4,.tox .tox-collection--list .tox-collection__item--enabled,ul.outcome-level li:focus,ul.outcome-level li:hover{background-color:var(--bcbuttons)!important}.eHQDY_dTxv{stroke:var(--bcbuttons)}.no-touch .ic-DashboardCard:hover{box-shadow:0 4px 10px rgb(0 0 0)!important}#calendar-drag-and-drop-container .fc-row .fc-content-skeleton td,#calendar-drag-and-drop-container .fc-row .fc-helper-skeleton td,.bettercanvas-course-credit,#kl_content_block_0,#kl_custom_block_0,#kl_custom_block_1,#kl_custom_block_2,#kl_wrapper_3.kl_colored_headings>div,#kl_wrapper_3.kl_colored_headings_box_left>div,#minical,#questions .group_bottom,#questions .group_top,#quiz_edit_wrapper #quiz_tabs #quiz_options_form .option-group,#quiz_show .description.teacher-version,.Button,.Container__DueDateRow,.CourseImageSelector,.ac-input-box,.ac-result-container,.ajas-search-widget__form input,.btn,.calendar .fc-row .fc-content-skeleton td,.calendar .fc-row .fc-helper-skeleton td,.closed-for-comments-discussions-v2__wrapper,.discussion-entries .entry,.discussion-reply-box,.discussion_entry>.discussion-entry-reply-area,.discussions-v2__wrapper>span>span>span>span>button>span,.form-actions,.ic-flash-error,.ic-flash-info,.ic-flash-success,.ic-flash-warning,.ig-list .ig-row,.item-group-condensed .ig-header,.item-group-condensed .item-group-expandable,.mini-cal-header,.mini_calendar,.outcomes-browser .outcomes-main,.outcomes-browser .outcomes-toolbar,.panel-border,.pinned-discussions-v2__wrapper,.question,.question .header,.question_editing,.quiz-submission,.rubric_container td,.rubric_container th,.submission-details-container,.submission_attachment button>span,.table-bordered,.toolbarView .headerBar,.tray-with-space-for-global-nav>div>span>form>button>span,.ui-button,.uneditable-input,.unpinned-discussions-v2__wrapper,form.question_form .form_answers .answer,.bettercanvas-course-percent,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea,.fdyuz_bGBk,.tox .tox-edit-area,.quiz_comment,.ic-tokens,.ic-tokeninput-list,.DyQTK_ddES,.ac-token,.muted-notice,.ui-state-default, .ui-widget-header .ui-state-default,.ui-widget-content,.bettercanvas-custom-btn,.bettercanvas-gpa-edit-btn,.css-26xxi8-view--block,.css-9fqfm7-view--block,.bettercanvas-todo-actions{border:1px solid var(--bcborders)!important}#content-wrapper .user_content.not_design_tools table td,#content-wrapper .user_content.not_design_tools table th,table.seas-homepage-table,.avatar,.css-7naoe-textInput__facade,.css-1dn3ise-textInput__facade{border:2px solid var(--bcborders)!important}#course_select_menu,#assignment_sort_order_select_menu,#TextInput_0{border:none!important}#assignment_show .student-assignment-overview,#grades_summary th.title,#kl_wrapper_3.kl_colored_headings h4,#kl_wrapper_3.kl_colored_headings_box_left h4,#minical .fc-toolbar,#quiz_show ul#quiz_student_details,#right-side .h2,#right-side h2,.CompletedItemsFacade-styles__root,.Container__DueDateRow-item,.EmptyDays-styles__root,.PlannerItem-styles__root,.agenda-day,.blnAQ_kWwi,.container_0 .slick-cell,.container_1 .slick-cell,.conversations .panel,.course_details td,.dropdown-menu .divider,.ef-directory-header,.ef-header,.event-details-content,.event-details-footer,.event-details-header,.header-bar,.hr,.ic-Action-header.ic-Action-header--before-item-groups,.ic-Dashboard-header__layout,.ic-Table td,.ic-Table th,.ic-app-nav-toggle-and-crumbs,.item-group-condensed .ig-row,.message-detail.conversations__message-detail .message-content>li,.message-detail.conversations__message-detail .message-header,.message-detail.span8 .message-content>li,.message-detail.span8 .message-header,.message-list .messages>li,.nav_list li.disabled,.page-action-list a,.page-header,.quiz-header,.recent-activity-header,.recent_activity>li,.slick-header-column.ui-state-default,.submission-details-header__heading-and-grades,.ui-datepicker .ui-dialog .ui-widget-header.ui-datepicker-header,.ui-dialog .ui-datepicker .ui-widget-header.ui-datepicker-header,.ui-dialog .ui-dialog-titlebar.ui-widget-header,.unpublished_courses_redesign .ic-DashboardCard__box__header,legend,table.summary caption,table.summary tbody th,table.summary td,table.summary thead th,.communication_message,.file-upload-submission,.submission-details-header__heading-and-grades,#right_side .content_box,.assignment-student-header,.bettercanvas-gpa-course{border-bottom:1px solid var(--bcborders)!important}#planner-today-btn,.al-options,.border,.dpCPB_caGd,.fc-unthemed .fc-divider,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead,.qBMHb_cSXm,.tox .tox-collection--list .tox-collection__group,.tox .tox-menu,.ui-tabs .ui-tabs-nav li.ui-tabs-active,.ui-tabs .ui-tabs-nav li.ui-tabs-active.ui-state-hover,.ui-tabs .ui-tabs-nav li.ui-tabs-active:hover,.ui-tabs .ui-tabs-nav li:hover,.ui-tabs .ui-tabs-panel,.fOyUs_dsNY, .fOyUs_tIxX,.fQfxa_dqAF.fQfxa_buuG,.question .question_comment.question_neutral_comment,#assignments-student-footer,.MyTable,#inbox-conversation-holder *,.css-1vqfmz1-view{border-color:var(--bcborders)!important}.discussion-section.message_wrapper table{border:4px solid var(--bcborders)!important}.nav_list li.navitem{border:solid var(--bcborders)!important;border-width:0 1px 1px!important}#questions .assessment_question_bank,#questions .insufficient_count_warning,#questions .question_holder.group,.container_0 .slick-cell,.container_1 .slick-cell,.ef-main .ef-folder-content,.rubric_container .rubric_title,.slick-header-column.ui-state-default,.topic .entry-content,body.responsive_awareness .message-list-scroller,ul.outcome-level{border-right:1px solid var(--bcborders)!important}#questions .assessment_question_bank,#questions .insufficient_count_warning,#questions .question_holder.group,.container_0 .slick-cell:first-child,.container_0 .slick-header-column:first-child,.outcomes-browser .outcomes-content,.rubric_container .rubric_title,.table-bordered td,.table-bordered th,.topic .entry-content,.submission-details-comments .comments{border-left:1px solid var(--bcborders)!important}#assignment_show .student-assignment-overview,#grades_summary tr.final_grade,#quiz_show ul#quiz_student_details,.discussion-entries .entry .entry,.ef-footer,.entry>.bottom-reply-with-box .discussion-entry-reply-area,.form-dialog .form-controls,.ic-app-footer,.module-sequence-footer .module-sequence-footer-content,.question.matching_question .answer,.question.multiple_answers_question .answer,.question.multiple_choice_question .answer,.question.true_false_question .answer,.rubric_container .rubric_title,.slick-header-column.ui-state-default,.table td,.table th,.dNoYT_bGBk{border-top:1px solid var(--bcborders)!important}.discussions-v2__container-image{border:.125rem dashed var(--bcborders)!important}.Button--active.ui-button,.Button.Button--active,.Button.active,.active.ui-button,.btn.Button--active,.btn.active,.btn.ui-button.ui-state-active,.message-list .message-count,.mini_calendar .day.today,.ui-button.ui-state-active,.ui-button.ui-state-active.ui-state-hover,.ui-button.ui-state-active:hover,.ui-progressbar .btn.ui-button.ui-widget-header,.ui-progressbar .ui-button.ui-widget-header,::-webkit-scrollbar-thumb,.ic-unread-badge__total-count,#calendar-app .fc-month-view .fc-today{background:var(--bcbackground-2)!important}.discussion-entries .entry .entry,.kl_image_white_border{border:0!important}.ac-result-wrapper:before{border-bottom:10px solid var(--bcborders)}.eIQkd_bGBk,.ui-tabs .ui-tabs-nav,.eHzxc_bGBk,.quiz_comment:after,.quiz_comment:before{border-bottom-color:var(--bcborders)!important}.ic-item-row{box-shadow:0 -1px var(--bcborders),inset 0 -1px var(--bcborders)!important}#GradeSummarySelectMenuGroup span,#kl_content_block_0 h3:nth-child(1),#kl_content_block_0 h3:nth-child(1) i,#kl_custom_block_0 h3:nth-child(1),#kl_custom_block_0 h3:nth-child(1) i,#kl_custom_block_1 h3:nth-child(1),#kl_custom_block_1 h3:nth-child(1) i,#kl_custom_block_2 h3:nth-child(1),#kl_custom_block_2 h3:nth-child(1) i,#kl_wrapper_3.kl_colored_headings #kl_modules h3,#kl_wrapper_3.kl_colored_headings #kl_modules h3:not(.ui-state-default) i,#kl_wrapper_3.kl_colored_headings>div>h3:not(.ui-state-default),#kl_wrapper_3.kl_colored_headings>div>h3:not(.ui-state-default) i,#kl_wrapper_3.kl_colored_headings_box_left #kl_modules h3,#kl_wrapper_3.kl_colored_headings_box_left #kl_modules h3 i,#kl_wrapper_3.kl_colored_headings_box_left>div>h3 i,#kl_wrapper_3.kl_colored_headings_box_left>div>h3:not(.ui-state-default),#kl_wrapper_3.kl_emta h3:not(.ui-state-default),.bettercanvas-card-grade,.bettercanvas-card-header,.discussion-fyi,.ic-DashboardCard__action-badge,.ic-app-header__menu-list-item.ic-app-header__menu-list-item--active .menu-item__text,.ig-list .ig-row,.kl_flex_column h4,.menu-item__badge,.mini_calendar .day.other_month,.ui-tabs .ui-tabs-nav li.ui-tabs-active a,.bettercanvas-course-percent,.bettercanvas-todo-container,.bettercanvas-todo-container:hover,.MlJlv_ebWM,.bettercanvas-todo-item,.bettercanvas-todo-item:hover,.bettercanvas-hover-preview,.baylorMainContainer,.baylor-table td,.fOyUs_dUgE, .fOyUs_bvKN,.muted,h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,blockquote small,.css-1v8v5q1-optionItem,.Button,button,.btn,h1,h2,h3,h4,h5,h6,#tinymce,.PlannerItem-styles__type > span,.bettercanvas-todo-actions{color:var(--bctext-0)!important}.ic-app-header__menu-list-item.ic-app-header__menu-list-item--active svg,.ToDoSidebarItem__Icon,.bettercanvas-todo-svg{fill:var(--bctext-0)!important}.ic-avatar{border:2px solid var(--bctext-0)!important}#breadcrumbs>ul>li+li:last-of-type a,#calendar-app .fc-agendaWeek-view .fc-axis,#calendar-app .fc-agendaWeek-view .fc-widget-header,#calendar-app .fc-month-view .fc-widget-header,#calendar-drag-and-drop-container .fc-agendaWeek-view .fc-axis,#calendar-drag-and-drop-container .fc-agendaWeek-view .fc-widget-header,#calendar-drag-and-drop-container .fc-month-view .fc-widget-header,#content-wrapper .user_content.not_design_tools h3,.bettercanvas-course-credit,#kl_banner,#kl_banner h2,#kl_banner_left,#kl_banner_right,#kl_custom_block_0,#kl_readings p,#kl_wrapper_3.kl_colored_headings #kl_banner #kl_banner_left,#kl_wrapper_3.kl_colored_headings #kl_banner .kl_subtitle,#kl_wrapper_3.kl_colored_headings #kl_modules h3:not(.ui-state-default) i,#kl_wrapper_3.kl_colored_headings h4,#kl_wrapper_3.kl_colored_headings>div>h3:not(.ui-state-default) i,#kl_wrapper_3.kl_colored_headings_box_left #kl_modules h3 i,#kl_wrapper_3.kl_colored_headings_box_left h4,#kl_wrapper_3.kl_colored_headings_box_left>div>h3 i,#kl_wrapper_3.kl_emta,#minical .fc-toolbar .h2,#minical .fc-toolbar h2,#minical .fc-widget-content,#nav-tray-portal>span>span>div>div>.navigation-tray-container.courses-tray>.tray-with-space-for-global-nav>div>ul>li>div,#right-side .details .header,#right-side .right-side-list li em,#right-side .right-side-list li p,.Day-styles__root h2,.EmptyDays-styles__root,.HwBsD_blJt,.HwBsD_fqzO,.MlJlv_dnnz,.PlannerItem-styles__due,.PlannerItem-styles__score,.ToDoSidebarItem__Info,.ToDoSidebarItem__Info li,.ac-input-box,.accessible-toggler,.bettercanvas-assignment-container,.bettercanvas-assignment-container:hover,.bjXfh_daKB,.bjXfh_daKB span,.cWmNi_bGBk,.ccWIh_bGBk,.close,.comment_list .comment,.discussion-points,.discussion-pubdate,.discussion-rate-action,.discussion-reply-action,.discussion-section h4,.discussion-section p,.discussion-section ul,.discussion-tododate,.discussions-v2__container-image>span>div,.dropdown-menu li>a,.ef-plain-link,.ef-plain-link:hover,.enRcg_bGBk.enRcg_qFsi,.entry-content span,.esvoZ_drOs,.event-details-timestring,.extension-ac a:hover,.extension-linkpreview,.fCrpb_egrg,.fCrpb_egrg.fCrpb_fVUh,.fNHEA_blJt,.fQfxa_bCUx.fQfxa_buuG,.fc-agendaWeek-view .fc-event-container a[class*=group_] .fc-content .fc-time,.fc-event,.fc-event:hover,.fwfoD_fsuY,.header-row a.sort-field-active i,.hypodivcalc,.ic-Dashboard-header__title,.ic-DashboardCard__header-subtitle,.ic-DashboardCard__header-term,.ic-discussion-content-container,.ig-header .name,.ig-list .ig-row a.ig-title,.ig-type-icon,.item-group-condensed .ig-header,.item-group-expandable .emptyMessage,.jpyTq_bGBk,.kl_mod_text,.kl_readings span,.list-view a.active,.message-detail.conversations__message-detail .no-messages,.message-detail.span8 .no-messages,.message-list .author,.message-list .subject,.message.user_content div,.mini-cal-header,.mini_calendar .day,.nav-icon,.nav_list li.navitem,.ofhgV_ddES,.pages.show .page-title,.planner-day,.standalone-icon:before,.submission_attachment button>span,.tox .tox-collection__item,.tox .tox-insert-table-picker__label,.tray-with-space-for-global-nav>div>span>form>button>span,.tree i[class*=icon-],.tree i[class^=icon-],.ui-button,.ui-state-default,.ui-tabs .ui-tabs-nav li a,.ui-widget .fc-event,.ui-widget-content,.ui-widget-header .ui-state-default,.uneditable-input,.user_content.enhanced,.user_content,.user_content.enhanced p,body,code,input.enRcg_bGBk[type].enRcg_qFsi,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],label.fCrpb_egrg,legend,pre,select,textarea,ul#question_list li i, .enRcg_bGBk.enRcg_bLsb, input.enRcg_bGBk[type].enRcg_bLsb,.erWSf_bGBk,.faJyW_blJt,.eMdva_bgqc,#right-side p.email_channel,.dpCPB_caGd,.XOwIb_ddES,.fdyuz_bGBk,.fOyUs_fZwI, .fOyUs_kXoP,.fQfxa_dqAF.fQfxa_buuG,.communication_message .header .header_title .title,.communication_message .header .header_title .sub_title,.ic-tokens,ic-tokeninput-input,.ftPBL_cuDj,.dUOHu_eCSh,.blnAQ_eCSh,#gradebook_header,.bettercanvas-assignment-link,.bettercanvas-assignment-link:hover,.jumbotron,.card,.ac-token,span[style='color: #000000;'],.bettercanvas-gpa-edit-btn{color:var(--bctext-1)!important}.list-view a.active{border-left:2px solid var(--bclinks)!important}.ToDoSidebarItem svg,.discussions-v2__wrapper>span>span>span>span>button>span>span>svg,.ic-DashboardCard__action-layout svg,.tox .tox-split-button__chevron svg,.tox .tox-tbtn svg,.tox .tox-tbtn svg g,.tox .tox-tbtn svg path{fill:var(--bctext-1)!important}.caret{border-top:4px solid var(--bctext-1)!important}#last_saved_indicator,#minical .fc-other-month,#nav_disabled_list li.navitem,.ToDoSidebarItem__Info>span,.extension-aldue,.ic-item-row__meta-content-timestamp p,.ig-list .icon-drag-handle,.ig-list .ig-row .ig-empty-msg,.message-detail.conversations__message-detail .date,.message-detail.conversations__message-detail .user-info .context,.message-detail.span8 .date,.message-detail.span8 .user-info .context,.message-list .summary,.profile_table .data_description,.question .header .question_points_holder,.student_assignment .context,.tox .tox-collection__item-accessory,.yyQPt_blJt,ul#question_list.read_only li.seen,ul#question_list li.current_question,.css-1sr6v3o-text{color:var(--bctext-2)!important}#content-wrapper .user_content.not_design_tools a,#media_comment_maybe,#nav-tray-portal a,.ToDoSidebarItem__Title a,.message-list .date,a,a:focus,a:hover,.fQfxa_bCUx.fQfxa_eCSh,.fake-link,.no-touch .ic-DashboardCard__action:hover,.enRcg_bGBk.enRcg_fpfC, input.enRcg_bGBk[type].enRcg_fpfC{color:var(--bclinks)!important}#minical .fc-bg .fc-state-highlight,#submit_file_button,.StickyButton-styles__root,.ic-DashboardCard__action-badge,.menu-item__badge,ul.outcome-level li.selected a::before,.eMdva_pypk .eMdva_dnnz,.ic-notification__icon,.fQfxa_dqAF.fQfxa_eCSh,.recent_activity>li .unread-count,.recent_activity>li .unread.message-list .read-state:before,.eMdva_pypk .eMdva_dnnz,.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled),.nav-badge,.message-list .read-state:before,.ic-unread-badge,.cECYn_bXiG,.unread-grade,.bettercanvas-todo-label{background:var(--bclinks)!important}.eHQDY_ddES .eHQDY_eWAY{stroke:var(--bclinks)!important}.message-list .messages>li:hover{box-shadow:inset -4px 0 0 var(--bclinks)!important}.agenda-event__item-container:focus,.agenda-event__item-container:hover{box-shadow:inset 3px 0 0 var(--bclinks)}#calendar-app .fc-agendaWeek-view .fc-day-grid .fc-today,#calendar-drag-and-drop-container .fc-agendaWeek-view .fc-day-grid .fc-today{box-shadow:.5px -6px 0 0 var(--bclinks)}.message-list .read-state.read:before{box-shadow:0 0 0 1px var(--bclinks)}#minical .event::after{border:1px solid var(--bclinks)}.ic-notification{border:2px solid var(--bclinks)!important}.eMdva_pypk,.tox .tox-edit-area.active, .tox .tox-edit-area.active iframe,.emSEn_QUBp:hover{border-color:var(--bclinks)!important}.eHQDY_ddES .eHQDY_eWAY{stroke:var(--bclinks)}.ui-dialog .ui-dialog-titlebar-close.ui-state-hover, .ui-dialog .ui-dialog-titlebar-close.ui-state-focus{box-shadow:0 0 0 2px var(--bclinks)}select.ic-Input:focus, textarea.ic-Input:focus, input[type=text].ic-Input:focus, input[type=password].ic-Input:focus, input[type=datetime].ic-Input:focus, input[type=datetime-local].ic-Input:focus, input[type=date].ic-Input:focus, input[type=month].ic-Input:focus, input[type=time].ic-Input:focus, input[type=week].ic-Input:focus, input[type=number].ic-Input:focus, input[type=email].ic-Input:focus, input[type=url].ic-Input:focus, input[type=search].ic-Input:focus, input[type=tel].ic-Input:focus, input[type=color].ic-Input:focus, .uneditable-input.ic-Input:focus{outline-color:var(--bclinks)}.discussion-section.message_wrapper table{border:4px solid red!important}.extension-linkpreview,.hypodivcalc,.kl_shadow_2,.kl_shadow_b2,.tox .tox-split-button:hover{box-shadow:none!important}#kl_wrapper_3.kl_colored_headings #kl_modules h3:not(.ui-state-default) i,#kl_wrapper_3.kl_colored_headings>div>h3:not(.ui-state-default) i,#kl_wrapper_3.kl_colored_headings_box_left #kl_modules h3 i,#kl_wrapper_3.kl_colored_headings_box_left>div>h3 i{border:none!important}.extension-aldue:hover,.ic-DashboardCard,.navigation-tray-container,.bettercanvas-gpa-card{box-shadow:0 2px 5px #00000080!important}::-webkit-scrollbar{width:15px}.ui-datepicker .ui-datepicker-time,.ui-datepicker .ui-dialog .ui-datepicker-time,.ui-dialog .ui-datepicker .ui-datepicker-time,.ui-dialog .ui-dialog-buttonpane,hr{border-top:none!important}#right-side .shared-space h2{border-bottom-style:none!important}#kl_content_block_0 h3:nth-child(1) i,#kl_custom_block_0 h3:nth-child(1) i,#kl_custom_block_1 h3:nth-child(1) i,#kl_custom_block_2 h3:nth-child(1) i{border:0!important}.ig-header .name{text-shadow:none!important}#right-side .events_list .event-details:after,#right-side .events_list .todo-details:after,#right-side .to-do-list .event-details:after,#right-side .to-do-list .todo-details:after{display:none!important},.muted-notice{background-image:none!important}.message-list .read-state.read:before{background:none!important}.ic-DashboardCard__header-button,.ic-app-header__secondary-navigation{background:none!important;border:none!important}.published-status.published .icon-publish::before{color:#0b874b!important}.ic-app-header{background:var(--bcsidebar)!important}.ic-app-header__menu-list-item.ic-app-header__menu-list-item--active .ic-app-header__menu-list-link, .ic-app-header__menu-list-link:hover{background:#0000004f!important}.ic-app-header__logomark-container{background:none!important}.ic-app-header__menu-list-link svg,.ic-app-header__menu-list-item.ic-app-header__menu-list-item--active svg{fill:var(--bcsidebar-text)!important}.menu-item-icon-container,.ic-app-header__menu-list-link .menu-item__text,.ic-app-header__menu-list-item.ic-app-header__menu-list-item--active .menu-item__text{color:var(--bcsidebar-text)!important} .ic-DashboardCard,.ic-DashboardCard__header_content,.bettercanvas-assignment-container,.recent_feedback .event-details{background:none!important} "; let css = (options.device_dark === true ? "@media (prefers-color-scheme: dark) {" : "") + ":root{"; Object.keys(options.dark_preset).forEach(key => { css += "--bc" + key + ":" + options.dark_preset[key] + ";"; }); css += "}" + darkmode_css + (options.device_dark === true ? "}" : ""); return css; } let darkStyleInserted = false; function toggleDarkMode() { const css = generateDarkModeCSS(); if ((options.dark_mode === true || options.device_dark === true) && !darkStyleInserted) { let style = document.createElement('style'); style.textContent = css; document.documentElement.append(style); style.id = 'darkcss'; style.className = "bettercanvas-darkmode-enabled"; darkStyleInserted = true; } else if (darkStyleInserted) { let style = document.querySelector("#darkcss"); style.textContent = options.dark_mode === true || options.device_dark ? css : ""; style.className = options.dark_mode === true || options.device_dark ? "bettercanvas-darkmode-enabled" : ""; } /* if (options.dark_mode === true || options.device_dark) { document.body.classList.add("bettercanvas--darkmode--enabled"); } else { document.body.classList.remove("bettercanvas--darkmode--enabled"); } */ runiframeChecker(); } function runDarkModeFixer(override = false) { if (options.dark_mode !== true) return { "path": "bettercanvas-darkmode_off", "time": "" }; if (override === false && !options["dark_mode_fix"].includes(window.location.pathname)) return { "path": "bettercanvas-none", "time": "" }; let output = inspectDarkMode(); return { "path": window.location.pathname, "time": output.time }; } function autoDarkModeCheck() { let date = new Date(); let currentHour = date.getHours(); let currentMinute = date.getMinutes(); let status = false; if (options.auto_dark === false) return; let startHour = parseInt(options.auto_dark_start["hour"]); let startMinute = parseInt(options.auto_dark_start["minute"]); let endHour = parseInt(options.auto_dark_end["hour"]); let endMinute = parseInt(options.auto_dark_end["minute"]); if (currentHour === startHour) { status = currentMinute >= startMinute; } else if (currentHour === endHour) { status = currentMinute <= endMinute; } else if (startHour > endHour) { status = currentHour > startHour || currentHour < endHour; } else if (startHour < endHour) { status = currentHour > startHour && currentHour < endHour; } if (options.auto_dark === true) { options.dark_mode = status; chrome.storage.sync.set({ "dark_mode": status }, toggleDarkMode); } } async function ScheduledReminderCheck() { let date = new Date(); let currentHour = date.getHours(); let currentMinute = date.getMinutes(); if (options.scheduledReminderTime) { let [hour, minute] = options.scheduledReminderTime.split(":"); if (parseInt(hour) == currentHour && parseInt(minute) == currentMinute) { const container = document.getElementById("bettercanvas-reminders") || makeElement("div", document.body, { "id": "bettercanvas-reminders" }); container.style.display = "flex"; container.textContent = ""; const storage = await chrome.storage.sync.get("reminders"); const now = (new Date()).getTime(); storage["reminders"].forEach(reminder => { if (reminder.d >= now) { createReminder(reminder, container); } }); } } } function toggleAutoDarkMode() { clearInterval(timeCheck); if (options.auto_dark && options.auto_dark === false) return; autoDarkModeCheck(); timeCheck = setInterval(autoDarkModeCheck, 60000); } function toggleScheduledReminders() { clearInterval(reminderCheck); if (options.scheduled_reminders === false) return; //TODO: add it to the options thing ScheduledReminderCheck(); reminderCheck = setInterval(ScheduledReminderCheck, 60000); } let iframeObserver; function runiframeChecker() { if (current_page === "/" || current_page === "") return; if (options.dark_mode !== true) { if (iframeObserver) iframeObserver.disconnect(); document.querySelectorAll('iframe').forEach((frame) => { if (frame.contentDocument && frame.contentDocument.documentElement && frame.contentDocument.documentElement.querySelector('#darkcss')) { frame.contentDocument.documentElement.querySelector('#darkcss').textContent = ''; frame.contentDocument.body.classList.remove("bettercanvas--darkmode--enabled"); } }); return; } const callback = (mutationList) => { for (const mutation of mutationList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0 && mutation.addedNodes[0].nodeName == "IFRAME") { const frame = mutation.addedNodes[0]; const new_style_element = document.createElement("style"); new_style_element.textContent = generateDarkModeCSS(); new_style_element.id = "darkcss"; frame.contentDocument.body.classList.add("bettercanvas--darkmode--enabled"); frame.contentDocument.documentElement.prepend(new_style_element); } } }; iframeObserver = new MutationObserver(callback); iframeObserver.observe(document.querySelector('html'), { childList: true, subtree: true }); } /* Dashboard grades */ function insertGrades() { if (options.dashboard_grades === true) { grades.then(data => { try { let cards = document.querySelectorAll('.ic-DashboardCard'); if (cards.length === 0 || cards[0].querySelectorAll(".ic-DashboardCard__link").length === 0) return; for (let i = 0; i < cards.length; i++) { let course_id = parseInt(cards[i].querySelector(".ic-DashboardCard__link").href.split("courses/")[1]); data.forEach(grade => { if (course_id === grade.id) { let gradepercent = grade.enrollments[0].has_grading_periods === true ? grade.enrollments[0].current_period_computed_current_score : grade.enrollments[0].computed_current_score; //let gradepercent = grade.enrollments[0].computed_current_score; let percent = (gradepercent || "--") + "%"; let gradeContainer = cards[i].querySelector(".bettercanvas-card-grade") || makeElement("a", cards[i].querySelector(".ic-DashboardCard__header"), { "className": "bettercanvas-card-grade", "textContent": percent }); if (options.grade_hover === true) { gradeContainer.classList.add("bettercanvas-hover-only"); } else { gradeContainer.classList.remove("bettercanvas-hover-only"); } gradeContainer.setAttribute("href", `${domain}/courses/${course_id}/grades`); gradeContainer.style.display = "block"; } }); } } catch (e) { logError(e); } }); } else { document.querySelectorAll('.bettercanvas-card-grade').forEach(grade => { grade.style.display = "none"; }); } } /* Card assignments */ /* function setAssignmentStatus(id, status, assignments_done = []) { if (assignments_done.length > 50) assignments_done = []; if (status === true) { assignments_done.push(id); } else { const pos = assignments_done.indexOf(id); if (pos > -1) assignments_done.splice(pos, 1); } chrome.storage.sync.set({ assignments_done: assignments_done }); } */ function createCardAssignment(assignment) { let assignmentContainer = document.createElement("div"); assignmentContainer.className = "bettercanvas-assignment-container"; let assignmentName = makeElement("a", assignmentContainer, { "className": "bettercanvas-assignment-link", "textContent": assignment.plannable.title, "href": assignment.html_url }); let assignmentDueAt = makeElement("span", assignmentContainer, { "className": "bettercanvas-assignment-dueat", "textContent": formatCardDue(new Date(assignment.plannable_date)) }); if (assignment.overdue === true) assignmentDueAt.classList.add("bettercanvas-assignment-overdue"); if (assignment?.submissions?.submitted === true) { assignmentContainer.classList.add("bettercanvas-completed"); } else { if (options.assignment_states[assignment.plannable_id]?.["crs"] === true) { assignmentContainer.classList.add("bettercanvas-completed"); } } assignmentDueAt.addEventListener('mouseup', function () { assignmentContainer.classList.toggle("bettercanvas-completed"); const status = assignmentContainer.classList.contains("bettercanvas-completed"); setAssignmentState(assignment.plannable_id, { "crs": status, "expire": assignment.plannable_date }); }); return assignmentContainer; } let cardAssignments; function preloadAssignmentEls() { return new Promise((resolve, reject) => { let assignmentEls = {}; const now = new Date(); assignments.then((data) => { data = combineAssignments(data); data.forEach(item => { let due = new Date(item.plannable_date); item.overdue = now >= due; let o = { "submitted": item.submissions && item.submissions.submitted === true, "override": item.planner_override && item.planner_override.marked_complete, "type": item.plannable_type, "due": due, "el": createCardAssignment(item) } if (assignmentEls[item.course_id]) { assignmentEls[item.course_id].push(o); } else { assignmentEls[item.course_id] = [o]; } }); resolve(assignmentEls); }); }); } function loadCardAssignments() { if (options.assignments_due !== true) { document.querySelectorAll(".bettercanvas-card-assignment").forEach(card => { card.style.display = "none"; }); return; } cardAssignments.then(els => { try { let cards = document.querySelectorAll('.ic-DashboardCard'); if (cards.length === 0) return; const now = new Date(); cards.forEach(card => { let count = 0; let link = card.querySelector(".ic-DashboardCard__link"); if (!link) return; let course_id = link.href.split("courses/")[1]; let cardContainer = card.querySelector('.bettercanvas-card-container'); cardContainer.textContent = ""; cardContainer.parentElement.style.display = "block"; if (els[course_id]) { els[course_id].forEach(assignment => { if (count >= options.num_assignments) return; if (options.hide_completed_cards === true && assignment.submitted === true) return; if ((options.card_overdues !== true && now >= assignment.due) || (options.card_overdues === true && assignment.submitted === true)) return; if (assignment.type !== "assignment" && assignment.type !== "quiz" && assignment.type !== "discussion_topic") return; if (assignment.override === true) return; //assignment.el.querySelector(".bettercanvas-assignment-dueat").textContent = formatCardDue(assignment.due); cardContainer.appendChild(assignment.el); count++; }); } if (count === 0) { let assignmentContainer = makeElement("div", cardContainer, { "className": "bettercanvas-assignment-container" }); let assignmentDivLink = makeElement("a", assignmentContainer, { "className": "bettercanvas-assignment-link", "textContent": "None" }); } }); } catch (e) { logError(e); } }); } /* function loadCardAssignments2(c = null) { if (options.assignments_due === true) { try { assignments.then(data => { //assignmentData = assignmentData === null ? data : assignmentData; ???? let items = combineAssignments(data); let cards = c ? c : document.querySelectorAll('.ic-DashboardCard'); const now = new Date(); cards.forEach(card => { let count = 0; let course_id = parseInt(card.querySelector(".ic-DashboardCard__link").href.split("courses/")[1]); let cardContainer = card.querySelector('.bettercanvas-card-container'); cardContainer.textContent = ""; cardContainer.parentElement.style.display = "block"; items.forEach(assignment => { let due = new Date(assignment.plannable_date); // lots of checks to make // 1. item belongs to card // 2. haven't exceeded item limit // 3. assignment hasn't been submitted (if hide completed option is on) // 4. disallow overdue and item not past due/allow overdue and item hasn't been submitted // 5. correct item type // 6. no planner override marking item complete if (course_id !== assignment.course_id) return; if (count >= options.num_assignments) return; if (options.hide_completed === true && assignment.submissions.submitted === true) return; if ((options.card_overdues !== true && now >= due) || (options.card_overdues === true && assignment.submissions.submitted === true)) return; if ((assignment.plannable_type !== "assignment" && assignment.plannable_type !== "quiz" && assignment.plannable_type !== "discussion_topic")) return; if (assignment.planner_override && assignment.planner_override.marked_complete === true) return; createCardAssignment(cardContainer, assignment, now >= due); count++; }); if (count === 0) { let assignmentContainer = makeElement("div", "bettercanvas-assignment-container", cardContainer); let assignmentDivLink = makeElement("a", "bettercanvas-assignment-link", assignmentContainer, "None"); } }); }); } catch (e) { logError(e); } } else { document.querySelectorAll(".bettercanvas-card-assignment").forEach(card => { card.style.display = "none"; }); } } */ function setupCardAssignments() { if (options.assignments_due !== true) return; try { if (document.querySelectorAll('.ic-DashboardCard').length > 0 && document.querySelectorAll('.bettercanvas-card-container').length > 0) return; let cards = document.querySelectorAll('.ic-DashboardCard'); cards.forEach(card => { let assignmentContainer = card.querySelector(".bettercanvas-card-assignment") || makeElement("div", card, { "className": "bettercanvas-card-assignment" }); let assignmentsDueHeader = card.querySelector(".bettercanvas-card-header-container") || makeElement("div", assignmentContainer, { "className": "bettercanvas-card-header-container" }); let assignmentsDueLabel = card.querySelector(".bettercanvas-card-header") || makeElement("h3", assignmentsDueHeader, { "className": "bettercanvas-card-header", "textContent": chrome.i18n.getMessage("due") }); let cardContainer = card.querySelector(".bettercanvas-card-container") || makeElement("div", assignmentContainer, { "className": "bettercanvas-card-container" }); let skeletonText = card.querySelector(".bettercanvas-skeleton-text") || makeElement("div", cardContainer, { "className": "bettercanvas-skeleton-text" }); }); } catch (e) { logError(e); } } /* Card customization */ function getCardId(card) { let id = card.querySelector(".ic-DashboardCard__link").href.split("courses/")[1]; // no ~ if (!id.includes("~")) return id; // has ~ but dashboard card method is used if (options["custom_cards"][id]) return id; // weird case, some canvases replace consecutive 0s with a ~ in the id // but the number of 0s isn't consistent between schools id = id.split("~"); let re = new RegExp(`${id[0]}0+${id[1]}`); for (const c of Object.keys(options["custom_cards"])) { if (c.match(re)) return c; } return -1; } function customizeCards(c = null) { if (!options.custom_cards) return; try { let cards = c ? c : document.querySelectorAll('.ic-DashboardCard'); if (cards.length && cards.length > 0 && cards[0].querySelectorAll(".ic-DashboardCard__link").length === 0) return; cards.forEach(card => { const id = getCardId(card); let cardOptions = options["custom_cards"][id] || null; let cardOptions_2 = options["custom_cards_2"][id] || null; if (!cardOptions) return; // hide card card.style.display = cardOptions.hidden === true ? "none" : "inline-block"; // card image if (cardOptions.img === "none") { let currentImg = card.querySelector(".ic-DashboardCard__header_image"); if (currentImg) { card.querySelector(".ic-DashboardCard__header_hero").style.opacity = 1; } } else if (cardOptions.img !== "") { let topColor = card.querySelector(".ic-DashboardCard__header_hero"); let container = card.querySelector(".ic-DashboardCard__header_image") || makeElement("div", card, { "className": "ic-DashboardCard__header_image" }); card.querySelector(".ic-DashboardCard__header").prepend(container); container.appendChild(topColor); container.style.backgroundImage = "url(\"" + cardOptions.img + "\")"; topColor.style.opacity = .5; } // card name if (cardOptions.name !== "") { card.querySelector(".ic-DashboardCard__header-title > span").textContent = cardOptions.name; } // card code if (cardOptions.code !== "") { card.querySelector(".ic-DashboardCard__header-subtitle").textContent = cardOptions.code; } // card links let links = card.querySelectorAll(".ic-DashboardCard__action"); for (let i = links.length; i < 4; i++) { makeElement("a", card.querySelector(".ic-DashboardCard__action-container"), { "className": "ic-DashboardCard__action" }); } links = card.querySelectorAll(".ic-DashboardCard__action"); for (let i = 0; i < 4; i++) { let img = links[i].querySelector(".bettercanvas-link-image") || makeElement("img", links[i], { "className": "bettercanvas-link-image" }); links[i].style.display = "inherit"; if (cardOptions_2.links[i].path === "none") { links[i].style.display = "none"; } else if (cardOptions_2.links[i].is_default === false) { links[i].href = cardOptions_2.links[i].path; img.src = getCustomLinkImage(cardOptions_2.links[i].path); if (links[i].querySelector(".ic-DashboardCard__action-layout")) links[i].querySelector(".ic-DashboardCard__action-layout").style.display = "none"; img.style.display = "block"; } else { if (links[i].querySelector(".ic-DashboardCard__action-layout")) links[i].querySelector(".ic-DashboardCard__action-layout").style.display = "inherit"; img.style.display = "none"; } img.addEventListener("error", () => { img.src = "https://www.instructure.com/favicon.ico"; }) } }); } catch (e) { logError(e); } } function getCustomLinkImage(path) { if (path.includes("webassign.net")) { return "https://www.cengage.com/favicon.ico"; } else if (path.includes("docs.google")) { return "https://ssl.gstatic.com/docs/documents/images/kix-favicon7.ico"; } else { let url = { "hostname": "instructure.com/" }; try { url = new URL(path); } catch (e) { logError(e); } return "https://" + url.hostname + "/favicon.ico";; } } /* GPA calculator */ function calculateGPA2() { let qualityPoints = 0, numCredits = 0, weightedQualityPoints = 0, cumulativePoints = 0, cumulativeCredits = 0; document.querySelectorAll('.bettercanvas-gpa-course').forEach(course => { const weight = course.querySelector('.bettercanvas-course-weight').value; const credits = parseFloat(course.querySelector('.bettercanvas-course-credit').value); const grade = parseFloat(course.querySelector('.bettercanvas-course-percent').value); if (weight === "dnc" || !credits || !grade) return; let letter = "--"; let gpa; if (grade >= options.gpa_calc_bounds["A+"].cutoff) { gpa = options.gpa_calc_bounds["A+"].gpa; letter = "A+"; } else if (grade >= options.gpa_calc_bounds["A"].cutoff) { gpa = options.gpa_calc_bounds["A"].gpa; letter = "A"; } else if (grade >= options.gpa_calc_bounds["A-"].cutoff) { gpa = options.gpa_calc_bounds["A-"].gpa; letter = "A-"; } else if (grade >= options.gpa_calc_bounds["B+"].cutoff) { gpa = options.gpa_calc_bounds["B+"].gpa; letter = "B+"; } else if (grade >= options.gpa_calc_bounds["B"].cutoff) { gpa = options.gpa_calc_bounds["B"].gpa; letter = "B"; } else if (grade >= options.gpa_calc_bounds["B-"].cutoff) { gpa = options.gpa_calc_bounds["B-"].gpa; letter = "B-" } else if (grade >= options.gpa_calc_bounds["C+"].cutoff) { gpa = options.gpa_calc_bounds["C+"].gpa; letter = "C+"; } else if (grade >= options.gpa_calc_bounds["C"].cutoff) { gpa = options.gpa_calc_bounds["C"].gpa; letter = "C"; } else if (grade >= options.gpa_calc_bounds["C-"].cutoff) { gpa = options.gpa_calc_bounds["C-"].gpa; letter = "C-"; } else if (grade >= options.gpa_calc_bounds["D+"].cutoff) { gpa = options.gpa_calc_bounds["D+"].gpa; letter = "D+"; } else if (grade >= options.gpa_calc_bounds["D"].cutoff) { gpa = options.gpa_calc_bounds["D"].gpa; letter = "D"; } else if (grade >= options.gpa_calc_bounds["D-"].cutoff) { gpa = options.gpa_calc_bounds["D-"].gpa; letter = "D-"; } else { letter = "F"; gpa = options.gpa_calc_bounds["F"].gpa; } /* if (course.id === "cumulative-gpa") { //gpa = parseFloat(options["cumulative_gpa"]["gr"]); gpa = 0; cumulativePoints += parseFloat(options["cumulative_gpa"]["gr"]) * credits; cumulativeCredits = credits; } else { */ course.querySelector(".bettercanvas-gpa-letter-grade").textContent = letter; let weightMultiplier = 0; if (weight === "ap") { weightMultiplier = 1; } else if (weight === "honors") { weightMultiplier = .5; } qualityPoints += gpa * credits; weightedQualityPoints += (gpa + weightMultiplier) * credits; numCredits += credits; //} }); document.querySelector("#bettercanvas-gpa-unweighted").textContent = (qualityPoints / numCredits).toFixed(2); document.querySelector("#bettercanvas-gpa-weighted").textContent = (weightedQualityPoints / numCredits).toFixed(2); const cGPA = document.querySelector("#bettercanvas-cumulative-gpa"); const g = parseFloat(cGPA.querySelector(".bettercanvas-course-percent").value); const c = parseInt(cGPA.querySelector(".bettercanvas-course-credit").value); document.querySelector("#bettercanvas-gpa-cumulative").textContent = (((options.gpa_calc_weighted === true ? weightedQualityPoints : qualityPoints) + (g * c)) / (numCredits + c)).toFixed(2); } function changeGPASettings(course_id, update) { calculateGPA2(); chrome.storage.sync.get(["custom_cards", "cumulative_gpa"], storage => { if (course_id === "cumulative") { chrome.storage.sync.set({ "cumulative_gpa": { ...storage["cumulative_gpa"], ...update } }); } else { chrome.storage.sync.set({ "custom_cards": { ...storage["custom_cards"], [course_id]: { ...storage["custom_cards"][course_id], ...update } } }); } }); } function createGPACalcCourse(location, course) { let customs; if (course.access_restricted_by_date === true) { return null; } if (course.id === "cumulative") { customs = options["cumulative_gpa"]; } else if (options.custom_cards && options.custom_cards[course.id]) { customs = options.custom_cards[course.id]; } else { return; customs = { "name": course.name, "hidden": false, "weight": "regular", "credits": 1, "gr": null }; } if (customs.hidden === true) return; let courseContainer = makeElement("div", location, { "className": course.id === "cumulative" ? "bettercanvas-gpa-cumulative" : "bettercanvas-gpa-course", "innerHTML": '
' }); let courseName = makeElement("p", courseContainer, { "className": "bettercanvas-gpa-name", "textContent": customs.name === "" ? course.course_code : customs.name }); let changerContainer = makeElement("div", courseContainer, { "className": "bettercanvas-gpa-percent-container" }); let credits = makeElement("div", courseContainer, { "className": "bettercanvas-course-credits", "innerHTML": 'cr' }); let creditsChanger = credits.querySelector(".bettercanvas-course-credit"); creditsChanger.value = customs.credits; let changer = makeElement("input", changerContainer, { "className": "bettercanvas-course-percent" }); let percent = makeElement("span", changerContainer, { "className": "bettercanvas-course-percent-sign", "textContent": course.id === "cumulative" ? "/4" : "%" }); let courseGrade = course?.enrollments[0].has_grading_periods === true ? course.enrollments[0].current_period_computed_current_score : course.enrollments[0].computed_current_score; if (customs["gr"] !== null) { changer.value = customs["gr"]; } else if (courseGrade) { changer.value = courseGrade; } else { changer.value = "--"; } if (course.id !== "cumulative") { let weightSelections = makeElement("form", courseContainer, { "className": "bettercanvas-course-weights" }); weightSelections.innerHTML = ''; let weightChanger = weightSelections.querySelector(".bettercanvas-course-weight"); weightChanger.value = changer.value === "--" ? "dnc" : customs.weight; weightChanger.addEventListener('change', () => changeGPASettings(course.id, { "weight": weightSelections.querySelector(".bettercanvas-course-weight").value })); let useCustomGr = makeElement("input", courseContainer, { "className": "bettercanvas-course-customgr", "type": "checkbox", "checked": customs.gr !== null ? true : false }); let useCustomGrLabel = makeElement("span", courseContainer, { "className": "bettercanvas-course-customgr-label", "textContent": "Save custom grade" }); useCustomGr.addEventListener("input", () => { if (options["custom_cards"][course.id]) { if (options["custom_cards"][course.id]["gr"] !== undefined && options["custom_cards"][course.id]["gr"] !== null) { changer.value = courseGrade; changeGPASettings(course.id, { "gr": null }); } else { changeGPASettings(course.id, { "gr": changer.value }); } } }); } changer.addEventListener('input', (e) => { if (course.id === "cumulative" || (options["custom_cards"][course.id]["gr"] !== undefined && options["custom_cards"][course.id]["gr"] !== null)) { changeGPASettings(course.id, { "gr": e.target.value }); } else { calculateGPA2(); } }); credits.querySelector(".bettercanvas-course-credit").addEventListener('input', () => changeGPASettings(course.id, { "credits": credits.querySelector(".bettercanvas-course-credit").value })); return courseContainer; } function setupGPACalc() { if (current_page !== "/" && current_page !== "") return; try { grades?.then(result => { if (!document.querySelector(".ic-DashboardCard__box__container")) return; let container2 = document.querySelector(".bettercanvas-gpa-card") || document.createElement("div"); container2.className = "bettercanvas-gpa-card"; container2.style.display = options.gpa_calc === true ? "inline-block" : "none"; container2.innerHTML = `

GPA

Current

Weighted

Cumulative

`; let editBtn = makeElement("button", container2, { "className": "bettercanvas-gpa-edit-btn", "textContent": "Edit Calculator" }); let container = document.querySelector(".bettercanvas-gpa") || document.createElement("div"); container.className = "bettercanvas-gpa"; container.innerHTML = '

GPA Calculator

'; if (options.gpa_calc_prepend === true) { document.querySelector(".ic-DashboardCard__box__container").prepend(container2); document.querySelector(".ic-DashboardCard__box__container").prepend(container); } else { document.querySelector(".ic-DashboardCard__box__container").appendChild(container2); document.querySelector(".ic-DashboardCard__box__container").appendChild(container); } let location = document.querySelector(".bettercanvas-gpa-courses"); let cumulative = createGPACalcCourse(location, { "id": "cumulative", "enrollments": [{ "has_grading_periods": true, "current_period_computed_current_score": 0 }] }); cumulative.id = "bettercanvas-cumulative-gpa"; result.forEach(course => createGPACalcCourse(location, course)); container.style.display = "none"; editBtn.addEventListener("click", () => { if (container.style.display === "none") { container.style.display = "inline-block"; editBtn.textContent = "Close Calculator"; } else { container.style.display = "none"; editBtn.textContent = "Edit Calculator"; } }); calculateGPA2(); }); } catch (e) { logError(e); } } /* Dashboard notes */ let dashboardNotesTimer; function delayDashboardNotesStorage(text) { clearTimeout(dashboardNotesTimer); dashboardNotesTimer = setTimeout(() => { chrome.storage.sync.set({ dashboard_notes_text: text }); }, 1000); } function loadDashboardNotes() { if (options.dashboard_notes === true) { let notes = document.querySelector('.bettercanvas-dashboard-notes') || document.createElement("textarea"); notes.classList.add("bettercanvas-dashboard-notes"); notes.value = options.dashboard_notes_text; notes.placeholder = "Enter notes here"; notes.style.display = "block"; if (notes.parentElement === null) document.querySelector("#DashboardCard_Container").prepend(notes); notes.style.height = notes.scrollHeight + 5 + "px"; notes.addEventListener('input', function () { delayDashboardNotesStorage(this.value); this.style.height = "1px"; this.style.height = this.scrollHeight + 5 + "px"; }); } else { let notes = document.querySelector('.bettercanvas-dashboard-notes'); if (notes) notes.style.display = "none"; } } /* Custom font */ function loadCustomFont() { let link = document.querySelector("#custom_font_link"); let style = document.querySelector("#custom_font"); let load = () => { if (options.custom_font.link !== "") { document.head.appendChild(style); link.href = `https://fonts.googleapis.com/css2?family=${options.custom_font.link}&display=swap`; link.rel = "stylesheet"; document.head.appendChild(link); } style.textContent = options.custom_font.link === "" ? "" : `*, input, a, button, h1, h2, h3, h4, h5, h6, p, span {font-family: ${options.custom_font.family}!important}`; } let createEls = () => { link = document.createElement("link"); link.id = "custom_font_link"; style = document.createElement("style"); style.id = "custom_font"; load(); } if (link && style) { load(); } else if (options.custom_font.link !== "") { if (document.readyState !== 'loading') { createEls(); } else { document.addEventListener("DOMContentLoaded", () => { createEls(); }); } } } /* Smaller features */ function applyAestheticChanges() { let style = document.querySelector("#bettercanvas-aesthetics") || document.createElement('style'); style.id = "bettercanvas-aesthetics"; style.textContent = ""; if (options.condensed_cards === true) style.textContent += ".ic-DashboardCard__header_hero {height:60px!important}.ic-DashboardCard__header-subtitle, .ic-DashboardCard__header-term{display:none}"; if (options.remlogo === true) style.textContent += ".ic-app-header__logomark-container{display:none}"; if (options.disable_color_overlay === true) style.textContent += ".ic-DashboardCard__header_hero{opacity: 0!important} .ic-DashboardCard__header-button-bg{opacity: 1!important}"; if (options.hide_feedback === true) style.textContent += ".recent_feedback {display: none}"; if (options.full_width === true) style.textContent += ".ic-Layout-wrapper{max-width:100%!important}"; if (options.customCardStyles === true) { if (options.imageSize !== undefined && options.imageSize !== 100) style.textContent += `.ic-DashboardCard__header_image {transform: scale(${options.imageSize / 100})!important; }`; if (options.cardRoundness !== undefined && options.cardRoundness !== 5) style.textContent += `.ic-DashboardCard {border-radius: ${options.cardRoundness}px!important;}`; if (options.cardSpacing !== undefined && options.cardSpacing !== 0) style.textContent += `.ic-DashboardCard {margin-right: ${options.cardSpacing / 2}px!important; margin-bottom: ${options.cardSpacing / 2}px!important;}`; if (options.cardWidth !== undefined && options.cardWidth !== 262) style.textContent += `.ic-DashboardCard {width: ${options.cardWidth}px!important;}`; if (options.cardHeight !== undefined && options.cardHeight !== 250) style.textContent += `.ic-DashboardCard {height: ${options.cardHeight}px!important;}`; } if (options.custom_styles !== "") style.textContent += options.custom_styles; document.documentElement.appendChild(style); } /* function changeFullWidth() { if (options.full_width == null) return; if (options.full_width === true) { document.body.classList.add("full-width"); } else { document.body.classList.remove("full-width"); } } */ function changeGradientCards() { if (options.gradient_cards === true) { let cardheads = document.querySelectorAll('.ic-DashboardCard__header_hero'); let cardcss = document.querySelector("#gradientcss") || document.createElement('style'); cardcss.id = "gradientcss"; cardcss.textContent = ""; document.documentElement.appendChild(cardcss); for (let i = 0; i < cardheads.length; i++) { let colorone = cardheads[i].style.backgroundColor.split(','); let [r, g, b] = [parseInt(colorone[0].split('(')[1]), parseInt(colorone[1]), parseInt(colorone[2])]; let [h, s, l] = [rgbToHsl(r, g, b)[0], rgbToHsl(r, g, b)[1], rgbToHsl(r, g, b)[2]]; let degree = ((h % 60) / 60) >= .66 ? 30 : ((h % 60) / 60) <= .33 ? -30 : 15; let newh = h > 300 ? (360 - (h + 65)) + (65 + degree) : h + 65 + degree; cardcss.textContent += ".ic-DashboardCard:nth-of-type(" + (i + 1) + ") .ic-DashboardCard__header_hero{background: linear-gradient(115deg, hsl(" + h + "deg," + s + "%," + l + "%) 5%, hsl(" + newh + "deg," + s + "%," + l + "%) 100%)!important}"; } } else { let cardcss = document.querySelector("#gradientcss"); if (cardcss) cardcss.textContent = ""; } } function showUpdateMsg() { // dont run if not on dashboard const el = document.getElementById("announcementWrapper"); if (!el) return; // option off or div already created let div = document.getElementById("bettercanvas-update-msg"); if (options.show_updates !== true || options.update_msg === "") { if (div) div.style.display = "none"; return; } else if (div) { div.style.display = "flex"; return; } // first creation div = makeElement("div", el, { "id": "bettercanvas-update-msg" }); makeElement("p", div, { "textContent": options.update_msg }); const close = makeElement("button", div, { "id": "bettercanvas-update-close", "textContent": "Close" }); close.addEventListener("click", () => { readUpdate(); div.remove(); }); } function readUpdate() { chrome.storage.sync.set({ "update_msg": "" }); } /* Other functions */ function combineAssignments(data) { let combined = data; try { options.custom_assignments_overflow.forEach(overflow => { combined = combined.concat(options[overflow]); }); } catch (e) { logError(e); } return combined.sort((a, b) => new Date(a.plannable_date).getTime() - new Date(b.plannable_date).getTime()); } function cleanCustomAssignments() { chrome.storage.sync.get("custom_assignments_overflow", overflows => { chrome.storage.sync.get(overflows["custom_assignments_overflow"], storage => { const now = new Date(); overflows["custom_assignments_overflow"].forEach(overflow => { let changed = false; for (let i = 0; i < storage[overflow].length; i++) { let assignmentDate = new Date(storage[overflow][i].plannable_date); if (!assignmentDate.getTime() || assignmentDate < now) { storage[overflow].splice(i, 1); changed = true; } } if (changed) chrome.storage.sync.set({ [overflow]: storage[overflow] }); }); }); }); } function setupCustomURL() { //let test = getData(`${domain}/api/v1/dashboard/dashboard_cards?include[]=concluded&include[]=term`); let test = getData(`${domain}/api/v1/courses?${/*enrollment_state=active&*/""}per_page=100`); test.then(res => { if (res.length) { getCards(res).then(() => { setTimeout(() => { console.log("Better Canvas - setting custom domain to " + domain); chrome.storage.sync.set({ custom_domain: [domain] }).then(location.reload()); }, 100); }); } else { console.log("Better Canvas - this url doesn't seem to be a canvas url (1)"); } }).catch(err => { console.log("Better Canvas - this url doesn't seem to be a canvas url (2)"); }); } function getGrades() { if (options.gpa_calc === true || options.dashboard_grades === true) { grades = getData(`${domain}/api/v1/courses?${/*enrollment_state=active&*/""}include[]=concluded&include[]=total_scores&include[]=computed_current_score&include[]=current_grading_period_scores&per_page=100`); } } function getColors() { if (options.tab_icons || options.todo_colors) { let colors = getData(`${domain}/api/v1/users/self/colors`); colors.then(data => { let cards = options.custom_cards_3; Object.keys(cards).forEach(key => { cards[key] = { ...cards[key], "color": data["custom_colors"]["course_" + key] ? data["custom_colors"]["course_" + key] : null }; }); chrome.storage.sync.set({ "custom_cards_3": cards }); }); } } function changeFavicon() { if (options.tab_icons !== true) return; let match = current_page.match(/courses\/(?\d*)/); if (match && match.groups.id && options.custom_cards_3[match.groups.id]?.color) { document.querySelector('link[rel="icon"').href = `data:image/svg+xml;utf8, `; } } function getAssignments() { if (options.assignments_due === true || options.better_todo === true) { let weekAgo = new Date(new Date() - 604800000); //let weekAgo = new Date(new Date() - (604800000 * 10)); assignments = getData(`${domain}/api/v1/planner/items?start_date=${weekAgo.toISOString()}&per_page=75`); cardAssignments = preloadAssignmentEls(); } } function getApiData() { if (current_page === "/" || current_page === "") { getAssignments(); getGrades(); getColors(); } } function makeElement(element, location, options) { let creation = document.createElement(element); Object.keys(options).forEach(key => { creation[key] = options[key]; }); location.appendChild(creation); return creation } function makeElement2(element, elclass, location, text) { let creation = document.createElement(element); creation.classList.add(elclass); creation.textContent = text; location.appendChild(creation); return creation } async function getData(url) { let response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }); let data = await response.json(); return data } function hexToHsl(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return rgbToHsl(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)); } function rgbToHex(rgb) { try { let pat = /^rgb\(\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*\)$/; let exec = pat.exec(rgb); return "#" + parseInt(exec[1]).toString(16).padStart(2, "0") + parseInt(exec[2]).toString(16).padStart(2, "0") + parseInt(exec[3]).toString(16).padStart(2, "0"); } catch (e) { console.warn(e); } } function rgbToHsl(r, g, b) { r /= 255, g /= 255, b /= 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if (max == min) { h = s = 0; } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h * 360, s * 100, l * 100]; } function getRelativeDate(date, short = false) { let now = new Date(); let timeSince = (now.getTime() - date.getTime()) / 60000; let time = "min"; timeSince = Math.abs(timeSince); if (timeSince >= 60) { timeSince /= 60; time = short ? "h" : "hour"; if (timeSince >= 24) { timeSince /= 24; time = short ? "d" : "day"; if (timeSince >= 7) { timeSince /= 7; time = short ? "w" : "week"; } } } timeSince = Math.round(timeSince); let relative = timeSince + (short ? "" : " ") + time + (timeSince > 1 && !short ? "s" : ""); return { time: relative, ms: now.getTime() - date.getTime() }; } const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; function formatTodoDate(date, submissions, hr24) { let { time, ms } = getRelativeDate(date); let fromNow = ms < 0 ? "in " + time : time + " ago"; let dueSoon = false; if (submissions && submissions.submitted === false && ms >= -21600000) { dueSoon = true; } return { "dueSoon": dueSoon, "date": months[date.getMonth()] + " " + date.getDate() + " at " + (date.getHours() - (hr24 ? "" : date.getHours() > 12 ? 12 : 0)) + ":" + (date.getMinutes() < 10 ? "0" : "") + date.getMinutes() + (hr24 ? "" : date.getHours() >= 12 ? "pm" : "am") + " (" + fromNow + ")" }; } function formatCardDue(date) { let due = new Date(date); if (options.relative_dues === true) { let relative = getRelativeDate(due, true); return relative.ms > 0 ? relative.time + " ago" : "in " + relative.time; } return options.assignment_date_format ? (due.getDate()) + "/" + (due.getMonth() + 1) : (due.getMonth() + 1) + "/" + (due.getDate()); } function logError(e) { chrome.storage.local.get("errors", storage => { if (storage.errors.length > 20) { storage["errors"] = []; } chrome.storage.local.set({ "errors": storage["errors"].concat(e.stack) }); console.log(e.stack); console.log(storage["errors"].concat(e.stack)); }) } const CSRFtoken = function () { return decodeURIComponent((document.cookie.match('(^|;) *_csrf_token=([^;]*)') || '')[2]) }