mirror of
https://github.com/GuySandler/CanvasRefined.git
synced 2026-11-03 22:07:18 +01:00
Better multi-canvas, but failed to make split settings
This commit is contained in:
parent
902eb5b128
commit
a4a4db7fb9
@ -24,7 +24,19 @@
|
||||
"message": "Dark Mode"
|
||||
},
|
||||
"enter_url": {
|
||||
"message": "Your Canvas url:"
|
||||
"message": "Your Canvas URLs:"
|
||||
},
|
||||
"add_canvas": {
|
||||
"message": "+ Add canvas"
|
||||
},
|
||||
"no_auto_detect": {
|
||||
"message": "Don't auto detect canvases"
|
||||
},
|
||||
"add_to_list": {
|
||||
"message": "Add to list"
|
||||
},
|
||||
"no_thanks": {
|
||||
"message": "No thanks"
|
||||
},
|
||||
"card_grades": {
|
||||
"message": "Card Grades"
|
||||
|
||||
@ -158,6 +158,20 @@
|
||||
.canvasrefined-todo-item-header {max-height:80px;overflow:hidden;}
|
||||
.canvasrefined-todo-svg {fill:#2d3b45!important}
|
||||
#canvasrefined-update-msg {margin-top: 5px; display: flex; justify-content: space-between; font-size:15px;background: linear-gradient(115deg, rgb(255, 53, 88, .9), rgb(255, 131, 73, .9), rgb(255, 103, 187, .9)); color: #fff; border-radius:4px; padding: 20px;white-space: pre-line;}
|
||||
|
||||
/* Detected-canvas prompt — centered modal with backdrop so it can't be missed */
|
||||
#canvasrefined-domain-prompt-backdrop {position: fixed!important;inset: 0!important;width: 100%!important;height: 100%!important;background: rgb(0 0 0 / 60%)!important;z-index: 2147483647!important;display: flex!important;align-items: center!important;justify-content: center!important;}
|
||||
#canvasrefined-domain-prompt {background: #fff!important;border-radius: 14px!important;padding: 24px 28px!important;max-width: 460px!important;width: 90%!important;box-sizing: border-box!important;box-shadow: 0 12px 48px rgb(0 0 0 / 45%)!important;animation: .25s ease canvasrefined-pop;}
|
||||
@keyframes canvasrefined-pop {0% {transform: scale(.9);opacity: 0} 100% {transform: scale(1);opacity: 1}}
|
||||
#canvasrefined-domain-prompt h3 {margin: 0 0 10px!important;font-size: 19px!important;font-weight: 700!important;color: #2d3b45!important;font-family: inherit!important;}
|
||||
#canvasrefined-domain-prompt p {margin: 0 0 18px!important;font-size: 14px!important;color: #4a5460!important;line-height: 1.5!important;font-family: inherit!important;}
|
||||
.canvasrefined-domain-host {font-weight: 700!important;color: #2d3b45!important;word-break: break-all!important;}
|
||||
.canvasrefined-domain-prompt-actions {display: flex!important;gap: 10px!important;}
|
||||
.canvasrefined-domain-prompt-actions button {flex: 1!important;border: none!important;border-radius: 8px!important;padding: 11px 16px!important;font-size: 14px!important;font-weight: 600!important;cursor: pointer!important;font-family: inherit!important;}
|
||||
.canvasrefined-domain-yes {background: #2d3b45!important;color: #fff!important;}
|
||||
.canvasrefined-domain-yes:hover {background: #3b4d61!important;}
|
||||
.canvasrefined-domain-no {background: #eceef0!important;color: #2d3b45!important;}
|
||||
.canvasrefined-domain-no:hover {background: #dfe2e5!important;}
|
||||
#canvasrefined-update-msg p {margin: 0;color:#fff!important;}
|
||||
#canvasrefined-update-close { background: #2f2f2f59; padding: 3px 6px; border-radius: 3px; height:100%; border:none; color: #fff!important;}
|
||||
#canvasrefined-reminders {animation: .4s ease slide; margin:20px!important;position: fixed!important; bottom: 0!important;right: 0;z-index:10000000000!important;flex-direction: column!important; gap:10px!important;}
|
||||
|
||||
@ -34,6 +34,15 @@ a.option-container:hover {background: #333}
|
||||
.options-left {margin-right: 8px;}
|
||||
.options-left, .options-right {width: 50%;}
|
||||
.customDomain {font-weight:600}
|
||||
|
||||
/* Canvas URL list — one text box per school */
|
||||
#custom-domain-list {display: flex;flex-direction: column;gap: 6px;margin-top: 8px}
|
||||
.domain-row {display: flex;gap: 6px;align-items: center}
|
||||
.domain-row input {flex: 1;min-width: 0;box-sizing: border-box;font-size: 12px;background: var(--inputbg);border: none;padding: 8px;color: #e2e2e2;border-radius: 7px;font-family: inherit}
|
||||
.domain-row .domain-remove {flex: none;width: 24px;height: 31px;padding: 0;border: none;border-radius: 6px;background: #2f2f2f;color: #a7a7a7;font-size: 13px;line-height: 1;cursor: pointer}
|
||||
.domain-row .domain-remove:hover {background: #3d3d3d;color: #fff}
|
||||
#add-domain-btn {margin-top: 8px;font-size: 12px;padding: 5px 12px}
|
||||
.domain-toggles {margin-top: 10px;display: flex;flex-direction: column}
|
||||
.changer {margin:0;margin-bottom: 0px;width:100%}
|
||||
.option-background {color: #fff}
|
||||
.color-changer { font-family: inherit;font-size: 20px;background: none;border: none;color: #000;height: 30px;width:100%;padding:10px 0px 10px 10px;}
|
||||
|
||||
@ -440,9 +440,15 @@
|
||||
</div>
|
||||
<div class="option-container">
|
||||
<div class="customDomain">
|
||||
<span data-i18n="enter_url">Your Canvas URL:</span>
|
||||
<p style="font-size: 11px;color:#8d8d8d" data-i18n="enter_multiple">(Enter multiple with a comma)</p>
|
||||
<input type="text" id="customDomain" placeholder="Or just refresh your Canvas page!"></input>
|
||||
<span data-i18n="enter_url">Your Canvas URLs:</span>
|
||||
<div id="custom-domain-list"></div>
|
||||
<button id="add-domain-btn" class="customization-button" data-i18n="add_canvas">+ Add canvas</button>
|
||||
<div class="domain-toggles">
|
||||
<div class="sub-option">
|
||||
<input type="checkbox" id="auto_detect_disabled" name="auto_detect_disabled">
|
||||
<label for="auto_detect_disabled" class="sub-text" data-i18n="no_auto_detect">Don't auto detect canvases</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -32,6 +32,8 @@ chrome.runtime.onInstalled.addListener(function () {
|
||||
"auto_dark_end": { "hour": "08", "minute": "00" },
|
||||
"num_assignments": 4,
|
||||
"custom_domain": [""],
|
||||
"custom_domain_denied": [],
|
||||
"auto_detect_disabled": false,
|
||||
"assignments_done": [],
|
||||
"dashboard_grades": true,
|
||||
"assignment_date_format": false,
|
||||
|
||||
133
js/content.js
133
js/content.js
@ -757,20 +757,48 @@ function showExampleReminder() {
|
||||
}
|
||||
|
||||
|
||||
// Canvas detection: if the domain is in the user's list, start normally.
|
||||
// Otherwise probe it (unless detection was declined for it); an empty list
|
||||
// auto-adds the first canvas, and with canvases already in the list the user
|
||||
// is asked once per domain.
|
||||
isDomainCanvasPage();
|
||||
|
||||
function isDomainCanvasPage() {
|
||||
chrome.storage.sync.get(['custom_domain', 'dark_mode', 'dark_preset', 'device_dark', 'remind'], result => {
|
||||
chrome.storage.sync.get(['custom_domain', 'custom_domain_denied', 'auto_detect_disabled', 'dark_mode', 'dark_preset', 'device_dark', 'remind'], 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])) {
|
||||
const domains = (result.custom_domain || []).filter(d => d && d !== "");
|
||||
const denied = result.custom_domain_denied || [];
|
||||
if (domains.some(d => domain.includes(d))) {
|
||||
startExtension();
|
||||
return;
|
||||
}
|
||||
|
||||
// not in the list — no prompting/probing if the user turned auto detect
|
||||
// off, or already said no here; just run reminders
|
||||
if (result.auto_detect_disabled === true || denied.some(d => domain.includes(d))) {
|
||||
startReminderMode();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the code reaches this point, its not a canvas page so run the reminders
|
||||
detectCanvasPage().then(({ isCanvas, courses }) => {
|
||||
if (!isCanvas) {
|
||||
startReminderMode();
|
||||
return;
|
||||
}
|
||||
if (domains.length === 0) {
|
||||
// first canvas ever — add it automatically (original first-run
|
||||
// behavior) and reload so the extension starts
|
||||
addCanvasDomain(courses).then(() => location.reload());
|
||||
} else {
|
||||
// at least one canvas already in the list: ask the first time
|
||||
// this page is seen. Saying no means we never ask again here.
|
||||
promptAddCanvasDomain(courses);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function startReminderMode() {
|
||||
setTimeout(reminderWatch, 2000);
|
||||
setInterval(reminderWatch, 60000);
|
||||
// turn the reminders on/off if the option is changed
|
||||
@ -779,9 +807,81 @@ function isDomainCanvasPage() {
|
||||
if (key === "remind") reminderWatch();
|
||||
})
|
||||
})
|
||||
} else {
|
||||
setupCustomURL();
|
||||
}
|
||||
|
||||
// The courses API only answers on real Canvas installs. Resolves { isCanvas, courses }.
|
||||
async function detectCanvasPage() {
|
||||
try {
|
||||
const courses = await getData(`${domain}/api/v1/courses?${/*enrollment_state=active&*/""}per_page=100`);
|
||||
if (Array.isArray(courses) && courses.length) {
|
||||
return { isCanvas: true, courses };
|
||||
}
|
||||
console.log("Canvas Refined - this url doesn't seem to be a canvas url (1)");
|
||||
return { isCanvas: false, courses: null };
|
||||
} catch (err) {
|
||||
console.log("Canvas Refined - this url doesn't seem to be a canvas url (2)");
|
||||
return { isCanvas: false, courses: null };
|
||||
}
|
||||
}
|
||||
|
||||
// Register the domain in the canvas list. `courses` (from detectCanvasPage) is
|
||||
// passed to getCards so this school's courses are indexed before the reload,
|
||||
// exactly like the original first-run flow did.
|
||||
function addCanvasDomain(courses) {
|
||||
return getCards(courses).then(() => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
console.log("Canvas Refined - setting custom domain to " + domain);
|
||||
chrome.storage.sync.get(["custom_domain"], storage => {
|
||||
const domains = (storage.custom_domain || []).filter(d => d && d !== "");
|
||||
domains.push(domain);
|
||||
// the domain was just accepted — make sure it isn't on the
|
||||
// "don't ask again" list
|
||||
chrome.storage.sync.get(["custom_domain_denied"], deniedStorage => {
|
||||
const denied = (deniedStorage.custom_domain_denied || []).filter(d => !domain.includes(d));
|
||||
chrome.storage.sync.set({ custom_domain: domains, custom_domain_denied: denied }).then(resolve);
|
||||
});
|
||||
});
|
||||
}, 100);
|
||||
}));
|
||||
}
|
||||
|
||||
function promptAddCanvasDomain(courses) {
|
||||
// content scripts run at document_start, so the body may not exist yet
|
||||
const show = () => showCanvasPrompt(courses);
|
||||
if (document.body) show();
|
||||
else document.addEventListener("DOMContentLoaded", show, { once: true });
|
||||
}
|
||||
|
||||
function showCanvasPrompt(courses) {
|
||||
if (document.getElementById("canvasrefined-domain-prompt-backdrop")) return;
|
||||
|
||||
const host = domain.replace(/^https?:\/\//, "");
|
||||
// modal with a backdrop so the ask is impossible to miss
|
||||
const backdrop = makeElement("div", document.body, { "id": "canvasrefined-domain-prompt-backdrop" });
|
||||
const prompt = makeElement("div", backdrop, { "id": "canvasrefined-domain-prompt" });
|
||||
makeElement("h3", prompt, { "textContent": "New Canvas detected" });
|
||||
const text = makeElement("p", prompt, {});
|
||||
text.append("Canvas Refined detected a Canvas page at ");
|
||||
makeElement("span", text, { "className": "canvasrefined-domain-host", "textContent": host });
|
||||
text.append(". Add it to your canvas list?");
|
||||
const actions = makeElement("div", prompt, { "className": "canvasrefined-domain-prompt-actions" });
|
||||
const yes = makeElement("button", actions, { "textContent": chrome.i18n.getMessage("add_to_list") || "Add to list", "className": "canvasrefined-domain-yes" });
|
||||
const no = makeElement("button", actions, { "textContent": chrome.i18n.getMessage("no_thanks") || "No thanks", "className": "canvasrefined-domain-no" });
|
||||
|
||||
const dismiss = () => backdrop.remove();
|
||||
|
||||
yes.addEventListener("click", () => {
|
||||
dismiss();
|
||||
addCanvasDomain(courses).then(() => location.reload());
|
||||
});
|
||||
no.addEventListener("click", () => {
|
||||
dismiss();
|
||||
chrome.storage.sync.get(["custom_domain_denied"], storage => {
|
||||
const denied = storage.custom_domain_denied || [];
|
||||
if (!denied.some(d => domain.includes(d))) denied.push(domain);
|
||||
chrome.storage.sync.set({ custom_domain_denied: denied });
|
||||
});
|
||||
startReminderMode();
|
||||
});
|
||||
}
|
||||
|
||||
@ -7634,25 +7734,6 @@ function cleanCustomAssignments() {
|
||||
});
|
||||
}
|
||||
|
||||
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("Canvas Refined - setting custom domain to " + domain);
|
||||
chrome.storage.sync.set({ custom_domain: [domain] }).then(location.reload());
|
||||
}, 100);
|
||||
});
|
||||
} else {
|
||||
console.log("Canvas Refined - this url doesn't seem to be a canvas url (1)");
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("Canvas Refined - 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`);
|
||||
|
||||
106
js/popup.js
106
js/popup.js
@ -29,6 +29,7 @@ const syncedSubOptions = [
|
||||
"todo_show_scores",
|
||||
"grade_hover",
|
||||
"card_letter",
|
||||
"auto_detect_disabled",
|
||||
// "hide_completed",
|
||||
"num_todo_items",
|
||||
"hover_preview",
|
||||
@ -111,6 +112,8 @@ const defaultOptions = {
|
||||
"auto_dark_end": { "hour": "08", "minute": "00" },
|
||||
"num_assignments": 4,
|
||||
"custom_domain": [""],
|
||||
"custom_domain_denied": [],
|
||||
"auto_detect_disabled": false,
|
||||
"assignments_done": [],
|
||||
"dashboard_grades": false,
|
||||
"assignment_date_format": false,
|
||||
@ -876,8 +879,8 @@ function setupFeatureSearch(menu) {
|
||||
add({ key: "sub:" + keyIdOf(sub), text, el: sub, action: () => goToMainElement(sub) });
|
||||
});
|
||||
|
||||
// Home: custom Canvas URL
|
||||
const customDomain = document.querySelector("#customDomain");
|
||||
// Home: custom Canvas URLs
|
||||
const customDomain = document.querySelector(".customDomain");
|
||||
if (customDomain && !shouldSkip(customDomain)) {
|
||||
const wrap = customDomain.closest(".customDomain");
|
||||
const label = wrap ? wrap.querySelector("[data-i18n='enter_url']") : null;
|
||||
@ -1094,6 +1097,7 @@ function setup() {
|
||||
"todo_show_scores",
|
||||
"grade_hover",
|
||||
"card_letter",
|
||||
"auto_detect_disabled",
|
||||
// "hide_completed",
|
||||
"hover_preview",
|
||||
"customBackgroundDaily",
|
||||
@ -1491,28 +1495,94 @@ function setup() {
|
||||
}
|
||||
updateStorageUsage();
|
||||
|
||||
// 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;
|
||||
// Canvas URL list — each school gets its own text box
|
||||
const domainListEl = document.querySelector("#custom-domain-list");
|
||||
const addDomainBtn = document.querySelector("#add-domain-btn");
|
||||
|
||||
// Normalize a single entry: a full URL becomes its hostname, anything else
|
||||
// is kept as typed. Idempotent — rows store bare hostnames
|
||||
// ("canvas.school.edu"), which fail new URL() without a scheme, so they're
|
||||
// validated as hostnames instead of re-flagged as invalid on every save.
|
||||
function sanitizeDomainEntry(val, showAlert = true) {
|
||||
let trimmed = val.trim().replace(/ /g, "");
|
||||
if (trimmed === "") return "";
|
||||
try {
|
||||
let url = new URL(val);
|
||||
domains[index] = url.hostname;
|
||||
const host = new URL(trimmed).hostname;
|
||||
if (host) {
|
||||
clearAlert();
|
||||
} catch (e) {
|
||||
domains[index] = val;
|
||||
displayAlert(true, "The URL you entered appears to be invalid, so it might not work.");
|
||||
return host;
|
||||
}
|
||||
} catch (e) { /* not a full URL — may still be a bare hostname */ }
|
||||
// strip scheme/path if new URL couldn't parse it, then validate as a hostname
|
||||
const host = trimmed.replace(/^https?:\/\//i, "").replace(/\/.*$/, "");
|
||||
if (host && /^[a-z0-9][a-z0-9.-]*$/i.test(host)) {
|
||||
clearAlert();
|
||||
return host;
|
||||
}
|
||||
if (showAlert) displayAlert(true, "The URL you entered appears to be invalid, so it might not work.");
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function saveDomainList() {
|
||||
const domains = [];
|
||||
domainListEl.querySelectorAll(".domain-row input").forEach(input => {
|
||||
const val = sanitizeDomainEntry(input.value);
|
||||
if (val !== "") domains.push(val);
|
||||
});
|
||||
// keep the legacy [""]-when-empty shape the rest of the code expects
|
||||
chrome.storage.sync.set({ custom_domain: domains.length ? domains : [""] });
|
||||
}
|
||||
|
||||
function makeDomainRow(value) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "domain-row";
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.placeholder = "canvas.myschool.edu";
|
||||
input.value = value || "";
|
||||
input.spellcheck = false;
|
||||
input.addEventListener("input", saveDomainList);
|
||||
input.addEventListener("blur", () => {
|
||||
// normalize what's shown and drop rows left empty
|
||||
if (input.value.trim() === "") {
|
||||
if (domainListEl.querySelectorAll(".domain-row").length > 1) row.remove();
|
||||
} else {
|
||||
input.value = sanitizeDomainEntry(input.value);
|
||||
}
|
||||
saveDomainList();
|
||||
});
|
||||
const remove = document.createElement("button");
|
||||
remove.className = "domain-remove";
|
||||
remove.type = "button";
|
||||
remove.textContent = "\u2715";
|
||||
remove.title = "Remove";
|
||||
remove.addEventListener("click", () => {
|
||||
const val = sanitizeDomainEntry(input.value, false);
|
||||
row.remove();
|
||||
saveDomainList();
|
||||
// clear any past "no thanks" for this canvas so the detection
|
||||
// prompt can ask again if it's visited later
|
||||
if (val) {
|
||||
chrome.storage.sync.get(["custom_domain_denied"], storage => {
|
||||
const denied = (storage.custom_domain_denied || []).filter(d => d !== val);
|
||||
chrome.storage.sync.set({ custom_domain_denied: denied });
|
||||
});
|
||||
}
|
||||
});
|
||||
chrome.storage.sync.set({ custom_domain: domains });
|
||||
row.appendChild(input);
|
||||
row.appendChild(remove);
|
||||
return row;
|
||||
}
|
||||
|
||||
chrome.storage.sync.get(["custom_domain"], storage => {
|
||||
const domains = (storage.custom_domain || []).filter(d => d && d.trim() !== "");
|
||||
domains.forEach(d => domainListEl.appendChild(makeDomainRow(d)));
|
||||
});
|
||||
|
||||
// setup custom url
|
||||
chrome.storage.sync.get(["custom_domain"], storage => {
|
||||
document.querySelector("#customDomain").value = storage.custom_domain ? storage.custom_domain : "";
|
||||
addDomainBtn.addEventListener("click", () => {
|
||||
domainListEl.appendChild(makeDomainRow(""));
|
||||
const rows = domainListEl.querySelectorAll(".domain-row input");
|
||||
rows[rows.length - 1].focus();
|
||||
});
|
||||
|
||||
// activate import input box
|
||||
|
||||
Loading…
Reference in New Issue
Block a user