mirror of
https://github.com/GuySandler/CanvasRefined.git
synced 2026-09-23 05:15:10 +02:00
Compare commits
4 Commits
759deeed75
...
1a484e726b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a484e726b | ||
|
|
6c1bc9dc98 | ||
|
|
fcdfe41e84 | ||
|
|
cedb83d3d3 |
@ -60,13 +60,16 @@
|
||||
"message": "Quiz Safe Mode"
|
||||
},
|
||||
"quiz_safe_mode_desc": {
|
||||
"message": "Turns off most Canvas Refined features on quiz pages so you get the default Canvas quiz experience."
|
||||
"message": "Turns off most Canvas Refined features on quiz pages so you get the default Canvas quiz experience"
|
||||
},
|
||||
"opacity_options": {
|
||||
"message": "Opacity & Blur"
|
||||
},
|
||||
"opacity_only_background": {
|
||||
"message": "Only available with a custom background. Lower opacity to let the background show through; add blur for a glass effect."
|
||||
"message": "Only available with a custom background"
|
||||
},
|
||||
"card_transparency": {
|
||||
"message": "Card transparency"
|
||||
},
|
||||
"background_opacity": {
|
||||
"message": "Background opacity"
|
||||
@ -80,6 +83,12 @@
|
||||
"sidebar_blur": {
|
||||
"message": "Sidebar blur"
|
||||
},
|
||||
"card_opacity": {
|
||||
"message": "Card opacity"
|
||||
},
|
||||
"card_blur": {
|
||||
"message": "Card blur"
|
||||
},
|
||||
"sidebar_opacity_auto": {
|
||||
"message": "Applies to the Better Sidebar when it's on, otherwise the normal sidebar."
|
||||
},
|
||||
|
||||
@ -21,8 +21,8 @@ h2 {font-size:24px;}
|
||||
.header {font-size: 16px;display: flex;align-items: center;gap:12px; margin-bottom:14px;}
|
||||
.header h1 { font-weight: 600; font-size:24px; }
|
||||
#bclogo {height: 30px;width:30px;filter: none}
|
||||
#numAssignmentsSlider, #numTodoItemsSlider, #sidebarScaleSlider, #customBackgroundScale, #bgOpacitySlider, #sidebarOpacitySlider, #bgBlurSlider, #sidebarBlurSlider {display: block;margin-top: 14px;width: 100%;-webkit-appearance: none; appearance: none;background:var(--inputbg);outline: none;height:6px;border-radius:20px;}
|
||||
#numAssignmentsSlider::-webkit-slider-thumb, #numTodoItemsSlider::-webkit-slider-thumb, #sidebarScaleSlider::-webkit-slider-thumb, #customBackgroundScale::-webkit-slider-thumb, #bgOpacitySlider::-webkit-slider-thumb, #sidebarOpacitySlider::-webkit-slider-thumb, #bgBlurSlider::-webkit-slider-thumb, #sidebarBlurSlider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none;height: 18px; width: 18px;background: #727272;cursor: pointer;border-radius:30px;}
|
||||
#numAssignmentsSlider, #numTodoItemsSlider, #sidebarScaleSlider, #customBackgroundScale, #bgOpacitySlider, #sidebarOpacitySlider, #bgBlurSlider, #sidebarBlurSlider, #cardOpacitySlider, #cardBlurSlider {display: block;margin-top: 14px;width: 100%;-webkit-appearance: none; appearance: none;background:var(--inputbg);outline: none;height:6px;border-radius:20px;}
|
||||
#numAssignmentsSlider::-webkit-slider-thumb, #numTodoItemsSlider::-webkit-slider-thumb, #sidebarScaleSlider::-webkit-slider-thumb, #customBackgroundScale::-webkit-slider-thumb, #bgOpacitySlider::-webkit-slider-thumb, #sidebarOpacitySlider::-webkit-slider-thumb, #bgBlurSlider::-webkit-slider-thumb, #sidebarBlurSlider::-webkit-slider-thumb, #cardOpacitySlider::-webkit-slider-thumb, #cardBlurSlider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none;height: 18px; width: 18px;background: #727272;cursor: pointer;border-radius:30px;}
|
||||
.option-container {font-size: 14px; margin-top: 8px;background: var(--containerbg);padding: 18px;border-radius: 14px;box-sizing:border-box}
|
||||
.options .option-container {padding: 14px;}
|
||||
a.option-container {display: block;color:#5ca5f6;font-size: 14px;}
|
||||
@ -69,6 +69,10 @@ h2 {margin-top: 20px;font-weight:600}
|
||||
#todo_progress_rings option {background:#1e1e1e;color:#e2e2e2;}
|
||||
#todo_progress_rings option:checked,
|
||||
#todo_progress_rings option:hover {background:#2e2e2e;color:#fff;}
|
||||
#todo_timeframe {font-size: 12px;background: var(--inputbg);color:#e2e2e2;border:none;border-radius:7px;padding:6px 8px;font-family:inherit;width:100%;box-sizing:border-box;margin-top:2px;color-scheme:dark;}
|
||||
#todo_timeframe option {background:#1e1e1e;color:#e2e2e2;}
|
||||
#todo_timeframe option:checked,
|
||||
#todo_timeframe option:hover {background:#2e2e2e;color:#fff;}
|
||||
#advanced-settings, #gpa-bounds-btn, #custom-font-btn, #customize-dark-btn, #card-colors-btn {width: 100%;}
|
||||
.back-btn {width: 120px;height:100%;margin-top:0}
|
||||
.big-button {border: none;border-radius: 7px;background: var(--containerbg);color: #fff;font-family: inherit;padding: 12px 10px;font-weight:600}
|
||||
|
||||
@ -259,6 +259,16 @@
|
||||
<option value="line">One line</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sub-option" style="flex-direction:column;align-items:stretch;gap:4px;">
|
||||
<label for="todo_timeframe" class="sub-text">Time frame</label>
|
||||
<select id="todo_timeframe" name="todo_timeframe">
|
||||
<option value="all">All time</option>
|
||||
<option value="1week">1 week</option>
|
||||
<option value="2week">2 weeks</option>
|
||||
<option value="month">Month</option>
|
||||
<option value="2month">2 months</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sub-option">
|
||||
<input type="checkbox" id="todo_confetti" name="todo_confetti">
|
||||
<label for="todo_confetti" class="sub-text">Show confetti on complete</label>
|
||||
@ -582,7 +592,7 @@
|
||||
</div>
|
||||
<div class="sub-option">
|
||||
<input type="checkbox" id="export-background" />
|
||||
<span>Background image</span>
|
||||
<span>Background & opacity</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1">
|
||||
@ -841,6 +851,13 @@
|
||||
<span class="sub-text">px</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-option">
|
||||
<div style="margin-top: 5px;display: flex;gap: 8px;align-items: center">
|
||||
<span class="sub-text" style="width:75px;">Card Padding</span>
|
||||
<input type="number" id="cardPadding" class="card-input" min="0" max="100" value="0" style="width: 80px;">
|
||||
<span class="sub-text">px</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1041,6 +1058,10 @@
|
||||
<input type="checkbox" id="fitImageToScreen" name="fitImageToScreen">
|
||||
<label for="fitImageToScreen" class="sub-text">Fit image to screen</label>
|
||||
</div>
|
||||
<div class="sub-option" style="margin-top:8px;">
|
||||
<input type="checkbox" id="card_transparency" name="card_transparency">
|
||||
<label for="card_transparency" class="sub-text" data-i18n="card_transparency">Card transparency</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-container" id="opacity-options" style="display:none;">
|
||||
<h3 class="header-small" data-i18n="opacity_options">Opacity & Blur</h3>
|
||||
@ -1085,6 +1106,26 @@
|
||||
</div>
|
||||
<input type="range" min="0" max="30" step="1" id="sidebarBlurSlider">
|
||||
</div>
|
||||
<div class="opacity-slider-row" id="card-transparency-options" style="display:none;">
|
||||
<div style="display:flex;justify-content:space-between;gap:10px;align-items:center;margin-top:10px;">
|
||||
<span class="sub-text" style="font-weight:600;color:#e2e2e2;" data-i18n="card_opacity">Card opacity</span>
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<button class="opacity-reset-btn" id="cardOpacityReset" type="button" title="Reset to default" data-i18n-title="reset_default"><svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg></button>
|
||||
<span class="sub-text" id="cardOpacityValue">80%</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="range" min="0" max="100" step="1" id="cardOpacitySlider">
|
||||
</div>
|
||||
<div class="opacity-slider-row" id="card-transparency-blur-row" style="display:none;">
|
||||
<div style="display:flex;justify-content:space-between;gap:10px;align-items:center;margin-top:10px;">
|
||||
<span class="sub-text" style="font-weight:600;color:#e2e2e2;" data-i18n="card_blur">Card blur</span>
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<button class="opacity-reset-btn" id="cardBlurReset" type="button" title="Reset to default" data-i18n-title="reset_default"><svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg></button>
|
||||
<span class="sub-text" id="cardBlurValue">8px</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="range" min="0" max="30" step="1" id="cardBlurSlider">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
150
js/content.js
150
js/content.js
@ -407,6 +407,14 @@ let reminderCheck = null;
|
||||
let betterSidebarLoading = false;
|
||||
let dashboardReadyTimer = null;
|
||||
let sidebarReadyTimer = null;
|
||||
// Signature of the dashboard card set last time we ran the full setup pass.
|
||||
// The MutationObserver in checkDashboardReady fires on every childList change
|
||||
// in the document — including the ones our own setup pass (loadCardAssignments,
|
||||
// customizeCards, etc.) causes. Without a guard, that re-triggers the observer
|
||||
// and re-runs the whole pass every animation frame (an infinite reflow loop).
|
||||
// We only need to re-run when Canvas actually changes the dashboard cards, so
|
||||
// we skip the heavy pass whenever the card set is unchanged.
|
||||
let lastDashboardCardSignature = null;
|
||||
let sidebarBadgeObserver = null;
|
||||
let sidebarBadgeSyncTimer = null;
|
||||
let sidebarBadgeWatchRetries = 0;
|
||||
@ -663,6 +671,10 @@ function applyOptionsChanges(changes) {
|
||||
)
|
||||
setupCardAssignments();
|
||||
loadCardAssignments();
|
||||
// The card overflow fix in applyAestheticChanges() depends on
|
||||
// assignments_due, so re-run it when that option toggles to keep
|
||||
// the overflow rule in sync without a page reload.
|
||||
applyAestheticChanges();
|
||||
break;
|
||||
case "custom_assignments":
|
||||
case "assignment_date_format":
|
||||
@ -684,6 +696,7 @@ function applyOptionsChanges(changes) {
|
||||
case "todo_separate_scrollbar":
|
||||
case "num_todo_items":
|
||||
case "hover_preview":
|
||||
case "todo_timeframe":
|
||||
// case "todo_overdues":
|
||||
case "todo_hide_feedback":
|
||||
case "todo_full_height":
|
||||
@ -745,8 +758,14 @@ function applyOptionsChanges(changes) {
|
||||
case "cardSpacing":
|
||||
case "cardWidth":
|
||||
case "cardHeight":
|
||||
case "cardPadding":
|
||||
case "customCardStyles":
|
||||
applyAestheticChanges();
|
||||
// Coalesce rapid card-style edits (e.g. holding the arrow keys on a
|
||||
// number input) into a single applyAestheticChanges() call. Each
|
||||
// storage onChanged event would otherwise re-run the dashboard style
|
||||
// pass immediately; a ~150ms cooldown is barely noticeable but keeps
|
||||
// the page from thrashing while the user is still adjusting values.
|
||||
debouncedApplyAestheticChanges();
|
||||
break;
|
||||
case "customBackgroundLink":
|
||||
applyCustomBackground();
|
||||
@ -761,6 +780,11 @@ function applyOptionsChanges(changes) {
|
||||
break;
|
||||
case "sidebar_opacity":
|
||||
case "sidebar_blur":
|
||||
applyCustomBackground();
|
||||
break;
|
||||
case "card_transparency":
|
||||
case "card_opacity":
|
||||
case "card_blur":
|
||||
applyCustomBackground();
|
||||
break;
|
||||
case "better_todo":
|
||||
@ -886,6 +910,14 @@ async function applyCustomBackground() {
|
||||
// the previous dashboard-header glass look; sidebar defaults to none.
|
||||
const bgBlur = Math.max(0, Math.min(30, Number(options.bg_blur ?? 8)));
|
||||
const sidebarBlur = Math.max(0, Math.min(30, Number(options.sidebar_blur ?? 0)));
|
||||
// Card transparency mirrors the content-panel glass effect (bg_opacity/
|
||||
// bg_blur) but applies it to dashboard course cards (.ic-DashboardCard).
|
||||
// Only active when the user explicitly enables it, since transparent cards
|
||||
// over a busy background can hurt legibility.
|
||||
const cardTransparency = options.card_transparency === true;
|
||||
const cardOpacity = Math.max(0, Math.min(100, Number(options.card_opacity ?? 80)));
|
||||
const cardBlur = Math.max(0, Math.min(30, Number(options.card_blur ?? 8)));
|
||||
const cardTransparent = 100 - cardOpacity;
|
||||
style.textContent = `
|
||||
#wrapper {
|
||||
background-image: url(${backgroundUrl}) !important;
|
||||
@ -1134,7 +1166,11 @@ async function applyCustomBackground() {
|
||||
.canvasrefined-gpa-card,
|
||||
.canvasrefined-gpa,
|
||||
.ic-DashboardCard {
|
||||
background: var(--bcbackground-0) !important;
|
||||
${cardTransparency
|
||||
? `background: color-mix(in srgb, var(--bcbackground-0), transparent ${cardTransparent}%) !important;
|
||||
backdrop-filter: blur(${cardBlur}px) saturate(120%) !important;
|
||||
-webkit-backdrop-filter: blur(${cardBlur}px) saturate(120%) !important;`
|
||||
: `background: var(--bcbackground-0) !important;`}
|
||||
}
|
||||
tr.student_assignment.assignment_graded.editable > * {
|
||||
border:none!important
|
||||
@ -1195,15 +1231,46 @@ function checkDashboardReady() {
|
||||
const c = document.querySelector("#DashboardCard_Container");
|
||||
if (c) {
|
||||
let cards = document.querySelectorAll(".ic-DashboardCard");
|
||||
changeGradientCards();
|
||||
setupCardAssignments();
|
||||
loadCardAssignments();
|
||||
customizeCards(cards);
|
||||
insertGrades();
|
||||
loadDashboardNotes();
|
||||
setupGPACalc();
|
||||
showUpdateMsg();
|
||||
createNasaInfoOverlay();
|
||||
// Build a cheap signature of the current card set. The setup
|
||||
// pass below mutates the cards' internals (assignment rows,
|
||||
// grades, etc.) but never adds/removes the .ic-DashboardCard
|
||||
// elements themselves, so the signature stays stable across
|
||||
// our own mutations. It only changes when Canvas re-renders the
|
||||
// dashboard (cards added/removed/reordered/replaced). Skipping
|
||||
// when it's unchanged breaks the self-retriggering reflow loop.
|
||||
let signature = cards.length + "";
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
const link = cards[i].querySelector(".ic-DashboardCard__link");
|
||||
signature += "|" + (link ? link.getAttribute("href") : "");
|
||||
}
|
||||
// Canvas often re-renders the dashboard on a hard reload and
|
||||
// replaces the .ic-DashboardCard nodes with fresh ones that have
|
||||
// the same courses/links (so the signature is unchanged) but no
|
||||
// longer carry our .canvasrefined-card-assignment marker. The
|
||||
// signature guard alone would skip re-setup in that case, leaving
|
||||
// card assignments empty until a popup toggle forces a reload.
|
||||
// Re-run whenever any card is missing its marker too. This is safe
|
||||
// from the self-retriggering reflow loop: after the pass every
|
||||
// card has the marker, so our own subsequent mutation bursts skip.
|
||||
let missingMarker = false;
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
if (!cards[i].querySelector(".canvasrefined-card-assignment")) {
|
||||
missingMarker = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (signature !== lastDashboardCardSignature || missingMarker) {
|
||||
lastDashboardCardSignature = signature;
|
||||
changeGradientCards();
|
||||
setupCardAssignments();
|
||||
loadCardAssignments();
|
||||
customizeCards(cards);
|
||||
insertGrades();
|
||||
loadDashboardNotes();
|
||||
setupGPACalc();
|
||||
showUpdateMsg();
|
||||
createNasaInfoOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
const rightSide = document.querySelector("#right-side");
|
||||
@ -1501,6 +1568,17 @@ function updateIndicator(element) {
|
||||
}
|
||||
// better todo html
|
||||
betterTodoFilter = "tasks";
|
||||
// Timeframe filter for the upcoming Tasks tab. "all" = no limit; otherwise
|
||||
// items are limited to those due on/before now+range (which also keeps
|
||||
// overdue items). Only affects the Tasks (upcoming) tab; announcements and
|
||||
// completed are unaffected because their dates are in the past.
|
||||
let betterTodoTimeframe = "all";
|
||||
const BETTER_TODO_TIMEFRAME_DAYS = {
|
||||
"1week": 7,
|
||||
"2week": 14,
|
||||
"month": 30,
|
||||
"2month": 60,
|
||||
};
|
||||
// null = show every class; a string courseId = only that class's tasks.
|
||||
let betterTodoProgressFilter = null;
|
||||
let domContainers = {};
|
||||
@ -2633,6 +2711,15 @@ async function createTodoSections(location) {
|
||||
announcements = displayData.filter(item => item.plannable_type == "announcement");
|
||||
assignmentsDue = displayData.filter(item => (item.plannable_type == "assignment" || item.plannable_type == "planner_note") && !item.submissions?.submitted && !item.planner_override?.marked_complete);
|
||||
completed = displayData.filter(item => (item.plannable_type == "assignment" || item.plannable_type == "planner_note") && (item.submissions?.submitted || item.planner_override?.marked_complete));
|
||||
// The timeframe is a persisted Better Todo List sub-option set in the
|
||||
// popup. Read the current value each render so popup changes apply on
|
||||
// the next render. Keeps items due on/before now+range (overdue items
|
||||
// are before now, so they are kept too). Only the Tasks tab is affected.
|
||||
betterTodoTimeframe = (options.todo_timeframe && Object.prototype.hasOwnProperty.call(BETTER_TODO_TIMEFRAME_DAYS, options.todo_timeframe)) ? options.todo_timeframe : "all";
|
||||
if (betterTodoTimeframe !== "all") {
|
||||
const cutoff = Date.now() + (BETTER_TODO_TIMEFRAME_DAYS[betterTodoTimeframe] * 24 * 60 * 60 * 1000);
|
||||
assignmentsDue = assignmentsDue.filter(item => new Date(item.plannable_date).getTime() <= cutoff);
|
||||
}
|
||||
// console.log("assignments", assignmentsDue);
|
||||
// console.log("announcements", announcements);
|
||||
// console.log("completed", completed);
|
||||
@ -3013,7 +3100,7 @@ function populateAssignments(iscompleted = false) {
|
||||
</div>
|
||||
<div style="width:calc(100% - 40px);height:80%;display:flex;flex-direction:column;gap:5px;padding-left:2px;box-sizing:border-box;overflow:hidden;position:relative;">
|
||||
<div style="display:flex;flex-direction:column;gap:3px;">
|
||||
<span style="color:${courseColor};font-size:12px;margin-top:-2px;">${item.context_name}</span>
|
||||
<span style="color:${courseColor};font-size:12px;margin-top:-2px;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;box-sizing:border-box;padding-right:22px;">${item.context_name}</span>
|
||||
<a href="${taskHref}" style="color:inherit;text-decoration:none;font-weight:bold;text-overflow:ellipsis;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:-5px;">${item.plannable.title}</a>
|
||||
<span style="color:var(--bctext-0);font-size:12px;margin-top:-5px;">${convertToDueDate(item.plannable_date)}</span>
|
||||
</div>
|
||||
@ -3113,7 +3200,7 @@ function populateAnnouncements() {
|
||||
</div>
|
||||
<div style="width:calc(100% - 40px);height:80%;display:flex;flex-direction:column;gap:5px;padding-left:2px;box-sizing:border-box;overflow:hidden;">
|
||||
<div style="display:flex;flex-direction:column;gap:3px;">
|
||||
<span style="color:${courseColor};font-size:12px;margin-top:-2px;">${item.context_name}</span>
|
||||
<span style="color:${courseColor};font-size:12px;margin-top:-2px;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;box-sizing:border-box;padding-right:22px;">${item.context_name}</span>
|
||||
<a href="${domain + item.html_url}" style="color:inherit;text-decoration:none;font-weight:bold;text-overflow:ellipsis;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:-5px;">${item.plannable.title}</a>
|
||||
<span style="color:var(--bctext-0);font-size:12px;margin-top:-5px;">${convertToDueDate(item.plannable_date)}</span>
|
||||
</div>
|
||||
@ -5141,6 +5228,18 @@ function loadCustomFont() {
|
||||
Smaller features
|
||||
*/
|
||||
|
||||
// Debounced wrapper around applyAestheticChanges for card-style options that
|
||||
// can fire many storage onChanged events in quick succession (number inputs).
|
||||
// See the "cardPadding"/"imageSize"/etc. cases in applyOptionsChanges.
|
||||
let aestheticDebounceTimer = null;
|
||||
function debouncedApplyAestheticChanges(delay = 150) {
|
||||
if (aestheticDebounceTimer) clearTimeout(aestheticDebounceTimer);
|
||||
aestheticDebounceTimer = setTimeout(() => {
|
||||
aestheticDebounceTimer = null;
|
||||
applyAestheticChanges();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
function applyAestheticChanges() {
|
||||
// Quiz safe mode: don't inject custom layout/aesthetic CSS on quiz pages.
|
||||
if (quizSafeModeActive()) return;
|
||||
@ -5158,7 +5257,30 @@ function applyAestheticChanges() {
|
||||
if (options.cardRoundness !== undefined && options.cardRoundness !== 5) style.textContent += `.ic-DashboardCard {border-radius: ${options.cardRoundness}px!important;}`;
|
||||
if (options.cardSpacing !== undefined && options.cardSpacing !== 0) style.textContent += `.ic-DashboardCard {margin-right: ${options.cardSpacing / 2}px!important; margin-bottom: ${options.cardSpacing / 2}px!important;}`;
|
||||
if (options.cardWidth !== undefined && options.cardWidth !== 262) style.textContent += `.ic-DashboardCard {width: ${options.cardWidth}px!important;}`;
|
||||
if (options.cardHeight !== undefined && options.cardHeight !== 250) style.textContent += `.ic-DashboardCard {height: ${options.cardHeight}px!important;}`;
|
||||
// Always emit a fixed card height when custom card styles are on. The old
|
||||
// `!== 250` guard silently dropped the height rule when cardHeight matched
|
||||
// the default, which is exactly what happens after importing a theme that
|
||||
// carries the default cardHeight (250) — leaving cards content-sized.
|
||||
// Content-sized cards plus the dashboard reflow loop trigger Firefox scroll
|
||||
// anchoring to yank the viewport back up while scrolling. A fixed height
|
||||
// (even the default 250px) keeps layout stable.
|
||||
if (options.cardHeight !== undefined && options.cardHeight !== null && options.cardHeight !== "") {
|
||||
style.textContent += `.ic-DashboardCard {height: ${options.cardHeight}px!important;}`;
|
||||
// Canvas sets overflow:hidden on .ic-DashboardCard. With a fixed
|
||||
// height that clips the appended .canvasrefined-card-assignment area
|
||||
// (the assignment rows live at the bottom of the card), making the
|
||||
// .canvasrefined-assignment-link anchors unclickable for users with
|
||||
// custom card styles enabled. Allow overflow so those rows stay
|
||||
// visible and interactive when card assignments are shown.
|
||||
if (options.assignments_due === true) style.textContent += `.ic-DashboardCard {overflow: visible!important;}`;
|
||||
}
|
||||
// Inner card padding. Applied to the whole .ic-DashboardCard box (the
|
||||
// element that holds the hero header, title, and action buttons) so the
|
||||
// colored hero header and its content all get consistent breathing room
|
||||
// from the card's edges. Guarded by !== 0 (default).
|
||||
if (options.cardPadding !== undefined && Number(options.cardPadding) > 0) {
|
||||
style.textContent += `.ic-DashboardCard {padding: ${options.cardPadding}px!important;}`;
|
||||
}
|
||||
}
|
||||
|
||||
style.textContent += ".ic-app-nav-toggle-and-crumbs{display:none!important}";
|
||||
|
||||
123
js/popup.js
123
js/popup.js
@ -4,6 +4,7 @@ const syncedSubOptions = [
|
||||
"todo_full_height",
|
||||
"todo_confetti",
|
||||
"todo_progress_rings",
|
||||
"todo_timeframe",
|
||||
"device_dark",
|
||||
"relative_dues",
|
||||
"card_overdues",
|
||||
@ -30,6 +31,7 @@ const syncedSubOptions = [
|
||||
"cardSpacing",
|
||||
"cardWidth",
|
||||
"cardHeight",
|
||||
"cardPadding",
|
||||
"customBackgroundLink",
|
||||
"customBackgroundScale",
|
||||
"customBackgroundDaily",
|
||||
@ -40,18 +42,20 @@ const syncedSubOptions = [
|
||||
"sidebar_opacity",
|
||||
"bg_blur",
|
||||
"sidebar_blur",
|
||||
"card_opacity",
|
||||
"card_blur",
|
||||
];
|
||||
const localSwitches = [];
|
||||
|
||||
// Theme export only carries visual settings, never personal productivity data.
|
||||
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"];
|
||||
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 exportSidebar = ["better_sidebar", "sidebar_scale"];
|
||||
const exportTodo = ["better_todo", "todo_hide_feedback", "todo_full_height", "todo_confetti", "todo_progress_rings", "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", "hover_preview"];
|
||||
const exportGpa = ["gpa_calc", "gpa_calc_prepend", "gpa_calc_cumulative", "gpa_calc_weighted"];
|
||||
const exportBackground = ["customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "customBackgroundNasaDaily", "fitImageToScreen", "bg_opacity", "sidebar_opacity", "bg_blur", "sidebar_blur"];
|
||||
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,
|
||||
// no personal productivity features).
|
||||
const exportToggles = ["dark_mode", "quiz_safe_mode"].concat(exportCardColorToggles, exportLayout, exportSidebar, exportTodo);
|
||||
@ -102,6 +106,9 @@ const defaultOptions = {
|
||||
"sidebar_opacity": 100,
|
||||
"bg_blur": 8,
|
||||
"sidebar_blur": 0,
|
||||
"card_transparency": false,
|
||||
"card_opacity": 80,
|
||||
"card_blur": 8,
|
||||
"todo_hr24": false,
|
||||
"todo_separate_scrollbar": false,
|
||||
"todo_alternate_colors": false,
|
||||
@ -147,6 +154,7 @@ const defaultOptions = {
|
||||
"todo_hide_feedback": false,
|
||||
"todo_full_height": false,
|
||||
"todo_progress_rings": "rings",
|
||||
"todo_timeframe": "all",
|
||||
"todo_confetti": true,
|
||||
"device_dark": false,
|
||||
"cumulative_gpa": { "name": "Cumulative GPA", "hidden": false, "weight": "dnc", "credits": 999, "gr": 3.21 },
|
||||
@ -159,6 +167,7 @@ const defaultOptions = {
|
||||
"cardSpacing": 0,
|
||||
"cardWidth": 262,
|
||||
"cardHeight": 250,
|
||||
"cardPadding": 0,
|
||||
"customCardStyles": false,
|
||||
"customBackgroundLink": "",
|
||||
"customBackgroundScale": 100,
|
||||
@ -308,6 +317,19 @@ function setupProgressRingsSelect(initial) {
|
||||
});
|
||||
}
|
||||
|
||||
// Timeframe dropdown for the upcoming Tasks tab. Persisted so the Better Todo
|
||||
// List remembers the selected range across sessions.
|
||||
function setupTimeframeSelect(initial) {
|
||||
const el = document.querySelector("#todo_timeframe");
|
||||
if (!el) return;
|
||||
const allowed = ["all", "1week", "2week", "month", "2month"];
|
||||
let value = allowed.includes(initial) ? initial : "all";
|
||||
el.value = value;
|
||||
el.addEventListener("change", function () {
|
||||
chrome.storage.sync.set({ "todo_timeframe": this.value });
|
||||
});
|
||||
}
|
||||
|
||||
function setupAutoDarkInput(initial, time) {
|
||||
let el = document.querySelector('#' + time);
|
||||
el.value = initial.hour + ":" + initial.minute;
|
||||
@ -339,11 +361,26 @@ function setupDashboardMethod(initial) {
|
||||
});
|
||||
}
|
||||
|
||||
// Custom card style inputs (image size, roundness, spacing, width, height,
|
||||
// padding) fire `chrome.storage.sync.set` on every keystroke/arrow press.
|
||||
// chrome.storage.sync caps writes at MAX_WRITE_OPERATIONS_PER_MINUTE (120/min),
|
||||
// so rapidly adjusting these number inputs used to hit the quota and surface
|
||||
// a "quota exceeded" error. Coalesce rapid edits into a single write with a
|
||||
// short (200ms) debounce — barely noticeable to the user, but stays well
|
||||
// under the write quota even when dragging arrows or typing fast.
|
||||
const cardStyleSetTimers = {};
|
||||
function debouncedCardStyleSet(key, value, delay = 200) {
|
||||
if (cardStyleSetTimers[key]) clearTimeout(cardStyleSetTimers[key]);
|
||||
cardStyleSetTimers[key] = setTimeout(() => {
|
||||
chrome.storage.sync.set({ [key]: value });
|
||||
}, delay);
|
||||
}
|
||||
|
||||
function setupImageSizeInput(initial) {
|
||||
let el = document.querySelector("#imageSize");
|
||||
el.value = initial;
|
||||
el.addEventListener("input", (e) => {
|
||||
chrome.storage.sync.set({ "imageSize": e.target.value });
|
||||
debouncedCardStyleSet("imageSize", e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
@ -351,7 +388,7 @@ function setupCardRoundnessInput(initial) {
|
||||
let el = document.querySelector("#cardRoundness");
|
||||
el.value = initial;
|
||||
el.addEventListener("input", (e) => {
|
||||
chrome.storage.sync.set({ "cardRoundness": e.target.value });
|
||||
debouncedCardStyleSet("cardRoundness", e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
@ -359,7 +396,7 @@ function setupCardSpacingInput(initial) {
|
||||
let el = document.querySelector("#cardSpacing");
|
||||
el.value = initial;
|
||||
el.addEventListener("input", (e) => {
|
||||
chrome.storage.sync.set({ "cardSpacing": e.target.value });
|
||||
debouncedCardStyleSet("cardSpacing", e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
@ -367,7 +404,7 @@ function setupCardWidthInput(initial) {
|
||||
let el = document.querySelector("#cardWidth");
|
||||
el.value = initial;
|
||||
el.addEventListener("input", (e) => {
|
||||
chrome.storage.sync.set({ "cardWidth": e.target.value });
|
||||
debouncedCardStyleSet("cardWidth", e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
@ -375,10 +412,18 @@ function setupCardHeightInput(initial) {
|
||||
let el = document.querySelector("#cardHeight");
|
||||
el.value = initial;
|
||||
el.addEventListener("input", (e) => {
|
||||
chrome.storage.sync.set({ "cardHeight": e.target.value });
|
||||
debouncedCardStyleSet("cardHeight", e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
function setupCardPaddingInput(initial) {
|
||||
let el = document.querySelector("#cardPadding");
|
||||
el.value = initial;
|
||||
el.addEventListener("input", (e) => {
|
||||
debouncedCardStyleSet("cardPadding", e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
function setupCustomBackgroundLink(initial) {
|
||||
let el = document.querySelector("#customBackgroundLink");
|
||||
el.value = initial || "";
|
||||
@ -499,6 +544,17 @@ function customBackgroundActive() {
|
||||
function toggleOpacityOptions() {
|
||||
const el = document.getElementById("opacity-options");
|
||||
if (el) el.style.display = customBackgroundActive() ? "" : "none";
|
||||
toggleCardTransparencyOptions();
|
||||
}
|
||||
|
||||
// Card opacity/blur sliders only show when both a custom background is active
|
||||
// (so #opacity-options is visible) and the "Card transparency" checkbox is on.
|
||||
function toggleCardTransparencyOptions() {
|
||||
const on = document.getElementById("card_transparency")?.checked === true;
|
||||
const opacityRow = document.getElementById("card-transparency-options");
|
||||
const blurRow = document.getElementById("card-transparency-blur-row");
|
||||
if (opacityRow) opacityRow.style.display = on ? "" : "none";
|
||||
if (blurRow) blurRow.style.display = on ? "" : "none";
|
||||
}
|
||||
|
||||
// Hide the standalone feedback toggle when Better Todo's own sub-option replaces it.
|
||||
@ -850,6 +906,7 @@ function setup() {
|
||||
"customBackgroundDaily",
|
||||
"customBackgroundNasaDaily",
|
||||
"fitImageToScreen",
|
||||
"card_transparency",
|
||||
"customCardStyles",
|
||||
],
|
||||
tabs: {
|
||||
@ -918,6 +975,14 @@ function setup() {
|
||||
{
|
||||
identifier: "sidebar_blur",
|
||||
setup: (initial) => setupRangeSlider("sidebar_blur", "sidebarBlurSlider", "sidebarBlurValue", "sidebarBlurReset", 0, 30, "px", initial),
|
||||
},
|
||||
{
|
||||
identifier: "card_opacity",
|
||||
setup: (initial) => setupRangeSlider("card_opacity", "cardOpacitySlider", "cardOpacityValue", "cardOpacityReset", 80, 100, "%", initial),
|
||||
},
|
||||
{
|
||||
identifier: "card_blur",
|
||||
setup: (initial) => setupRangeSlider("card_blur", "cardBlurSlider", "cardBlurValue", "cardBlurReset", 8, 30, "px", initial),
|
||||
},
|
||||
{
|
||||
identifier: "card_limit",
|
||||
@ -951,6 +1016,10 @@ function setup() {
|
||||
identifier: "cardHeight",
|
||||
setup: (initial) => setupCardHeightInput(initial),
|
||||
},
|
||||
{
|
||||
identifier: "cardPadding",
|
||||
setup: (initial) => setupCardPaddingInput(initial),
|
||||
},
|
||||
{
|
||||
identifier: "customBackgroundLink",
|
||||
setup: (initial) => setupCustomBackgroundLink(initial),
|
||||
@ -963,6 +1032,10 @@ function setup() {
|
||||
identifier: "todo_progress_rings",
|
||||
setup: (initial) => setupProgressRingsSelect(initial),
|
||||
},
|
||||
{
|
||||
identifier: "todo_timeframe",
|
||||
setup: (initial) => setupTimeframeSelect(initial),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -1426,32 +1499,13 @@ function setup() {
|
||||
"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 + "%";
|
||||
});
|
||||
|
||||
// Note: the card-style number inputs (imageSize, cardRoundness, cardSpacing,
|
||||
// cardWidth, cardHeight, cardPadding) are wired via their setup*Input
|
||||
// handlers in menu.special above, which debounce the storage writes. The
|
||||
// duplicate listeners that used to live here referenced nonexistent
|
||||
// #*Value spans (threw on every input) and double-wrote to storage, which
|
||||
// is what blew the sync write quota when adjusting card styles quickly.
|
||||
|
||||
document.getElementById("clearCustomBackground").addEventListener("click", () => {
|
||||
chrome.storage.sync.set({ "customBackgroundLink": "", "customBackgroundScale": 100 });
|
||||
@ -1658,6 +1712,7 @@ function saveCurrentTheme() {
|
||||
"todo_full_height": current["todo_full_height"],
|
||||
"todo_confetti": current["todo_confetti"],
|
||||
"todo_progress_rings": current["todo_progress_rings"],
|
||||
"todo_timeframe": current["todo_timeframe"],
|
||||
"todo_hr24": current["todo_hr24"],
|
||||
"todo_separate_scrollbar": current["todo_separate_scrollbar"],
|
||||
"better_sidebar": current["better_sidebar"],
|
||||
|
||||
53638
lighthouseReport.json
53638
lighthouseReport.json
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user