diff --git a/README.md b/README.md index eeea504..b5ef572 100644 --- a/README.md +++ b/README.md @@ -79,13 +79,11 @@ Canvas Refined adds more with more to come! - global canvas search - fix darkmode fixer (and automatic) - grade history with graph -- more shapes for the better todo ring thing ## Extra features that might be added: - card grade position, card outline - streaks - maybe find a way for faster loading -- liquid glass theme? - rotating background, time/weather background overlay - custom side logo - goals @@ -96,10 +94,8 @@ Canvas Refined adds more with more to come! ## Community suggestions (maybe will be done at some point) - when opening assignments it will show you "if you get a 0 on this your grade will be _" -- quick modules button on cards - module sorting (newest, oldest) (maybe grid view) - grade leaderboard per class (opt in) -- GPA preset by school name maybe - sticky notes ## Dev Installation diff --git a/html/popup.html b/html/popup.html index 8285159..50a9d62 100644 --- a/html/popup.html +++ b/html/popup.html @@ -277,6 +277,14 @@ +
+ + +
+
+ + +
Max items to show: @@ -362,14 +370,6 @@
Full Width Fix -
- - -
-
-
-
Hide recent feedback -
diff --git a/js/background.js b/js/background.js index 1ce223e..d1bfa25 100644 --- a/js/background.js +++ b/js/background.js @@ -74,7 +74,6 @@ chrome.runtime.onInstalled.addListener(function () { "card_overdues": false, "relative_dues": false, "equal_height_cards": false, - "hide_feedback": false, "hide_new_canvas": true, "quiz_safe_mode": false, "dark_mode_fix": [], diff --git a/js/content.js b/js/content.js index f2d5dd7..126e684 100644 --- a/js/content.js +++ b/js/content.js @@ -1,5 +1,31 @@ const domain = window.location.origin; -const current_page = window.location.pathname; +let current_page = window.location.pathname; + +// Canvas' "New Canvas" UI navigates client-side via history.pushState/ +// replaceState without a full page reload. current_page is captured once at +// document_start, so without this hook it goes stale and page-specific features +// (Back to Assignment button, sequence-footer removal, profile logout button) +// never activate when the user clicks into a page instead of loading it directly. +function setupNavigationListener() { + const update = () => { + const next = window.location.pathname; + if (next === current_page) return; + current_page = next; + // Re-run the page-scoped watchers so they evaluate against the new URL. + watchSequenceFooter(); + watchSubmissionPageButton(); + watchProfileLogoutPageButton(); + }; + for (const method of ["pushState", "replaceState"]) { + const orig = history[method]; + history[method] = function (...args) { + const ret = orig.apply(this, args); + update(); + return ret; + }; + } + window.addEventListener("popstate", update); +} function getCurrentCourseId() { const match = current_page.match(/^\/courses\/(\d+)(?:\/|$)/); @@ -104,7 +130,10 @@ function ensureProfileLogoutPageButton() { } function watchProfileLogoutPageButton() { - if (!isProfilePage()) return; + if (!isProfilePage()) { + document.getElementById("canvasrefined-profile-logout")?.remove(); + return; + } if (ensureProfileLogoutPageButton()) return; if (profileLogoutButtonObserver) return; @@ -129,7 +158,12 @@ function ensureSubmissionPageButton() { if (!assignmentLink) return false; const content = document.getElementById("content"); if (!content) return false; - if (content.querySelector("#canvasrefined-assignment-return")) return true; + const existing = content.querySelector("#canvasrefined-assignment-return"); + if (existing) { + // Keep the href in sync when navigating between submission pages via SPA routing. + existing.href = assignmentLink; + return true; + } addSubmissionPageButton(); return Boolean(content.querySelector("#canvasrefined-assignment-return")); } @@ -168,7 +202,11 @@ function watchSequenceFooter() { } function watchSubmissionPageButton() { - if (!getSubmissionAssignmentLink()) return; + if (!getSubmissionAssignmentLink()) { + // Navigated away from a submission page; remove any stale button left in #content. + document.getElementById("canvasrefined-assignment-return")?.remove(); + return; + } if (ensureSubmissionPageButton()) return; if (submissionPageButtonObserver) return; @@ -608,6 +646,7 @@ function startExtension() { watchSequenceFooter(); watchSubmissionPageButton(); watchProfileLogoutPageButton(); + setupNavigationListener(); setupQuizSafeModeBanner(); @@ -640,6 +679,12 @@ function applyOptionsChanges(changes) { case "device_dark": toggleDarkMode(); applyTodoAlternateColors(); + // "Ignore card colors" picks black vs. theme text color based on dark + // mode, so re-render the Better Todo list to keep it in sync. + if (options.todo_ignore_card_colors && options.better_todo && document.getElementById("better-todo-main")) { + clearTodoList(); + createTodoSections(document.querySelector("#canvasrefined-todo-list")); + } break; case "todo_alternate_colors": applyTodoAlternateColors(); @@ -688,6 +733,16 @@ function applyOptionsChanges(changes) { equalizeCardHeights(); break; case "custom_cards": + customizeCards(); + // Hiding/unhiding a card changes which courses appear in the todo + // list and the progress display, so re-render them immediately. + if (options.better_todo && document.getElementById("better-todo-main")) { + moreAnnouncementCount = 0; + moreAssignmentCount = 0; + clearTodoList(); + createTodoSections(document.querySelector("#canvasrefined-todo-list")); + } + break; case "custom_cards_2": case "custom_cards_3": customizeCards(); @@ -700,6 +755,8 @@ function applyOptionsChanges(changes) { // case "todo_overdues": case "todo_hide_feedback": case "todo_full_height": + case "todo_ignore_card_colors": + case "todo_remove_icons": case "custom_cards_3": moreAnnouncementCount = 0; moreAssignmentCount = 0; @@ -723,7 +780,6 @@ function applyOptionsChanges(changes) { case "remlogo": case "disable_color_overlay": case "condensed_cards": - case "hide_feedback": case "full_width": case "center_cards": case "custom_styles": @@ -802,12 +858,7 @@ function applyOptionsChanges(changes) { if (typeof assignments?.then === 'function') { assignments.then(data => { const courseId = getCurrentCourseId(); - const scopedData = courseId - ? data.filter(item => { - const itemCourseId = parseInt(item.course_id || item.context_id || item?.plannable?.course_id); - return itemCourseId === courseId; - }) - : data; + const scopedData = getTodoScopedData(data, courseId); renderProgressRings(placeholder, scopedData); }); } @@ -1302,12 +1353,11 @@ function recieveMessage(request, sender, sendResponse) { switch (request.message) { case ("getCards"): if (options["card_method_dashboard"] === true) { - getCardsFromDashboard(); + getCardsFromDashboard().then(() => sendResponse(true)); } else { - getCards(); + getCards().then(() => sendResponse(true)); } - sendResponse(true); - break; + return true; // keep the message channel open for async sendResponse case ("setcolors"): changeColorPreset(request.options); sendResponse(true); break; case ("getcolors"): sendResponse(getCardColors()); break; case ("inspect"): sendResponse(inspectDarkMode(true)); break; @@ -1396,6 +1446,7 @@ function getCardColors() { function getCardsFromDashboard() { console.log("getting cards from dashboard") const dashboard_cards = document.querySelectorAll(".ic-DashboardCard"); + return new Promise(resolve => { 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"] || {}; @@ -1409,7 +1460,7 @@ function getCardsFromDashboard() { 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 }; + cards[id] = { "default": card.querySelector(".ic-DashboardCard__header-subtitle").textContent.substring(0, 20), "fullName": card.querySelector(".ic-DashboardCard__header-title")?.textContent?.trim() || "", "name": "", "code": "", "img": "", "hidden": false, "weight": "regular", "credits": 1, "eid": 100000 - count, "gr": null }; let links = []; for (let i = 0; i < 4; i++) { @@ -1418,6 +1469,13 @@ function getCardsFromDashboard() { cards_2[id] = { "links": links }; cards_3[id] = { "url": domain }; + } else { + // backfill full name for cards created before this field existed + const full = card.querySelector(".ic-DashboardCard__header-title")?.textContent?.trim() || ""; + if (full && cards[id].fullName !== full) { + cards[id].fullName = full; + newCards = true; + } } count++; }); @@ -1452,15 +1510,17 @@ function getCardsFromDashboard() { console.log("Error getting dashboard cards\n", e); logError(e); } finally { - if(newCards !== true) return; + if(newCards !== true) { resolve(); return; } console.log(newCards ? "new cards found" : ""); - chrome.storage.sync.set({ "custom_cards": cards, "custom_cards_2": cards_2, "custom_cards_3": cards_3 }); + chrome.storage.sync.set({ "custom_cards": cards, "custom_cards_2": cards_2, "custom_cards_3": cards_3 }, () => resolve()); } }); + }); } async function getCards(api = null) { let dashboard_cards = api ? api : await getData(`${domain}/api/v1/courses?${/*enrollment_state=active&*/""}per_page=100`); + await new Promise(resolve => { 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"] || {}; @@ -1479,10 +1539,11 @@ async function getCards(api = null) { 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 }; + cards[id] = { "default": card.course_code.substring(0, 20), "fullName": card.name || card.course_code || "", "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].fullName = card.name || card.course_code || cards[id].fullName || ""; cards[id].eid = card.enrollment_term_id || 0; if (!cards[id].code) cards[id].code = ""; } @@ -1530,9 +1591,10 @@ async function getCards(api = null) { } 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 } : {}); + chrome.storage.sync.set(newCards ? { "custom_cards": cards, "custom_cards_2": cards_2, "custom_cards_3": cards_3 } : {}, () => resolve()); } }); + }); } /* @@ -1643,6 +1705,46 @@ function courseRingLabel(courseId) { return card?.default || `Course ${courseId}`; } +// Planner items for courses the user has hidden from their dashboard should +// not appear in the Better Todo list or its progress display. Personal +// tasks (planner notes with no course) are always kept. +function isCourseHidden(courseId) { + if (courseId === undefined || courseId === null) return false; + const cards = options.custom_cards || {}; + const card = cards[String(courseId)] || cards[courseId]; + return !!card && card.hidden === true; +} + +function filterHiddenCourses(data) { + return data.filter(item => { + const cid = item.course_id || item.context_id || item?.plannable?.course_id; + return !isCourseHidden(cid); + }); +} + +// Build scoped data for the Better Todo list: drop hidden courses, then (on +// a course page) restrict to the current course. +function getTodoScopedData(data, courseId) { + const visible = filterHiddenCourses(data); + if (!courseId) return visible; + return visible.filter(item => { + const itemCourseId = parseInt(item.course_id || item.context_id || item?.plannable?.course_id); + return itemCourseId === courseId; + }); +} + +// Returns a Map of courseId (string) -> dashboard position index, read from +// the live dashboard card DOM order. Empty when not on the dashboard. Used +// to order the progress display the same way the user ordered their cards. +function getDashboardCourseOrder() { + const order = new Map(); + document.querySelectorAll('.ic-DashboardCard').forEach((card, idx) => { + const id = getCardId(card); + if (id && id !== -1 && !order.has(String(id))) order.set(String(id), idx); + }); + return order; +} + // Mode "rings": concentric rings, one per course, each filled by completion. function renderProgressRingsMode(wrapper, shown, totalAll, completedAll, percent) { const containerWidth = wrapper.clientWidth || 240; @@ -2066,8 +2168,17 @@ function renderProgressRings(container, scopedData) { if (!entries.length) { container.innerHTML = ""; return; } - // sort by total desc and limit to 6 courses - entries.sort((a, b) => b.total - a.total); + // Order courses to match the user's dashboard card order. Courses that + // aren't on the dashboard (personal tasks, dropped courses) sort after + // dashboard courses, keeping their relative order; ties fall back to + // most assignments first so the display stays stable. + const dashboardOrder = getDashboardCourseOrder(); + entries.sort((a, b) => { + const ai = dashboardOrder.has(a.courseId) ? dashboardOrder.get(a.courseId) : Infinity; + const bi = dashboardOrder.has(b.courseId) ? dashboardOrder.get(b.courseId) : Infinity; + if (ai !== bi) return ai - bi; + return b.total - a.total; + }); const shown = entries.slice(0, 6); const totalAll = shown.reduce((s, e) => s + e.total, 0); @@ -2690,12 +2801,7 @@ async function createTodoSections(location) { let mainSection = location.querySelector("#better-todo-main"); assignments.then(data => { const courseId = getCurrentCourseId(); - const scopedData = courseId - ? data.filter(item => { - const itemCourseId = parseInt(item.course_id || item.context_id || item?.plannable?.course_id); - return itemCourseId === courseId; - }) - : data; + const scopedData = getTodoScopedData(data, courseId); // Clicking a color in the progress display filters the list to that // one class. The filter only makes sense where multiple classes show @@ -3072,6 +3178,16 @@ function populateAssignments(iscompleted = false) { options.custom_cards_3?.[item.plannable.course_id]?.color ?? "#cccccc"; + // "Ignore card colors" (Better Todo List): when on, the class name is + // rendered black in light mode or the theme text color in dark mode + // instead of the course's card color. + const classNameColor = options.todo_ignore_card_colors + ? (options.dark_mode === true ? "var(--bctext-0)" : "#000000") + : courseColor; + // "Remove icons" (Better Todo List): when on, the task-type icon is + // omitted from the colored strip on the left of each task. + const removeIcons = options.todo_remove_icons === true; + const isCustomTask = item.plannable_type == "planner_note" || item.planner_override?.custom === true; const taskHref = isCustomTask ? customTaskHref(item) : (domain + item.html_url); const editButtonSvg = isCustomTask @@ -3079,7 +3195,7 @@ function populateAssignments(iscompleted = false) { : ""; const iconSize = isCustomTask ? 26 : 20; const iconLeftOffset = isCustomTask ? 2 : 5; - const taskIcon = isCustomTask + const taskIcon = removeIcons ? "" : isCustomTask ? ` ` @@ -3100,7 +3216,7 @@ function populateAssignments(iscompleted = false) {
- ${item.context_name} + ${item.context_name} ${item.plannable.title} ${convertToDueDate(item.plannable_date)}
@@ -3181,6 +3297,13 @@ function populateAnnouncements() { options.custom_cards_3?.[item.plannable.course_id]?.color ?? "#cccccc"; + // "Ignore card colors": black in light mode, theme text color in dark. + const classNameColor = options.todo_ignore_card_colors + ? (options.dark_mode === true ? "var(--bctext-0)" : "#000000") + : courseColor; + // "Remove icons": drop the announcement icon from the colored strip. + const removeIcons = options.todo_remove_icons === true; + let filter = ""; if (item.plannable.read_state == "read") { filter = "filter: grayscale(40%);" @@ -3190,17 +3313,17 @@ function populateAnnouncements() {
- + ${removeIcons ? "" : ` - + `}
- ${item.context_name} + ${item.context_name} ${item.plannable.title} ${convertToDueDate(item.plannable_date)}
@@ -3353,13 +3476,7 @@ function markAs(item, element) { if (progressPlaceholder && typeof assignments?.then === 'function' && progressRingsEnabled()) { assignments.then(data => { const courseId = getCurrentCourseId(); - const scopedData = courseId - ? data.map(d => Object.assign({}, d)) // shallow copy - .filter(d => { - const itemCourseId = parseInt(d.course_id || d.context_id || d?.plannable?.course_id); - return itemCourseId === courseId; - }) - : data.map(d => Object.assign({}, d)); + const scopedData = getTodoScopedData(data.map(d => Object.assign({}, d)), courseId); // reflect the updated state for this item in the snapshot for (let i = 0; i < scopedData.length; i++) { @@ -5249,7 +5366,6 @@ function applyAestheticChanges() { 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 += "#wrapper,.ic-Layout-wrapper{max-width:100%!important}"; if (options.center_cards === true) style.textContent += ".ic-DashboardCard__box__container{display:flex!important;flex-wrap:wrap!important;justify-content:center!important;align-items:flex-start!important}"; if (options.customCardStyles === true) { diff --git a/js/popup.js b/js/popup.js index e0db467..c9e4cd3 100644 --- a/js/popup.js +++ b/js/popup.js @@ -1,4 +1,4 @@ -const syncedSwitches = ['remind', 'tab_icons', 'hide_feedback', 'dark_mode', 'remlogo', 'full_width', 'auto_dark', 'assignments_due', 'gpa_calc', 'gradient_cards', 'disable_color_overlay', 'dashboard_grades', 'dashboard_notes', 'better_todo', 'better_sidebar', 'condensed_cards', 'hide_new_canvas', 'center_cards', 'quiz_safe_mode']; +const syncedSwitches = ['remind', 'tab_icons', 'dark_mode', 'remlogo', 'full_width', 'auto_dark', 'assignments_due', 'gpa_calc', 'gradient_cards', 'disable_color_overlay', 'dashboard_grades', 'dashboard_notes', 'better_todo', 'better_sidebar', 'condensed_cards', 'hide_new_canvas', 'center_cards', 'quiz_safe_mode']; const syncedSubOptions = [ "todo_hide_feedback", "todo_full_height", @@ -21,6 +21,8 @@ const syncedSubOptions = [ "todo_hr24", "todo_separate_scrollbar", "todo_alternate_colors", + "todo_ignore_card_colors", + "todo_remove_icons", "grade_hover", // "hide_completed", "num_todo_items", @@ -51,9 +53,9 @@ const localSwitches = []; const exportDarkSchedule = ["auto_dark", "auto_dark_start", "auto_dark_end", "device_dark"]; const exportCardColorToggles = ["gradient_cards", "disable_color_overlay"]; const exportCardStyles = ["customCardStyles", "imageSize", "cardRoundness", "cardSpacing", "cardWidth", "cardHeight", "cardPadding"]; -const exportLayout = ["full_width", "center_cards", "condensed_cards", "equal_height_cards", "remlogo", "hide_new_canvas", "hide_feedback", "tab_icons"]; +const exportLayout = ["full_width", "center_cards", "condensed_cards", "equal_height_cards", "remlogo", "hide_new_canvas", "tab_icons"]; const exportSidebar = ["better_sidebar", "sidebar_scale"]; -const exportTodo = ["better_todo", "todo_hide_feedback", "todo_full_height", "todo_confetti", "todo_progress_rings", "todo_timeframe", "todo_hr24", "todo_separate_scrollbar", "todo_alternate_colors", "hover_preview"]; +const exportTodo = ["better_todo", "todo_hide_feedback", "todo_full_height", "todo_confetti", "todo_progress_rings", "todo_timeframe", "todo_hr24", "todo_separate_scrollbar", "todo_alternate_colors", "todo_ignore_card_colors", "todo_remove_icons", "hover_preview"]; const exportGpa = ["gpa_calc", "gpa_calc_prepend", "gpa_calc_cumulative", "gpa_calc_weighted"]; const exportBackground = ["customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "customBackgroundNasaDaily", "fitImageToScreen", "card_transparency", "bg_opacity", "sidebar_opacity", "bg_blur", "sidebar_blur", "card_opacity", "card_blur"]; // Master "On/off toggles" = every visual toggle (no GPA, no dark-mode schedule, @@ -112,6 +114,8 @@ const defaultOptions = { "todo_hr24": false, "todo_separate_scrollbar": false, "todo_alternate_colors": false, + "todo_ignore_card_colors": false, + "todo_remove_icons": false, "condensed_cards": false, "center_cards": false, "custom_cards": {}, @@ -145,7 +149,6 @@ const defaultOptions = { "card_overdues": false, "relative_dues": false, "equal_height_cards": false, - "hide_feedback": false, "hide_new_canvas": true, "quiz_safe_mode": false, "dark_mode_fix": [], @@ -557,14 +560,6 @@ function toggleCardTransparencyOptions() { if (blurRow) blurRow.style.display = on ? "" : "none"; } -// Hide the standalone feedback toggle when Better Todo's own sub-option replaces it. -function toggleBetterTodoSubOptions(betterTodoOn) { - const hideFeedbackEl = document.getElementById("hide_feedback"); - if (hideFeedbackEl) { - hideFeedbackEl.style.display = betterTodoOn ? "none" : "flex"; - } -} - // "Alternate colors" (Better Todo List sub-option) only makes sense in light // mode, so hide its checkbox whenever dark mode is on. function toggleAlternateColorsVisibility(darkModeOn) { @@ -619,6 +614,9 @@ function setupFeatureSearch(menu) { if (label) return label.textContent.trim(); const st = sub.querySelector(".sub-text"); if (st) return st.textContent.trim(); + // Bare label (e.g. Export Settings checkboxes have no label/.sub-text). + const span = sub.querySelector("span"); + if (span) return span.textContent.trim(); return ""; } @@ -769,6 +767,50 @@ function setupFeatureSearch(menu) { add({ key: "color:" + (btnId || "?") + ":" + text, text, el: h, action: () => goToTabElement(h) }); }); + // Tab: range sliders that live in dedicated rows (Opacity & Blur, Image scale). + // These aren't .sub-option, so the generic blocks above miss them. + document.querySelectorAll(".tab .opacity-slider-row, .tab .background-scale-row").forEach(row => { + if (shouldSkip(row)) return; + const label = row.querySelector(".sub-text"); + const input = row.querySelector("input[type='range']"); + if (!label || !input) return; + const text = label.textContent.trim(); + if (!text) return; + const btnId = tabElToBtnId.get(row.closest(".tab")); + add({ key: "slider:" + (btnId || "?") + ":" + (input.id || text), text, el: row, action: () => goToTabElement(row) }); + }); + + // Tab: preset buttons (dark mode presets + popular color palettes). + document.querySelectorAll(".tab .preset-button").forEach(btn => { + if (shouldSkip(btn)) return; + const text = btn.textContent.trim().replace(/\s+/g, " "); + if (!text || text === "placeholder") return; + const btnId = tabElToBtnId.get(btn.closest(".tab")); + add({ key: "preset:" + (btnId || "?") + ":" + (btn.id || text), text, el: btn, action: () => goToTabElement(btn) }); + }); + + // Tab: standalone action buttons with distinct labels (GPA scale presets, revert colors). + ["gpa-plus-minus", "gpa-by-letter", "revert-colors", "clearCustomBackground"].forEach(id => { + const btn = document.getElementById(id); + if (!btn || shouldSkip(btn)) return; + const text = btn.textContent.trim(); + if (!text) return; + add({ key: "action:" + id, text, el: btn, action: () => goToTabElement(btn) }); + }); + + // Tab: checkbox/radio controls whose label is a bare sibling span and that + // aren't wrapped in .sub-option (e.g. "Get Active Cards From Dashboard", + // sidebar background "Solid/Gradient/Image"). + document.querySelectorAll(".tab input[type='checkbox'], .tab input[type='radio']").forEach(input => { + if (!input.id) return; + if (input.closest(".sub-option") || input.closest(".option")) return; + if (shouldSkip(input)) return; + const text = (input.parentElement && input.parentElement.textContent.trim()) || ""; + if (!text) return; + const btnId = tabElToBtnId.get(input.closest(".tab")); + add({ key: "control:" + (btnId || "?") + ":" + input.id, text, el: input, action: () => goToTabElement(input) }); + }); + return index; } @@ -900,6 +942,8 @@ function setup() { "todo_hr24", "todo_separate_scrollbar", "todo_alternate_colors", + "todo_ignore_card_colors", + "todo_remove_icons", "grade_hover", // "hide_completed", "hover_preview", @@ -1058,9 +1102,6 @@ function setup() { if (option === "better_sidebar") { toggleBetterSidebarSubOptions(status); } - if (option === "better_todo") { - toggleBetterTodoSubOptions(status); - } if (option === "dark_mode") { toggleAlternateColorsVisibility(status); } @@ -1068,7 +1109,6 @@ function setup() { }); }); toggleBetterSidebarSubOptions(sync["better_sidebar"] === true); - toggleBetterTodoSubOptions(sync["better_todo"] === true); ["gpa_calc", "assignments_due", "better_todo", "auto_dark"].forEach(opt => { toggleSubOptionsVisibility(opt, sync[opt] === true); }); @@ -1715,6 +1755,9 @@ function saveCurrentTheme() { "todo_timeframe": current["todo_timeframe"], "todo_hr24": current["todo_hr24"], "todo_separate_scrollbar": current["todo_separate_scrollbar"], + "todo_alternate_colors": current["todo_alternate_colors"], + "todo_ignore_card_colors": current["todo_ignore_card_colors"], + "todo_remove_icons": current["todo_remove_icons"], "better_sidebar": current["better_sidebar"], "sidebar_scale": current["sidebar_scale"], "imageSize": current["imageSize"], @@ -1983,8 +2026,18 @@ function setCustomImage(key, val) { updateCards(key, { "img": val }); } -function displayAdvancedCards() { - sendFromPopup("getCards"); +async function displayAdvancedCards() { + // Sync from Canvas only when needed: when there are no cards yet (fresh + // install) or when older cards predate the stored fullName field. Once + // every card has a fullName this is a no-op and the grid renders instantly. + const needsSync = await new Promise(resolve => { + chrome.storage.sync.get("custom_cards", s => { + const c = s.custom_cards || {}; + const ids = Object.keys(c); + resolve(!ids.length || ids.some(id => !c[id].fullName)); + }); + }); + if (needsSync) await sendFromPopup("getCards"); chrome.storage.sync.get(["custom_cards", "custom_cards_2"], storage => { @@ -2024,12 +2077,14 @@ function displayAdvancedCards() { } }); + // showCardEditMenu sets an inline display:none on the grid; clear it + // so reopening the menu shows the cards again (CSS default = grid). + cardGrid.style.display = ""; const editMenu = document.getElementById("card-edit-menu"); if (editMenu) { editMenu.style.display = "none"; } }); - sendFromPopup("getCards"); } function createCourseButton(courseId, courseData) { @@ -2037,6 +2092,7 @@ function createCourseButton(courseId, courseData) { button.className = "course-card-button"; const displayName = courseData.name || + courseData.fullName || courseData.default || courseData.code || `Course ${courseId}`; @@ -2064,6 +2120,7 @@ function showCardEditMenu(courseId, courseData) { const displayName = courseData.name || + courseData.fullName || courseData.default || courseData.code || `Course ${courseId}`;