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']; const syncedSubOptions = [ "todo_hide_feedback", "todo_full_height", "todo_confetti", "device_dark", "relative_dues", "card_overdues", "equal_height_cards", // "todo_overdues", "gpa_calc_prepend", "auto_dark", "auto_dark_start", "auto_dark_end", "num_assignments", "assignment_date_format", "todo_hr24", "todo_separate_scrollbar", "grade_hover", // "hide_completed", "num_todo_items", "hover_preview", // "scheduledReminder", // "scheduledReminderTime", "customCardStyles", "imageSize", "cardRoundness", "cardSpacing", "cardWidth", "cardHeight", "customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "customBackgroundNasaDaily", "fitImageToScreen", "sidebar_scale", ]; const localSwitches = []; const fontsDropdownStateKey = "fonts_dropdown_open"; //const apiurl = "http://localhost:3000"; // const apiurl = "https://canvasrefined.diditupe.dev"; const apiurl = "none"; const defaultOptions = { "local": { "previous_colors": null, "previous_theme": null, "errors": [], }, "sync": { "dark_preset": { "background-0": "#161616", "background-1": "#1e1e1e", "background-2": "#262626", "borders": "#3c3c3c", "text-0": "#f5f5f5", "text-1": "#e2e2e2", "text-2": "#ababab", "links": "#56Caf0", "sidebar": "#1e1e1e", "sidebar-text": "#f5f5f5" }, "new_install": true, "assignments_due": true, "gpa_calc": false, "dark_mode": true, "gradent_cards": false, "disable_color_overlay": false, "auto_dark": false, "auto_dark_start": { "hour": "20", "minute": "00" }, "auto_dark_end": { "hour": "08", "minute": "00" }, "num_assignments": 4, "custom_domain": [""], "assignments_done": [], "dashboard_grades": false, "assignment_date_format": false, "dashboard_notes": false, "dashboard_notes_text": "", "dashboard_notes_mode": "edit", "better_todo": false, "better_sidebar": false, "sidebar_scale": 100, "todo_hr24": false, "todo_separate_scrollbar": false, "condensed_cards": false, "custom_cards": {}, "custom_cards_2": {}, "custom_cards_3": {}, "custom_assignments": [], "custom_assignments_overflow": ["custom_assignments"], "grade_hover": false, // "hide_completed": false, "num_todo_items": 10, "custom_font": { "link": "", "family": "" }, "hover_preview": true, "full_width": null, "remlogo": null, "gpa_calc_bounds": { "A+": { "cutoff": 97, "gpa": 4.3 }, "A": { "cutoff": 93, "gpa": 4 }, "A-": { "cutoff": 90, "gpa": 3.7 }, "B+": { "cutoff": 87, "gpa": 3.3 }, "B": { "cutoff": 83, "gpa": 3 }, "B-": { "cutoff": 80, "gpa": 2.7 }, "C+": { "cutoff": 77, "gpa": 2.3 }, "C": { "cutoff": 73, "gpa": 2 }, "C-": { "cutoff": 70, "gpa": 1.7 }, "D+": { "cutoff": 67, "gpa": 1.3 }, "D": { "cutoff": 63, "gpa": 1 }, "D-": { "cutoff": 60, "gpa": .7 }, "F": { "cutoff": 0, "gpa": 0 } }, // "todo_overdues": false, "card_overdues": false, "relative_dues": false, "equal_height_cards": false, "hide_feedback": false, "dark_mode_fix": [], "assignment_states": {}, "tab_icons": false, "todo_hide_feedback": false, "todo_full_height": false, "todo_progress_rings": true, "todo_confetti": true, "device_dark": false, "cumulative_gpa": { "name": "Cumulative GPA", "hidden": false, "weight": "dnc", "credits": 999, "gr": 3.21 }, // "show_updates": false, "card_method_date": false, "card_method_dashboard": true, "card_limit": 25, // "scheduledReminder": false, // "scheduledReminderTime": { "hour": "09", "minute": "00" }, "imageSize": 100, "cardRoundness": 5, "cardSpacing": 0, "cardWidth": 262, "cardHeight": 250, "customCardStyles": false, "customBackgroundLink": "", "customBackgroundScale": 100, "customBackgroundDaily": false, "customBackgroundNasaDaily": false, "fitImageToScreen": false, } }; sendFromPopup("getCards"); // refresh the cards if new ones were just recieved chrome.storage.onChanged.addListener((changes) => { if (changes["custom_cards"]) { if (Object.keys(changes["custom_cards"].oldValue).length !== Object.keys(changes["custom_cards"].newValue).length) { displayAdvancedCards(); } } }); function displayErrors() { chrome.storage.local.get("errors", storage => { storage["errors"].forEach(e => { document.querySelector("#error_log_output").value += (e + "\n\n"); }) }); } function displayDarkModeFixUrls() { let output = document.getElementById("dark-mode-fix-urls"); output.textContent = ""; chrome.storage.sync.get("dark_mode_fix", sync => { sync["dark_mode_fix"].forEach(url => { //let div = makeElement("div", "customization-button", output, url); let div = makeElement("div", output, { "className": "customization-button", "textContent": url }); div.classList.add("fixed-url"); let btn = makeElement("button", div, { "className": "dd", "textContent": "x" }); btn.addEventListener("click", () => { chrome.storage.sync.get("dark_mode_fix", sync => { for (let i = 0; i < sync["dark_mode_fix"].length; i++) { if (sync["dark_mode_fix"][i] === url) { sync["dark_mode_fix"].splice(i); chrome.storage.sync.set({ "dark_mode_fix": sync["dark_mode_fix"] }).then(() => div.remove()); } } }); }) }) }) } document.addEventListener("DOMContentLoaded", setup); function setupAssignmentsSlider(initial) { let el = document.querySelector('#numAssignmentsSlider'); el.value = initial; document.querySelector('#numAssignments').textContent = initial; el.addEventListener('input', function () { document.querySelector('#numAssignments').textContent = this.value; chrome.storage.sync.set({ "num_assignments": this.value }); }); } function setupTodoSlider(initial) { let el = document.querySelector('#numTodoItemsSlider'); el.value = initial; document.querySelector('#numTodoItems').textContent = initial; document.querySelector('#numTodoItemsSlider').addEventListener('input', function () { document.querySelector('#numTodoItems').textContent = this.value; chrome.storage.sync.set({ "num_todo_items": this.value }); }); } function setupSidebarScaleSlider(initial) { let el = document.querySelector("#sidebarScaleSlider"); if (!el) return; el.value = initial; document.querySelector("#sidebarScaleValue").textContent = initial; el.addEventListener("input", function () { document.querySelector("#sidebarScaleValue").textContent = this.value; chrome.storage.sync.set({ "sidebar_scale": parseInt(this.value) }); }); } function setupAutoDarkInput(initial, time) { let el = document.querySelector('#' + time); el.value = initial.hour + ":" + initial.minute; el.addEventListener('change', function () { let timeinput = { "hour": this.value.split(':')[0], "minute": this.value.split(':')[1] }; time === "auto_dark_start" ? chrome.storage.sync.set({ auto_dark_start: timeinput }) : chrome.storage.sync.set({ auto_dark_end: timeinput }); }); } // function setupScheduledReminderInput(initial) { // let el = document.querySelector('#scheduledReminderTime'); // el.value = initial.hour + ":" + initial.minute; // el.addEventListener('change', function () { // let timeinput = { "hour": this.value.split(':')[0], "minute": this.value.split(':')[1] }; // chrome.storage.sync.set({ scheduledReminderTime: timeinput }); // }); // } function setupCardLimitSlider(initial) { let el = document.querySelector("#card_limit"); el.value = initial; document.querySelector("#card_limit_num").textContent = initial; el.addEventListener("change", (e) => { chrome.storage.sync.set({ "custom_cards": {}, "custom_cards_2": {}, "custom_cards_3": {}, "card_limit": parseInt(e.target.value)}); }); el.addEventListener("input", (e) => { document.querySelector("#card_limit_num").textContent = e.target.value; }) } function setupDashboardMethod(initial) { const el = document.getElementById("card_method_dashboard"); el.checked = initial === true ? true : false; el.addEventListener("change", (e) => { chrome.storage.sync.set({ "custom_cards": {}, "custom_cards_2": {}, "custom_cards_3": {}, "card_method_dashboard": e.target.checked }); }); } function setupImageSizeInput(initial) { let el = document.querySelector("#imageSize"); el.value = initial; el.addEventListener("input", (e) => { chrome.storage.sync.set({ "imageSize": e.target.value }); }); } function setupCardRoundnessInput(initial) { let el = document.querySelector("#cardRoundness"); el.value = initial; el.addEventListener("input", (e) => { chrome.storage.sync.set({ "cardRoundness": e.target.value }); }); } function setupCardSpacingInput(initial) { let el = document.querySelector("#cardSpacing"); el.value = initial; el.addEventListener("input", (e) => { chrome.storage.sync.set({ "cardSpacing": e.target.value }); }); } function setupCardWidthInput(initial) { let el = document.querySelector("#cardWidth"); el.value = initial; el.addEventListener("input", (e) => { chrome.storage.sync.set({ "cardWidth": e.target.value }); }); } function setupCardHeightInput(initial) { let el = document.querySelector("#cardHeight"); el.value = initial; el.addEventListener("input", (e) => { chrome.storage.sync.set({ "cardHeight": e.target.value }); }); } function setupCustomBackgroundLink(initial) { let el = document.querySelector("#customBackgroundLink"); el.value = initial || ""; el.addEventListener("input", (e) => { chrome.storage.sync.set({ "customBackgroundLink": e.target.value }); renderBackgroundPresetSelection(); }) } function setupCustomBackgroundScale(initial) { const el = document.querySelector("#customBackgroundScale"); const output = document.querySelector("#customBackgroundScaleValue"); if (!el || !output) return; const value = Number(initial) || 100; el.value = value; output.textContent = `${value}%`; el.addEventListener("input", (e) => { const nextValue = parseInt(e.target.value); output.textContent = `${nextValue}%`; chrome.storage.sync.set({ "customBackgroundScale": nextValue }); renderBackgroundPresetSelection(); }); } function getDailyBackgroundPreset() { if (typeof backgroundPresets === "undefined" || !Array.isArray(backgroundPresets) || backgroundPresets.length === 0) { return null; } const today = new Date(); const dayNumber = Math.floor(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()) / 86400000); return backgroundPresets[Math.abs(dayNumber) % backgroundPresets.length]; } function syncCustomBackgroundDailyState(isDaily) { const manualControls = document.getElementById("customBackgroundManualControls"); if (manualControls) { manualControls.style.opacity = isDaily ? "0.45" : "1"; manualControls.style.pointerEvents = isDaily ? "none" : "auto"; manualControls.style.filter = isDaily ? "grayscale(1)" : "none"; } ["customBackgroundLink", "customBackgroundScale", "clearCustomBackground"].forEach(id => { const el = document.getElementById(id); if (el) el.disabled = isDaily; }); document.querySelectorAll(".background-preset-card").forEach(button => { button.disabled = isDaily; }); renderBackgroundPresetSelection(); } function renderBackgroundPresetSelection() { const isDaily = document.querySelector("#customBackgroundDaily")?.checked === true || document.querySelector("#customBackgroundNasaDaily")?.checked === true; const dailyPreset = isDaily ? getDailyBackgroundPreset() : null; const currentLink = isDaily ? (dailyPreset?.url || "") : (document.querySelector("#customBackgroundLink")?.value || ""); const currentScale = isDaily ? String(dailyPreset?.scale || "100") : String(document.querySelector("#customBackgroundScale")?.value || "100"); document.querySelectorAll(".background-preset-card").forEach(button => { const matchesLink = button.dataset.backgroundUrl === currentLink; const matchesScale = button.dataset.backgroundScale === currentScale; button.classList.toggle("selected", matchesLink && matchesScale); }); } function displayBackgroundPresets() { const container = document.querySelector("#background-presets"); if (!container || container.dataset.rendered === "true" || typeof backgroundPresets === "undefined") return; container.dataset.rendered = "true"; container.innerHTML = backgroundPresets.map(preset => ` `).join(""); container.querySelectorAll(".background-preset-card").forEach(button => { button.addEventListener("click", () => { const backgroundUrl = button.dataset.backgroundUrl; const backgroundScale = parseInt(button.dataset.backgroundScale); document.querySelector("#customBackgroundLink").value = backgroundUrl; document.querySelector("#customBackgroundScale").value = backgroundScale; document.querySelector("#customBackgroundScaleValue").textContent = `${backgroundScale}%`; chrome.storage.sync.set({ "customBackgroundLink": backgroundUrl, "customBackgroundScale": backgroundScale }); renderBackgroundPresetSelection(); }); }); renderBackgroundPresetSelection(); syncCustomBackgroundDailyState(document.querySelector("#customBackgroundDaily")?.checked === true || document.querySelector("#customBackgroundNasaDaily")?.checked === true); } function toggleBetterSidebarSubOptions(betterSidebarOn) { const remlogoEl = document.getElementById("remlogo"); if (remlogoEl) { remlogoEl.style.display = betterSidebarOn ? "none" : "flex"; } const sidebarScaleEl = document.getElementById("sidebarScaleSub"); if (sidebarScaleEl) { sidebarScaleEl.style.display = betterSidebarOn ? "block" : "none"; } } // When the Better Todo List is on, its own "Hide Recent Feedback" sub-option // (todo_hide_feedback) replaces the standalone "Hide recent feedback" toggle, // so hide the big #hide_feedback toggle to avoid showing two controls for the // same thing. Mirrors toggleBetterSidebarSubOptions. function toggleBetterTodoSubOptions(betterTodoOn) { const hideFeedbackEl = document.getElementById("hide_feedback"); if (hideFeedbackEl) { hideFeedbackEl.style.display = betterTodoOn ? "none" : "flex"; } } // Hide the sub-options of a big toggle when it is turned off. // For gpa_calc / assignments_due / better_todo the whole .sub-options block is // hidden. For auto_dark ("schedule dark mode") only the start/end time clocks // (.timesets) are hidden, leaving the device-dark checkbox visible. function toggleSubOptionsVisibility(option, isOn) { const togglesWithSubOptions = ["gpa_calc", "assignments_due", "better_todo", "auto_dark"]; if (!togglesWithSubOptions.includes(option)) return; const optionEl = document.getElementById(option); if (!optionEl) return; const subOptions = optionEl.parentElement.querySelector(":scope > .sub-options"); if (!subOptions) return; if (option === "auto_dark") { const timesets = subOptions.querySelector(".timesets"); if (timesets) timesets.style.display = isOn ? "" : "none"; } else { subOptions.style.display = isOn ? "" : "none"; } } function setupFeatureSearch(menu) { const searchInput = document.querySelector("#feature-search"); const resultsEl = document.querySelector("#feature-search-results"); const headerSearch = document.querySelector("#header-search"); if (!searchInput || !resultsEl || !headerSearch) return; // Map (reference-keyed) each .tab element -> the button id that opens it. // A plain object won't work: DOM elements stringify to the same key. const tabElToBtnId = new Map(); Object.entries(menu.tabs).forEach(([btnId, info]) => { const tabEl = document.querySelector(info.tab); if (tabEl) tabElToBtnId.set(tabEl, btnId); }); function shouldSkip(el) { // Skip overlays / scrapped containers that aren't real features if (el.closest('#submit-popup, #browser-settings-popup, #opt-in, #card-edit-menu')) return true; // Skip anything inside a statically-hidden option-container // (e.g. the scrapped "remind" block, "Submit your theme"). // Dynamically-hidden sub-options (whose parent toggle is off) are kept. let node = el; while (node && node !== document.body) { if (node.classList && node.classList.contains('option-container') && node.style.display === 'none') return true; node = node.parentElement; } return false; } function labelOf(sub) { const label = sub.querySelector("label"); if (label) return label.textContent.trim(); const st = sub.querySelector(".sub-text"); if (st) return st.textContent.trim(); return ""; } function keyIdOf(sub) { const label = sub.querySelector("label"); if (label && label.getAttribute("for")) return label.getAttribute("for"); const input = sub.querySelector("input"); if (input && input.id) return input.id; return labelOf(sub); } function categoryFor(el) { const tabEl = el.closest(".tab"); if (tabEl) { const btnId = tabElToBtnId.get(tabEl); if (btnId) { const btn = document.getElementById(btnId); const span = btn && btn.querySelector("span"); if (span) return span.textContent.trim(); } return "Section"; } if (el.classList && el.classList.contains("tab-btn")) return "Section"; if (el.classList && el.classList.contains("option")) return "Settings"; const owner = el.closest(".option"); if (owner) { const name = owner.querySelector(".option-name"); if (name) return name.textContent.trim(); } return "Settings"; } function openTab(btnId) { const btn = document.getElementById(btnId); if (btn) btn.click(); } function showMain() { const back = document.querySelector(".back-btn"); if (back) back.click(); } // Reveal any hidden sub-option blocks between el and .main so the target // is visible (purely visual; doesn't change stored settings). function revealAncestors(el) { const main = document.querySelector(".main"); let node = el; while (node && node !== main && node !== document.body) { if (node.style && node.style.display === "none") node.style.display = ""; node = node.parentElement; } } function highlight(el) { el.classList.add("search-highlight"); setTimeout(() => el.classList.remove("search-highlight"), 2000); } function goToMainElement(el) { showMain(); revealAncestors(el); requestAnimationFrame(() => { el.scrollIntoView({ behavior: "smooth", block: "center" }); highlight(el); }); } function goToTabElement(el) { const tabEl = el.closest(".tab"); const btnId = tabElToBtnId.get(tabEl); if (btnId) openTab(btnId); requestAnimationFrame(() => { el.scrollIntoView({ behavior: "smooth", block: "center" }); highlight(el); }); } let featureIndex = null; let currentMatches = []; let activeIndex = -1; function buildIndex() { const index = []; const seen = new Set(); function add(entry) { if (!entry.text || seen.has(entry.key)) return; seen.add(entry.key); index.push(entry); } // Big section buttons (Edit Dark Mode, Cards, Themes, Styles, GPA Settings, Report issue) document.querySelectorAll(".more-options-container .tab-btn").forEach(btn => { const span = btn.querySelector("span"); add({ key: "tab:" + btn.id, text: span ? span.textContent.trim() : "", el: btn, action: () => openTab(btn.id) }); }); // Home: option toggles document.querySelectorAll(".options .option").forEach(opt => { if (!opt.id) return; const name = opt.querySelector(".option-name"); if (!name) return; if (shouldSkip(opt)) return; add({ key: "option:" + opt.id, text: name.textContent.trim(), el: opt, action: () => goToMainElement(opt) }); }); // Home: sub-options / timesets / labelled rows (e.g. "Use dd/mm", "Start time", max-items) document.querySelectorAll(".options .sub-option, .options .timeset, .options .sub-options > div").forEach(sub => { if (shouldSkip(sub)) return; // Skip statically-hidden sub-options (e.g. the "hover preview" TODO). // Dynamically-hidden sub-options have display:none on their parent // .sub-options, not on themselves, so they stay indexed. if (sub.classList.contains("sub-option") && sub.style.display === "none") return; const text = labelOf(sub); if (!text) return; add({ key: "sub:" + keyIdOf(sub), text, el: sub, action: () => goToMainElement(sub) }); }); // Home: custom Canvas URL const customDomain = document.querySelector("#customDomain"); if (customDomain && !shouldSkip(customDomain)) { const wrap = customDomain.closest(".customDomain"); const label = wrap ? wrap.querySelector("[data-i18n='enter_url']") : null; add({ key: "custom:customDomain", text: label ? label.textContent.trim() : "Canvas URL", el: wrap || customDomain, action: () => goToMainElement(wrap || customDomain) }); } // Tabs: section headings (e.g. "Presets", "Custom styles", "Popular Palettes", "Custom Background") document.querySelectorAll(".tab .header-small").forEach(h => { if (shouldSkip(h)) return; const text = h.textContent.trim(); if (!text) return; const btnId = tabElToBtnId.get(h.closest(".tab")); add({ key: "heading:" + (btnId || "?") + ":" + text, text, el: h, action: () => goToTabElement(h) }); }); // Tabs: checkbox sub-options (e.g. "Daily Random Image", "Custom Card Styles") document.querySelectorAll(".tab .sub-option").forEach(sub => { if (shouldSkip(sub)) return; const text = labelOf(sub); if (!text) return; const btnId = tabElToBtnId.get(sub.closest(".tab")); add({ key: "tabsub:" + (btnId || "?") + ":" + keyIdOf(sub), text, el: sub, action: () => goToTabElement(sub) }); }); // Dark mode color fields (e.g. "Sidebar Text", "Background Main") document.querySelectorAll(".tab .color-type-header").forEach(h => { if (shouldSkip(h)) return; const text = h.textContent.trim(); if (!text) return; const btnId = tabElToBtnId.get(h.closest(".tab")); add({ key: "color:" + (btnId || "?") + ":" + text, text, el: h, action: () => goToTabElement(h) }); }); return index; } function filterFeatures(query) { const q = query.trim().toLowerCase(); if (!q) return []; if (!featureIndex) featureIndex = buildIndex(); const scored = []; for (const entry of featureIndex) { const t = entry.text.toLowerCase(); let score = -1; if (t === q) score = 1000; else if (t.startsWith(q)) score = 500 - t.length; else { const idx = t.indexOf(q); if (idx !== -1) score = 200 - idx; else if (t.split(/\s+/).some(w => w.toLowerCase().startsWith(q))) score = 50; } if (score >= 0) scored.push({ entry, score }); } scored.sort((a, b) => b.score - a.score || a.entry.text.length - b.entry.text.length); return scored.slice(0, 12).map(s => s.entry); } function renderResults(matches) { currentMatches = matches; activeIndex = matches.length ? 0 : -1; resultsEl.innerHTML = ""; if (!matches.length) { const empty = document.createElement("div"); empty.className = "search-result empty"; empty.textContent = "No features found"; resultsEl.appendChild(empty); resultsEl.classList.add("open"); return; } matches.forEach((entry, i) => { const item = document.createElement("div"); item.className = "search-result" + (i === activeIndex ? " active" : ""); item.setAttribute("role", "option"); const title = document.createElement("span"); title.className = "search-result-title"; title.textContent = entry.text; const cat = document.createElement("span"); cat.className = "search-result-category"; cat.textContent = categoryFor(entry.el); item.appendChild(title); item.appendChild(cat); item.addEventListener("mousedown", (e) => { e.preventDefault(); selectResult(i); }); resultsEl.appendChild(item); }); resultsEl.classList.add("open"); } function setActive(i) { activeIndex = i; const items = resultsEl.querySelectorAll(".search-result"); items.forEach((el, idx) => el.classList.toggle("active", idx === i)); const active = resultsEl.querySelector(".search-result.active"); if (active) active.scrollIntoView({ block: "nearest" }); } function closeResults() { resultsEl.classList.remove("open"); resultsEl.innerHTML = ""; currentMatches = []; activeIndex = -1; } function selectResult(i) { const entry = currentMatches[i]; if (!entry) return; closeResults(); searchInput.value = ""; searchInput.blur(); entry.action(); } searchInput.addEventListener("input", () => { if (!searchInput.value.trim()) { closeResults(); return; } renderResults(filterFeatures(searchInput.value)); }); searchInput.addEventListener("focus", () => { if (searchInput.value.trim()) renderResults(filterFeatures(searchInput.value)); }); searchInput.addEventListener("keydown", (e) => { if (!currentMatches.length) { if (e.key === "Escape") searchInput.blur(); return; } if (e.key === "ArrowDown") { e.preventDefault(); setActive((activeIndex + 1) % currentMatches.length); } else if (e.key === "ArrowUp") { e.preventDefault(); setActive((activeIndex - 1 + currentMatches.length) % currentMatches.length); } else if (e.key === "Enter") { e.preventDefault(); if (activeIndex >= 0) selectResult(activeIndex); } else if (e.key === "Escape") { e.preventDefault(); closeResults(); searchInput.blur(); } }); searchInput.addEventListener("blur", () => setTimeout(closeResults, 150)); const icon = headerSearch.querySelector(".header-search-icon"); if (icon) icon.addEventListener("click", () => searchInput.focus()); document.addEventListener("click", (e) => { if (!e.target.closest("#header-search")) closeResults(); }); } function setup() { const menu = { switches: syncedSwitches, checkboxes: [ "browser_show_likes", "gpa_calc_weighted", "gpa_calc_cumulative", // /*'card_method_date',*/ "show_updates", "todo_hide_feedback", "todo_progress_rings", "todo_confetti", "todo_full_height", "device_dark", "relative_dues", "card_overdues", "equal_height_cards", // "todo_overdues", "gpa_calc_prepend", "auto_dark", "assignment_date_format", "todo_hr24", "todo_separate_scrollbar", "grade_hover", // "hide_completed", "hover_preview", "customBackgroundDaily", "customBackgroundNasaDaily", "fitImageToScreen", // "scheduledReminder", "customCardStyles", ], tabs: { "advanced-settings": { setup: displayAdvancedCards, tab: ".advanced", }, "gpa-bounds-btn": { setup: displayGPABounds, tab: ".gpa-bounds-container", }, "custom-font-btn": { setup: displayCustomFont, tab: ".custom-font-container", }, "card-colors-btn": { setup: null, tab: ".card-colors-container" }, "customize-dark-btn": { setup: displayDarkModeFixUrls, tab: ".customize-dark", }, "import-export-btn": { setup: displayThemeList, tab: ".import-export", }, "report-issue-btn": { setup: displayErrors, tab: ".report-issue-container", }, "updates-btn": { setup: null, tab: ".updates-container" }, }, special: [ { identifier: "auto_dark_start", setup: (initial) => setupAutoDarkInput(initial, "auto_dark_start"), }, { identifier: "auto_dark_end", setup: (initial) => setupAutoDarkInput(initial, "auto_dark_end"), }, { identifier: "num_assignments", setup: (initial) => setupAssignmentsSlider(initial), }, { identifier: "num_todo_items", setup: (initial) => setupTodoSlider(initial), }, { identifier: "sidebar_scale", setup: (initial) => setupSidebarScaleSlider(initial), }, { identifier: "card_limit", setup: (initial) => setupCardLimitSlider(initial), }, { identifier: "card_method_dashboard", setup: (initial) => setupDashboardMethod(initial), }, { identifier: "custom_styles", setup: (initial) => setupCustomStyle(initial), }, // { // identifier: "scheduledReminderTime", // setup: (initial) => setupScheduledReminderInput(initial), // }, { identifier: "imageSize", setup: (initial) => setupImageSizeInput(initial), }, { identifier: "cardRoundness", setup: (initial) => setupCardRoundnessInput(initial), }, { identifier: "cardSpacing", setup: (initial) => setupCardSpacingInput(initial), }, { identifier: "cardWidth", setup: (initial) => setupCardWidthInput(initial), }, { identifier: "cardHeight", setup: (initial) => setupCardHeightInput(initial), }, { identifier: "customBackgroundLink", setup: (initial) => setupCustomBackgroundLink(initial), }, { identifier: "customBackgroundScale", setup: (initial) => setupCustomBackgroundScale(initial), }, ], }; chrome.storage.sync.get(menu.switches, sync => { menu.switches.forEach(option => { let optionSwitch = document.getElementById(option); let status = sync[option] === true ? "#on" : "#off"; optionSwitch.querySelector(status).checked = true; optionSwitch.querySelector(status).classList.add('checked'); optionSwitch.querySelector(".slider").addEventListener("mouseup", () => { let status = !optionSwitch.querySelector("#on").checked; optionSwitch.querySelector("#on").checked = status; optionSwitch.querySelector("#on").classList.toggle("checked"); optionSwitch.querySelector("#off").classList.toggle("checked"); chrome.storage.sync.set({ [option]: status }); if (option === "auto_dark") { toggleDarkModeDisable(status); } if (option === "better_sidebar") { toggleBetterSidebarSubOptions(status); } if (option === "better_todo") { toggleBetterTodoSubOptions(status); } toggleSubOptionsVisibility(option, status); }); }); 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); }); }); chrome.storage.sync.get(menu.checkboxes, sync => { menu.checkboxes.forEach(option => { const checkbox = document.querySelector("#" + option); if (!checkbox) {console.log(option); return;} checkbox.addEventListener("change", function (e) { let status = this.checked; if (option === "customBackgroundDaily" && status) { document.querySelector("#customBackgroundNasaDaily").checked = false; chrome.storage.sync.set({ "customBackgroundDaily": true, "customBackgroundNasaDaily": false }); } else if (option === "customBackgroundNasaDaily" && status) { document.querySelector("#customBackgroundDaily").checked = false; chrome.storage.sync.set({ "customBackgroundNasaDaily": true, "customBackgroundDaily": false }); } else { chrome.storage.sync.set(JSON.parse(`{"${option}": ${status}}`)); } syncCustomBackgroundDailyState(document.querySelector("#customBackgroundDaily")?.checked === true || document.querySelector("#customBackgroundNasaDaily")?.checked === true); }); const value = sync[option] !== undefined ? sync[option] : defaultOptions.sync[option]; document.querySelector("#" + option).checked = value; }); syncCustomBackgroundDailyState(sync.customBackgroundDaily === true || sync.customBackgroundNasaDaily === true); /* document.querySelector('#autodark_start').value = result.auto_dark_start["hour"] + ":" + result.auto_dark_start["minute"]; document.querySelector('#autodark_end').value = result.auto_dark_end["hour"] + ":" + result.auto_dark_end["minute"]; document.querySelector("#assignment_date_format").checked = result.assignment_date_format == true; document.querySelector("#todo_hr24").checked = result.todo_hr24 == true; */ toggleDarkModeDisable(sync.auto_dark); displayBackgroundPresets(); }); const specialOptions = menu.special.map(obj => obj.identifier); chrome.storage.sync.get(specialOptions, sync => { console.log(sync); menu.special.forEach(option => { if (option.setup !== null) { const value = sync[option.identifier] !== undefined ? sync[option.identifier] : defaultOptions.sync[option.identifier]; option.setup(value); } }); }) /* // checkboxes menu.checkboxes.forEach(checkbox => { document.querySelector("#" + checkbox).addEventListener('change', function () { let status = this.checked; chrome.storage.sync.set(JSON.parse(`{"${checkbox}": ${status}}`)); }); }); */ // activate tab buttons document.querySelectorAll(".tab-btn").forEach(btn => { btn.addEventListener("click", () => { if (menu.tabs[btn.id].setup !== null) menu.tabs[btn.id].setup(); document.querySelector(".main").style.display = "none"; document.querySelector(menu.tabs[btn.id].tab).style.display = "block"; window.scrollTo(0, 0); }); }); // activate the back buttons on each tab document.querySelectorAll(".back-btn").forEach(btn => { btn.addEventListener("click", function () { document.querySelectorAll(".tab").forEach(tab => { tab.style.display = "none"; }); document.querySelector(".main").style.display = "block"; }); }); // give everything the appropirate i18n text document.querySelectorAll('[data-i18n]').forEach(text => { text.innerText = chrome.i18n.getMessage(text.dataset.i18n); }); document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { const msg = chrome.i18n.getMessage(el.dataset.i18nPlaceholder); if (msg) el.placeholder = msg; }); // header feature search (search bar that jumps to features) setupFeatureSearch(menu); // activate dark mode inspector button document.querySelector("#inspector-btn").addEventListener("click", async function () { document.querySelector("#inspector-output").textContent = (await sendFromPopup("inspect"))["selectors"]; }); // activate dark mode fixer button document.querySelector("#fix-dm-btn").addEventListener("click", async function () { let output = await sendFromPopup("fixdm"); if (output.path === "canvasrefined-none" || output.path === "canvasrefined-darkmode_off") return; let rating = "bad"; if (output.time < 100) { rating = "good"; } else if (output.time < 250) { rating = "ok"; } document.getElementById("fix-dm-output").textContent = "Fix took " + Math.round(output.time) + "ms (rating: " + rating + ")"; chrome.storage.sync.get("dark_mode_fix", sync => { if (sync["dark_mode_fix"].includes(output.path)) return; sync["dark_mode_fix"].push(output.path); chrome.storage.sync.set({ "dark_mode_fix": sync["dark_mode_fix"] }).then(() => displayDarkModeFixUrls()); }) }); // activate storage dump button document.querySelector("#rk_btn").addEventListener("click", () => { chrome.storage.local.get(null, local => { chrome.storage.sync.get(null, sync => { document.querySelector("#rk_output").value = JSON.stringify(local) + JSON.stringify(sync); }) }) }); // activate storage reset button document.querySelector("#storage-reset-btn").addEventListener("click", () => { chrome.storage.sync.set(defaultOptions["sync"]); }); // activate custom url input document.querySelector('#customDomain').addEventListener('input', function () { let domains = this.value.split(","); domains.forEach((domain, index) => { let val = domain.replace(" ", ""); if (val === "") return; //if (!val.includes("https://") && !val.includes("http://")) val = "https://" + val; try { let url = new URL(val); domains[index] = url.hostname; clearAlert(); } catch (e) { domains[index] = val; displayAlert(true, "The URL you entered appears to be invalid, so it might not work."); } }); chrome.storage.sync.set({ custom_domain: domains }); }); // setup custom url chrome.storage.sync.get(["custom_domain"], storage => { document.querySelector("#customDomain").value = storage.custom_domain ? storage.custom_domain : ""; }); // activate import input box document.querySelector("#import-input").addEventListener("input", (e) => { const obj = JSON.parse(e.target.value); importTheme(obj); }); // activate export checkbox document.querySelectorAll(".export-details input").forEach(input => { input.addEventListener("change", () => { chrome.storage.sync.get(syncedSwitches.concat(syncedSubOptions).concat(["dark_preset", "custom_cards", "custom_font", "gpa_calc_bounds"]), async storage => { //chrome.storage.local.get(["dark_preset"], async local => { let final = {}; for await (item of document.querySelectorAll(".export-details input")) { if (item.checked) { switch (item.id) { case "export-toggles": final = { ...final, ...(await getExport(storage, syncedSwitches.concat(syncedSubOptions))) }; break; case "export-dark": final = { ...final, ...(await getExport(storage, ["dark_preset"])) }; break; case "export-cards": final = { ...final, ...(await getExport(storage, ["custom_cards"])) }; break; case "export-font": final = { ...final, ...(await getExport(storage, ["custom_font"])) }; break; case "export-colors": final = { ...final, ...(await getExport(storage, ["card_colors"])) } break; case "export-gpa": final = { ...final, ...(await getExport(storage, ["gpa_calc_bounds"])) } break; case "export-customStyles": final = { ...final, ...(await getExport(storage, ["custom_styles"])) }; break; case "export-background": final = { ...final, ...(await getExport(storage, ["customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "fitImageToScreen"])) }; break; } } } document.querySelector("#export-output").value = JSON.stringify(final); //}); }); }); }); // activate revert to original button document.querySelector("#theme-revert").addEventListener("click", () => { chrome.storage.local.get("previous_theme", local => { if (local["previous_theme"] !== null) { importTheme(local["previous_theme"]); } }); }); document.querySelector("#alert").addEventListener("click", clearAlert); // TODO: maybe fix this at some point, idk what is causing the error here document.querySelectorAll(".preset-button.customization-button").forEach(btn => btn.addEventListener("click", changeToPresetCSS)); // activate card color inputs document.querySelector("#singleColorInput").addEventListener("change", e => document.querySelector("#singleColorText").value = e.target.value); document.querySelector("#singleColorText").addEventListener("change", e => document.querySelector("#singleColorInput").value = e.target.value); document.querySelector("#gradientColorFrom").addEventListener("change", e => document.querySelector("#gradientColorFromText").value = e.target.value); document.querySelector("#gradientColorFromText").addEventListener("change", e => document.querySelector("#gradientColorFrom").value = e.target.value); document.querySelector("#gradientColorTo").addEventListener("change", e => document.querySelector("#gradientColorToText").value = e.target.value); document.querySelector("#gradientColorToText").addEventListener("change", e => document.querySelector("#gradientColorTo").value = e.target.value); document.querySelector("#setSingleColor").addEventListener("click", () => { let colors = [document.querySelector("#singleColorInput").value];; sendFromPopup("setcolors", colors); }); document.querySelector("#setGradientColor").addEventListener("click", () => { chrome.storage.sync.get("custom_cards", sync => { length = 0; Object.keys(sync["custom_cards"]).forEach(key => { if (sync["custom_cards"][key].hidden !== true) length++; }); let colors = []; let from = document.querySelector("#gradientColorFrom").value; let to = document.querySelector("#gradientColorTo").value; for (let i = 1; i <= length; i++) { colors.push(getColorInGradient(i / length, from, to)); } sendFromPopup("setcolors", colors); }); }); // activate revert to original card colors button document.querySelector("#revert-colors").addEventListener("click", () => { chrome.storage.local.get("previous_colors", local => { if (local["previous_colors"] !== null) { sendFromPopup("setcolors", local["previous_colors"].colors); } }) }) // activate every card color palette button document.querySelectorAll(".preset-button.colors-button").forEach(btn => { const colors = getPalette(btn.querySelector("p").textContent); let preview = btn.querySelector(".colors-preview"); colors.forEach(color => { let div = makeElement("div", preview, { "className": "color-preview"}); div.style.background = color; }); btn.addEventListener("click", () => { sendFromPopup("setcolors", colors); }) }); /* ['autodark_start', 'autodark_end'].forEach(function (timeset) { document.querySelector('#' + timeset).addEventListener('change', function () { let timeinput = { "hour": this.value.split(':')[0], "minute": this.value.split(':')[1] }; timeset === "autodark_start" ? chrome.storage.sync.set({ auto_dark_start: timeinput }) : chrome.storage.sync.set({ auto_dark_end: timeinput }); }); }); */ // activate sidebar tool radio ["#radio-sidebar-image", "#radio-sidebar-gradient", "#radio-sidebar-solid"].forEach(radio => { document.querySelector(radio).addEventListener("click", () => { chrome.storage.sync.get(["dark_preset"], storage => { let mode = radio === "#radio-sidebar-image" ? "image" : radio === "#radio-sidebar-gradient" ? "gradient" : "solid"; displaySidebarMode(mode, storage["dark_preset"]["sidebar"]); }); }) }); // theme browser controls document.getElementById("premade-themes-left").addEventListener("click", () => changePage(-1)); document.getElementById("premade-themes-right").addEventListener("click", () => changePage(1)); document.getElementById("theme-sorts").addEventListener("click", () => { const el = document.getElementById("theme-sort-selector"); if (el.classList.contains("open")) { clickout(); } else { el.classList.add("open"); setTimeout(() => { document.addEventListener("click", clickout); }, 10); } }); document.getElementById("theme-search").addEventListener("change", async (e) => { searchFor = e.target.value; console.log(searchFor); // current_page_num = 1; // displayThemeList(0); // linear search let themesToShow = []; for (let i = 0; i < themes.length; i++) { if (themes[i].title.toLowerCase().includes(searchFor.toLowerCase())) { themesToShow.push(themes[i]); } } console.log(themesToShow); displayThemeSearchList(themesToShow); }); // activate theme save button document.getElementById("save-theme").addEventListener("click", saveCurrentTheme); // activate submit theme button // document.getElementById("submit-theme-btn").addEventListener("click", submitTheme); document.getElementById("submit-theme-btn-1").addEventListener("click", () => { document.getElementById("submit-popup").classList.add("open"); }); document.getElementById("cancel-theme-btn").addEventListener("click", () => { document.getElementById("submit-popup").classList.remove("open"); }) // update theme button preview on input document.getElementById("submit-title").addEventListener("input", (e) => { document.getElementById("theme-button-title-preview").textContent = e.target.value.replaceAll(" ", ""); }); // update theme button preview on input document.getElementById("submit-credits").addEventListener("input", (e) => { document.getElementById("theme-button-creator-preview").textContent = e.target.value; }); // activate the show button to open the theme submission drawer document.getElementById("show-submit-form").addEventListener("click", (e) => { const drawer = document.getElementById("submit-drawer"); if (drawer.style.display === "none") { drawer.style.display = "block"; e.target.textContent = "Hide"; } else { drawer.style.display = "none"; e.target.textContent = "Show"; } }); // activate theme browser opt out // document.getElementById("new_browser_out").addEventListener("click", () => { chrome.storage.sync.set({ "new_browser": false }); current_page_num = 1; displayThemeList(0); // displayAlert(false, "Success! You are now viewing the old theme browser. This one will no longer recieve updates, but there is still plenty to choose from."); // }); // activate theme browser opt in // document.getElementById("new_browser_in").addEventListener("click", registerUser); document.querySelectorAll(".theme-sort-btn").forEach(btn => { btn.addEventListener("click", (e) => { themeSort(e.target.textContent); }); }); // browser settings buttons document.getElementById("browser-settings-btn").addEventListener("click", () => { document.getElementById("browser-settings-popup").classList.add("open"); }); document.getElementById("close-settings-btn").addEventListener("click", () => { displayThemeList(0); document.getElementById("browser-settings-popup").classList.remove("open"); }); // document.getElementById("reset-optin").addEventListener("click", () => { // chrome.storage.sync.set({ "new_browser": null }); // document.getElementById("opt-in").style.display = "block"; // }); // document.getElementById("view-submissions-btn").addEventListener("click", displayMySubmissions); document.getElementById("submit-form-btn").addEventListener("click", displayThemeSubmissionForm); document.getElementById("gpa-plus-minus").addEventListener("click", () => { applyGPAPreset({ "A+": { "cutoff": 97, "gpa": 4.0 }, "A": { "cutoff": 93, "gpa": 4.0 }, "A-": { "cutoff": 90, "gpa": 3.7 }, "B+": { "cutoff": 87, "gpa": 3.3 }, "B": { "cutoff": 83, "gpa": 3.0 }, "B-": { "cutoff": 80, "gpa": 2.7 }, "C+": { "cutoff": 77, "gpa": 2.3 }, "C": { "cutoff": 73, "gpa": 2.0 }, "C-": { "cutoff": 70, "gpa": 1.7 }, "D+": { "cutoff": 67, "gpa": 1.3 }, "D": { "cutoff": 63, "gpa": 1.0 }, "D-": { "cutoff": 60, "gpa": 0.7 }, "F": { "cutoff": 0, "gpa": 0 } }); }); document.getElementById("gpa-by-letter").addEventListener("click", () => { applyGPAPreset({ "A+": { "cutoff": 97, "gpa": 4.0 }, "A": { "cutoff": 93, "gpa": 4.0 }, "A-": { "cutoff": 90, "gpa": 4.0 }, "B+": { "cutoff": 87, "gpa": 3.0 }, "B": { "cutoff": 83, "gpa": 3.0 }, "B-": { "cutoff": 89, "gpa": 3.0 }, "C+": { "cutoff": 77, "gpa": 2.0 }, "C": { "cutoff": 73, "gpa": 2.0 }, "C-": { "cutoff": 70, "gpa": 2.0 }, "D+": { "cutoff": 67, "gpa": 1.0 }, "D": { "cutoff": 63, "gpa": 1.0 }, "D-": { "cutoff": 60, "gpa": 1.0 }, "F": { "cutoff": 0, "gpa": 0 } }); }); document.getElementById("imageSize").addEventListener("input", (e) => { const value = e.target.value; chrome.storage.sync.set({ "imageSize": value }); document.querySelector("#imageSizeValue").textContent = value + "%"; }) document.getElementById("cardRoundness").addEventListener("input", (e) => { const value = e.target.value; chrome.storage.sync.set({ "cardRoundness": value }); document.querySelector("#cardRoundnessValue").textContent = value + "px"; }) document.getElementById("cardSpacing").addEventListener("input", (e) => { const value = e.target.value; chrome.storage.sync.set({ "cardSpacing": value }); document.querySelector("#cardSpacingValue").textContent = value + "px"; }) document.getElementById("cardWidth").addEventListener("input", (e) => { const value = e.target.value; chrome.storage.sync.set({ "cardWidth": value }); document.querySelector("#cardWidthValue").textContent = value + "%"; }); document.getElementById("cardHeight").addEventListener("input", (e) => { const value = e.target.value; chrome.storage.sync.set({ "cardHeight": value }); document.querySelector("#cardHeightValue").textContent = value + "%"; }); document.getElementById("clearCustomBackground").addEventListener("click", () => { chrome.storage.sync.set({ "customBackgroundLink": "", "customBackgroundScale": 100 }); document.querySelector("#customBackgroundLink").value = ""; document.querySelector("#customBackgroundScale").value = 100; document.querySelector("#customBackgroundScaleValue").textContent = "100%"; renderBackgroundPresetSelection(); sendFromPopup("updateBackground"); }); const applyFontsDropdownState = (isOpen) => { const el = document.getElementById("quick-fonts"); const el2 = document.getElementsByClassName("custom-font")[0]; const arrow = document.getElementById("fontsDropdownArrow"); if (!el || !el2 || !arrow) return; el.style.display = isOpen ? "flex" : "none"; el2.style.display = isOpen ? "block" : "none"; arrow.style.transform = isOpen ? "rotate(180deg)" : "rotate(0deg)"; }; chrome.storage.local.get([fontsDropdownStateKey], (storage) => { const isOpen = storage[fontsDropdownStateKey] !== false; applyFontsDropdownState(isOpen); }); document.getElementById("fontsDropdown").addEventListener("click", () => { const el = document.getElementById("quick-fonts"); const el2 = document.getElementsByClassName("custom-font")[0]; if (!el || !el2) return; const isCurrentlyOpen = getComputedStyle(el).display !== "none" && getComputedStyle(el2).display !== "none"; const nextOpen = !isCurrentlyOpen; applyFontsDropdownState(nextOpen); chrome.storage.local.set({ [fontsDropdownStateKey]: nextOpen }); }); } function applyGPAPreset(bounds) { chrome.storage.sync.set({ "gpa_calc_bounds": bounds }, () => { displayGPABounds(); }); } function setupCustomStyle(initial) { const el = document.getElementById("custom-styles"); el.value = initial; el.addEventListener("change", (e) => { chrome.storage.sync.set({ "custom_styles": e.target.value }); }); } function displayThemeSubmissionForm() { document.getElementById("submit-form").style.display = "block"; document.getElementById("view-submissions").style.display = "none"; document.getElementById("submit-form-btn").classList.add("active"); document.getElementById("view-submissions-btn").classList.remove("active"); } async function displayMySubmissions() { //TODO: remake const sync = await chrome.storage.sync.get("id"); const res = await fetch(`${apiurl}/api/themes/submissions?id=${sync["id"]}`); const data = await res.json(); //if (data?.errors !== false) return; document.getElementById("submit-form").style.display = "none"; document.getElementById("view-submissions").style.display = "block"; document.getElementById("submit-form-btn").classList.remove("active"); document.getElementById("view-submissions-btn").classList.add("active"); const el = document.getElementById("latest-submissions"); el.textContent = ""; if (data.message.length === 0) { el.textContent = "You haven't submitted any themes yet."; } data.message.forEach(theme => { const container = makeElement("div", el, {"className": "submitted-theme" }); const btn = makeElement("button", container, { "className": "theme-button clickable customization-button", "style": `min-width:105px;max-width:105px;background-image:linear-gradient(rgba(0, 0, 0, 0.44), rgba(0, 0, 0, 0.44)), url(${theme.preview})` }); const title = makeElement("p", btn, { "className": "theme-button-title clickable", "textContent": theme.title }); const credits = makeElement("p", btn, { "className": "theme-button-creator clickable", "textContent": theme.credits }); const details = makeElement("div", container, { "className": "submitted-theme-details" }); const top = makeElement("div", details, { "style": "display:flex;justify-content:space-between;align-items:center" }); const tag = makeElement("span", top, { "className": "submitted-theme-tag", "textContent": theme.approved === 1 ? "Approved" : theme.approved === 0 ? "Pending" : "Rejected", "style": `background: ${theme.approved === 1 ? "#ad3a74" : theme.approved === 0 ? "#514e4e": "#000"}` }); const msg = makeElement("p", details, { "textContent": theme.approved === 1 ? "Looks great! Thanks for submitting" : theme.approved === 0 ? "Your theme is still awaiting approval." : `Your theme was rejected${theme.reason ? (": " + theme.reason) : " because it did not meet the theme guidelines."}`}); const ago = makeElement("span", top, { "className": "submitted-theme-time", "textContent": `${getRelativeDate(new Date(parseInt(theme.time))).time} ago` }); }); } async function getExport(storage, options) { let final = {}; for (const option of options) { switch (option) { case "custom_cards": let arr = []; Object.keys(storage["custom_cards"]).forEach(key => { if (storage["custom_cards"][key].img !== "") arr.push(storage["custom_cards"][key].img); }); if (arr.length === 0) { arr = ["none"]; } final["custom_cards"] = arr; break; case "card_colors": final["card_colors"] = []; try { final["card_colors"] = await sendFromPopup("getcolors"); } catch (e) { console.log(e); } break; case "custom_styles": final["customCardStyles"] = storage["customCardStyles"]; final["imageSize"] = storage["imageSize"]; final["cardRoundness"] = storage["cardRoundness"]; final["cardSpacing"] = storage["cardSpacing"]; final["cardWidth"] = storage["cardWidth"]; final["cardHeight"] = storage["cardHeight"]; break; default: final[option] = storage[option]; } } return final; } let pageTimeout = false; function changePage(direction) { if (pageTimeout) return; pageTimeout = true; displayThemeList(direction); setTimeout(() => { pageTimeout = false; }, 500); } const colorValues = { "red": 1, "pink": 2, "orange": 3, "yellow": 4, "lightgreen": 5, "green": 6, "lightblue": 7, "blue": 8, "lightpurple": 9, "purple": 10, "lightpurple": 11, "beige": 12, "brown": 13, "gray": 14, } function themeSortFn(method) { let themes = getTheme("all"); switch (method) { case "New": return themes.reverse(); case "Old": return themes; case "Color": return themes.sort((a, b) => { //return (colorValues[a.color] || 88) - (colorValues[b.color] || 88) return (colorValues[a.color] || (a.color !== "whiteblack" && a.color.includes("white") ? 15 : 16)) - (colorValues[b.color] || (b.color !== "whiteblack" && b.color.includes("white") ? 15 : 16)) }) return themes.sort((a, b) => { return a.color < b.color ? 1 : -1; }) case "ABC": return themes.sort((a, b) => { return a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1; }) default: return shuffle(themes).sort((a, b) => { a = a.score + ""; b = b.score + ""; a = parseInt(a.charAt(0)) + parseInt(a.charAt(1)) + parseInt(a.charAt(2)) + parseInt(a.charAt(3)); b = parseInt(b.charAt(0)) + parseInt(b.charAt(1)) + parseInt(b.charAt(2)) + parseInt(b.charAt(3)); return b - a; }); } } let cache = {}; // new theme sort button function themeSort(sort) { current_sort = sort; current_page_num = 1; allThemes = themeSortFn(current_sort); displayThemeList(0); } function clickout() { setTimeout(() => { document.getElementById("theme-sort-selector").classList.remove("open"); document.removeEventListener("click", clickout); }, 10); } // shuffle function for the score sorting so theres no order bias function shuffle (arr) { var j, x, index; for (index = arr.length - 1; index > 0; index--) { j = Math.floor(Math.random() * (index + 1)); x = arr[index]; arr[index] = arr[j]; arr[j] = x; } return arr; } let current_page_num = 1; let maxPage = 0; let searchFor = ""; let current_sort = "Popular"; let allThemes = themeSortFn(current_sort); //sortThemes(current_sort); function shortScore(score) { if (score >= 1400) { return (Math.floor(score / 1000) + "." + Math.round((score % 1000) / 100)) + "k"; } return score; } let fallback = false; async function submitTheme() { //TODO: remake const sync = await chrome.storage.sync.get(null); // if (sync["new_browser"] !== true) { // displayAlert(true, "You'll need to opt in to the new browser if you want to submit your theme. If you've opted out and want to opt in, you can scroll down to the bottom of this page and opt back in."); // return; // } const theme = await getExport(sync, [ ...syncedSwitches, ...syncedSubOptions, "custom_cards", "card_colors", "dark_preset", "custom_font", "gradient_cards", "disable_color_overlay", ]); const title = document.getElementById("submit-title"); const credits = document.getElementById("submit-credits"); if (title.value === "") { displayAlert(true, "The title of your theme can't be empty"); return; } if (credits.value === "") { displayAlert(true, "The credits for your theme can't be empty"); return; } const body = JSON.stringify({ "identity": sync["id"], "title": title.value, "credits": credits.value, "theme": JSON.stringify(theme) }); fetch(`${apiurl}/api/themes/submit`, { // "method": "POST", "body": body, "headers": { "Content-Type": "application/json", }, }).then(res => res.json()) .then(data => { console.log(data); if (data.errors === false) { displayAlert(false, "Thanks for submitting your theme! I will try to approve it soon, but not every theme may be accepted."); document.getElementById("submit-popup").classList.remove("open"); } else { displayAlert(true, `Submission error: ${data.message} Please contact sandlerguy5@gmail.com if you believe this is incorrect.`); } }); } async function registerUser() { // TODO: remake try { let id; const sync = await chrome.storage.sync.get("id"); if (sync["id"] && sync["id"] !== "") { id = sync["id"] } else { const res = await fetch(`${apiurl}/api/register`); const data = await res.json(); id = data.id; } chrome.storage.sync.set({ "id": id }).then(async () => { // test to see if the id was set correctly // don't know why this is happening ?? const test = await chrome.storage.sync.get("id"); if (test["id"] === undefined || test["id"] === "") throw new Error(); // show the new browser chrome.storage.sync.set({ "new_browser": true }).then(() => { document.getElementById("opt-in").style.display = "none"; current_page_num = 1; displayThemeList(0); displayAlert(false, "Success! You should be able to see the new themes browser now. Enjoy!"); }); }).catch(e => { displayAlert(true, "There was an error connecting an ID to your account. Please try again, and if this error persists, contact sandlerguy5@gmail.com!"); }); } catch (e) { console.log(e); displayAlert(true, "There was an error opting in. Please contact sandlerguy5@gmail.com if this error persists!"); } } function saveCurrentTheme() { const allOptions = syncedSwitches.concat(syncedSubOptions).concat(["dark_preset", "custom_cards", "custom_font", "gpa_calc_bounds", "card_colors"]); chrome.storage.local.get("saved_themes", local => { chrome.storage.sync.get(allOptions, async sync => { let current = await getExport(sync, allOptions); let trimmed = { "disable_color_overlay": current["disable_color_overlay"], "gradient_cards": current["gradient_cards"], "dark_mode": current["dark_mode"], "dark_preset": current["dark_preset"], "custom_cards": current["custom_cards"], "card_colors": current["card_colors"] === null ? [current["dark_preset"]["links"]] : current["card_colors"], "custom_font": current["custom_font"], "better_todo": current["better_todo"], "todo_hide_feedback": current["todo_hide_feedback"], "todo_full_height": current["todo_full_height"], "todo_hr24": current["todo_hr24"], "todo_separate_scrollbar": current["todo_separate_scrollbar"], "num_todo_items": current["num_todo_items"], "hover_preview": current["hover_preview"], "better_sidebar": current["better_sidebar"], "sidebar_scale": current["sidebar_scale"], "imageSize": current["imageSize"], "cardRoundness": current["cardRoundness"], "cardSpacing": current["cardSpacing"], "cardWidth": current["cardWidth"], "cardHeight": current["cardHeight"], "customBackgroundLink": current["customBackgroundLink"], "customBackgroundScale": current["customBackgroundScale"], "customBackgroundDaily": current["customBackgroundDaily"], } const now = new Date(); local["saved_themes"][now.getTime()] = trimmed; chrome.storage.local.set({ "saved_themes": local["saved_themes"] }).then(() => { displaySavedThemes(); }); }); }); } async function displayThemeList(direction = 0) { // const sync = await chrome.storage.sync.get("new_browser"); // if (sync["new_browser"] === true && fallback === false) { // displayThemeListNew(direction); // } else { displayThemeListOld(direction); // } // remove the opt-in notice // if (sync["new_browser"] !== null && document.getElementById("opt-in")) document.getElementById("opt-in").style.display = "none"; } function createThemeButton(location, theme) { let themeBtn = makeElement("button", location, { "className": "theme-button clickable" }); themeBtn.classList.add("customization-button"); if (!themeBtn.style.background) themeBtn.style.backgroundImage = "linear-gradient(#00000070, #00000070), url(" + theme.preview + ")"; if (theme.title) makeElement("p", themeBtn, { "className": "theme-button-title clickable", "textContent": theme.title.replaceAll(" ", "") }); if (theme.credits) makeElement("p", themeBtn, { "className": "theme-button-creator clickable", "textContent": theme.credits }); return themeBtn; } function createThemeLikeBtn(location, initial, score, show) { const likeBtn = makeElement("div", location, {"className": "theme-button-like"}); if (initial === true) { likeBtn.classList.add("theme-liked"); score += 1; } const amount = makeElement("span", likeBtn, { "className": "theme-button-like-amount", "textContent": shortScore(score) }); if (show === true) amount.classList.add("showalways"); likeBtn.innerHTML += ``; return likeBtn; } let likeThemeTimeout = false; function setLikeTimeout() { if (likeThemeTimeout === true) return; likeThemeTimeout = true; setTimeout(() => { likeThemeTimeout = false; }, 1000); } async function likeTheme(location, code, score) { // TODO: remake if (likeThemeTimeout === true) return; const sync = await chrome.storage.sync.get("id"); const local = await chrome.storage.local.get("liked_themes"); const setLikeStatus = (direction) => { let output = local; if (direction === -1) { location.classList.remove("theme-liked"); location.querySelector(".theme-button-like-amount").textContent = shortScore(score); output = local["liked_themes"].filter(x => x !== code); } else if (direction === 1) { location.classList += (" theme-liked animate-like"); location.querySelector(".theme-button-like-amount").textContent = shortScore(score + 1); output = [...local["liked_themes"], code]; } return output; } // show the updated like status immediately setLikeStatus(location.classList.contains("theme-liked") ? -1 : 1); const res = await fetch(`${apiurl}/api/themes/theme/${code}/like`, { "method": "POST", "body": JSON.stringify({ "id": sync["id"] }), "headers": { "Content-Type": "application/json" }, }); const data = await res.json(); if (data.errors === false) { const direction = parseInt(data.message); // update the like status if there is some disagreement with the server const update = setLikeStatus(direction); chrome.storage.local.set({ "liked_themes": update }).then(setLikeTimeout); } else { setLikeTimeout(); } } async function getAndLoadTheme(code) { // todo: remake const key = `themes/${code}`; let output = {}; if (cache[key]) { output = cache[key]; console.log("got this theme from the cache."); } else { const res = await fetch(`${apiurl}/api/themes/theme/${code}`); const data = await res.json(); output = JSON.parse(data.message.exports); cache[key] = output; } importTheme(output); } async function displayThemeListNew(direction) { // TODO: remake document.getElementById("theme-current-sort").textContent = current_sort; if (direction === -1 && current_page_num > 1) current_page_num--; if (direction === 1 && current_page_num < maxPage) current_page_num++; let themes = []; let apiLink = `${current_sort.toLowerCase()}?page=${current_page_num}` + (searchFor === "" ? "" : `&searchFor=${searchFor}`); if (current_sort === "Liked") { const sync = await chrome.storage.sync.get("id"); const local = await chrome.storage.local.get("liked_themes"); if (sync["id"] && sync["id"] !== "") { apiLink += `&id=${sync["id"]}`; maxPage = Math.ceil(local["liked_themes"].length / 28); } else { // fallback if there is no id current_page_num = 1; apiLink = `popular?page=${current_page_num}` + (searchFor === "" ? "" : `&searchFor=${searchFor}`); } } // fetch api, fallback if necessary if (cache[apiLink]) { themes = cache[apiLink]["themes"]; maxPage = cache[apiLink]["pages"] || maxPage; } else { try { const res = await fetch(`${apiurl}/api/themes/${apiLink}`, { method: "get", headers: { "Content-Type": "application/json" }, }); const data = await res.json(); if (data.errors === true) throw new Error(data.message); themes = data.message.themes; cache[apiLink] = data.message; if (data?.message?.pages) { maxPage = data.message.pages; } } catch (e) { console.log(e); current_page_num = 1; fallback = true; displayAlert(true, "there is no server you should not be seeing this. There was a problem getting themes from the Canvas Refined server, so the old themes browser is being displayed for now."); displayThemeListOld(0); return; } } let container = document.getElementById("premade-themes"); container.textContent = ""; const local = await chrome.storage.local.get("liked_themes"); const sync = await chrome.storage.sync.get("browser_show_likes"); themes.forEach(theme => { const themeBtn = createThemeButton(container, theme); themeBtn.addEventListener("click", (e) => { if (!e.target.classList.contains("clickable")) return; // getAndLoadTheme(theme.code) }); const liked = local["liked_themes"].includes(theme.code); // TODO: remake const likeBtn = createThemeLikeBtn(themeBtn, liked, theme.score, sync["browser_show_likes"]); // likeBtn.addEventListener("click" , (e) => likeTheme(likeBtn, theme.code, theme.score)); }); if (themes.length === 0) { container.innerHTML = `
Hide
Links
No course cards found. Visit your Canvas dashboard to load courses.
'; return; } Object.keys(allCards).forEach(courseId => { const course = allCards[courseId]; if (course && typeof course === 'object') { const courseButton = createCourseButton(courseId, course); cardGrid.appendChild(courseButton); } }); const editMenu = document.getElementById("card-edit-menu"); if (editMenu) { editMenu.style.display = "none"; } }); sendFromPopup("getCards"); } function createCourseButton(courseId, courseData) { const button = document.createElement("button"); button.className = "course-card-button"; const displayName = courseData.name || courseData.default || courseData.code || `Course ${courseId}`; button.textContent = displayName; button.dataset.courseId = courseId; if (courseData.img || courseData.hidden || courseData.hide) { button.classList.add("customized"); } button.addEventListener("click", () => { document.querySelectorAll(".course-card-button").forEach(btn => btn.classList.remove("active")); button.classList.add("active"); showCardEditMenu(courseId, courseData); }); return button; } function showCardEditMenu(courseId, courseData) { const editMenu = document.getElementById("card-edit-menu"); const cardGrid = document.getElementById("card-grid"); if (cardGrid) cardGrid.style.display = "none"; editMenu.style.display = "block"; const displayName = courseData.name || courseData.default || courseData.code || `Course ${courseId}`; editMenu.innerHTML = `