Compare commits

...

4 Commits

Author SHA1 Message Date
Guy Sandler
0325b38a82 cleaned up old stuff
unused stuff from bettercanvas live service and such
2026-08-13 21:09:53 -07:00
Guy Sandler
cfd8a60f84 cleaning up 2026-08-13 20:17:14 -07:00
Guy Sandler
37dc65f488 md in notes 2026-08-13 20:16:50 -07:00
Guy Sandler
2ac702887f some daily background fixes 2026-08-13 16:13:08 -07:00
12 changed files with 877 additions and 1099 deletions

10
.vscode/settings.json vendored
View File

@ -1,10 +0,0 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/.DS_Store": true,
"**/Thumbs.db": true
},
"hide-files.files": []
}

View File

@ -56,11 +56,18 @@ Canvas Refined adds more with more to come!
- Searching themes (the original didn't actually impliment that)
- made the dark mode into a css file instead of a reallllllly long string
- Card Styles (image size, card roundness, card spacing, width, height, theme compatible)
- Custom Background (by URL, theme compatible)
- Custom Background
- Use presets or add your own by URL
- Theme compatible
- Daily backgrounds
- Popup UI revamp
- NEW Better todo list
- Better sidebar
- Simplified UI
- Some options won't show up when not needed
- Markdown in dashboard notes
- Smaller additions:
- Equal height cards for card assignments
## Planned Features (by priority)
- auto rotate theme + theme history + fix theme submissions

View File

@ -2,6 +2,9 @@
"Canvas_Refined": {
"message": "Canvas Refined"
},
"search_features": {
"message": "Search features..."
},
"updates": {
"message": "Updates"
},

View File

@ -1,78 +0,0 @@
import os
def format_minified_css(minified_css):
formatted = []
indent_level = 0
in_string = False
string_char = ''
# Clean up any existing odd spacing
minified_css = minified_css.replace('\n', '').replace('\r', '').replace('\t', '')
i = 0
while i < len(minified_css):
char = minified_css[i]
# Handle strings to avoid formatting inside content: "" or urls
if in_string:
formatted.append(char)
if char == string_char and minified_css[i-1] != '\\':
in_string = False
elif char in ('"', "'"):
in_string = True
string_char = char
formatted.append(char)
elif char == '{':
indent_level += 1
formatted.append(' {\n' + (' ' * indent_level))
elif char == '}':
indent_level = max(0, indent_level - 1)
# Clean up trailing indents from empty blocks
if formatted[-1].endswith(' '):
formatted[-1] = formatted[-1][:-4]
if not formatted[-1].endswith('\n'):
formatted.append('\n')
formatted.append((' ' * indent_level) + '}\n\n')
elif char == ';':
formatted.append(';\n' + (' ' * indent_level))
elif char == ',':
if indent_level == 0:
# Break long comma-separated selectors onto new lines
formatted.append(',\n')
else:
formatted.append(', ')
else:
# Skip extra spaces at the start of a newly indented line
if char == ' ' and (not formatted or formatted[-1].endswith(' ') or formatted[-1].endswith('\n')):
pass
else:
formatted.append(char)
i += 1
return "".join(formatted)
if __name__ == "__main__":
input_file = "./css/darkmodecss.js"
output_file = "./css/darkmodecss_formatted.js"
print(f"Reading from {input_file}...")
if not os.path.exists(input_file):
print(f"Error: Create an '{input_file}' file in this directory and paste your minified CSS inside it.")
exit(1)
with open(input_file, "r", encoding="utf-8") as f:
minified = f.read()
formatted = format_minified_css(minified)
with open(output_file, "w", encoding="utf-8") as f:
f.write(formatted)
print(f"Done! Formatted CSS saved to {output_file}")

View File

@ -22,7 +22,7 @@
.canvasrefined-export-output {position: fixed;height:100vh;width:100vw;top:0;left:0;z-index:10000;background:#000000c7;display:flex;align-items:center;justify-content:center}
.canvasrefined-export-copy { font-size:12px;white-space: pre-wrap;max-height:80vh;overflow:auto;padding:8px}
.canvasrefined-export-output-inner {width: 50%;background:#000;color:#fff;}
.canvasrefined-dashboard-notes {width: 100%; box-sizing: border-box; overflow: hidden; resize: none;}
.canvasrefined-dashboard-notes {width:100%;box-sizing:border-box;margin:18px 0 14px;border:1px solid var(--bcborders,#c7cdd1);border-radius:4px;overflow:hidden;background:var(--bcbackground-1,#fff);}
.canvasrefined-card-container {padding-bottom: 4px;}
.canvasrefined-assignment-container {margin: 0;padding:0;color: var(--ic-brand-font-color-dark-lightened-30);display: flex;justify-content:space-between;font-size:14px;align-items: center;transition:.2s all;}
.canvasrefined-card-header-container {align-items: center;display: flex;justify-content: space-between;padding: 0 18px;color:var(--ic-brand-font-color-dark-lightened-30);font-size:16px;font-weight:700;margin: 0;}
@ -97,7 +97,7 @@
.canvasrefined-gpa-courses {min-width:600px;width:max-content;}
.canvasrefined-gpa-course-top, .canvasrefined-gpa-course-bottom {display: flex;}
.canvasrefined-gpa-header {font-weight:700;font-size:16px;margin-top:0;margin-bottom:6px;padding-bottom:6px;}
.canvasrefined-dashboard-notes {width: 100%; box-sizing: border-box; margin-top: 18px;border: 1px solid #c7cdd1;border-radius:3px;padding:8px 14px;resize:vertical}
.canvasrefined-notes-editor {box-sizing:border-box;width:100%;min-height:140px;border:none;background:transparent;resize:vertical;outline:none;font-family:inherit;font-size:14px;line-height:1.5;padding:12px 16px;color:var(--bctext-0,var(--ic-brand-font-color-dark-lightened-30,#2d3b45));}
.canvasrefined-todosidebar, #canvasrefined-todo-list, #canvasrefined-announcement-list {margin: 0}
.canvasrefined-todo-container {display: flex; margin-top: 14px;position: relative;transition: .2s all;}
.canvasrefined-removing {max-height: 0;margin-top: 0}
@ -176,4 +176,42 @@
50% {
opacity: .5;
}
}
}
/* ===== Dashboard notes (Markdown) ===== */
.canvasrefined-notes-toolbar {display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;align-items:center;background:var(--bcbackground-2,#f5f7f8);border-bottom:1px solid var(--bcborders,#c7cdd1);}
.cr-fmt {border:1px solid transparent;background:transparent;color:var(--bctext-1,#5b6770);font:inherit;font-size:13px;line-height:1;padding:5px 7px;border-radius:4px;cursor:pointer;}
.cr-fmt:hover {background:var(--bcbackground-1,rgba(0,0,0,.06));color:var(--bctext-0,#2d3b45);}
.cr-fmt:active {background:var(--bcbackground-1,rgba(0,0,0,.12));}
.cr-fmt code {font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;}
.cr-fmt-sep {width:1px;height:18px;align-self:center;background:var(--bcborders,#c7cdd1);margin:0 3px;}
.canvasrefined-notes-surface {position:relative;background:var(--bcbackground-1,#fff);}
.canvasrefined-notes-rendered {box-sizing:border-box;width:100%;min-height:140px;padding:12px 16px;font-size:14px;line-height:1.5;color:var(--bctext-0,var(--ic-brand-font-color-dark-lightened-30,#2d3b45));overflow-wrap:break-word;cursor:text;outline:none;border-radius:0 0 4px 4px;}
.canvasrefined-notes-rendered:hover {background:var(--bcbackground-2,rgba(0,0,0,.02));}
.canvasrefined-notes-rendered:empty::before {content:"Click to add a note…";color:var(--bctext-2,#9aa3ab);font-style:italic;}
.canvasrefined-notes-rendered > :first-child {margin-top:0;}
.canvasrefined-notes-rendered > :last-child {margin-bottom:0;}
.canvasrefined-notes-rendered h1,.canvasrefined-notes-rendered h2,.canvasrefined-notes-rendered h3,.canvasrefined-notes-rendered h4,.canvasrefined-notes-rendered h5,.canvasrefined-notes-rendered h6 {margin:14px 0 6px;font-weight:600;line-height:1.3;color:var(--bctext-0,#1f2d35);}
.canvasrefined-notes-rendered h1 {font-size:1.5em;}
.canvasrefined-notes-rendered h2 {font-size:1.3em;}
.canvasrefined-notes-rendered h3 {font-size:1.15em;}
.canvasrefined-notes-rendered h4 {font-size:1em;}
.canvasrefined-notes-rendered p {margin:8px 0;}
.canvasrefined-notes-rendered ul,.canvasrefined-notes-rendered ol {margin:8px 0;padding-left:24px;}
.canvasrefined-notes-rendered li {margin:3px 0;}
.canvasrefined-notes-rendered li.cr-task {list-style:none;margin-left:-18px;display:flex;align-items:flex-start;gap:6px;}
.canvasrefined-notes-rendered li.cr-task input {margin-top:4px;}
.canvasrefined-notes-rendered blockquote {margin:8px 0;padding:4px 12px;border-left:3px solid var(--bcborders,#c7cdd1);color:var(--bctext-1,#5b6770);background:var(--bcbackground-2,rgba(0,0,0,.03));}
.canvasrefined-notes-rendered code {font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em;background:var(--bcbackground-2,#f1f3f5);padding:1px 5px;border-radius:3px;}
.canvasrefined-notes-rendered pre {margin:10px 0;padding:10px 12px;background:var(--bcbackground-2,#f1f3f5);border:1px solid var(--bcborders,#e2e6e8);border-radius:4px;overflow:auto;}
.canvasrefined-notes-rendered pre code {background:transparent;padding:0;}
.canvasrefined-notes-rendered a {color:var(--bclinks,var(--ic-link-color,#0072b5));text-decoration:underline;}
.canvasrefined-notes-rendered a:hover {text-decoration:none;}
.canvasrefined-notes-rendered img {max-width:100%;height:auto;border-radius:3px;}
.canvasrefined-notes-rendered hr {border:none;border-top:1px solid var(--bcborders,#c7cdd1);margin:14px 0;}
.canvasrefined-notes-rendered strong {font-weight:600;}
.canvasrefined-notes-rendered del {color:var(--bctext-2,#9aa3ab);}
.canvasrefined-notes-editor::placeholder {color:var(--bctext-2,#9aa3ab);}
.canvasrefined-dashboard-notes:not(.is-editing) .canvasrefined-notes-toolbar {display:none;}
.canvasrefined-dashboard-notes.is-editing .canvasrefined-notes-rendered {display:none;}
.canvasrefined-dashboard-notes:not(.is-editing) .canvasrefined-notes-editor {display:none;}

View File

@ -29,7 +29,7 @@ a.option-container {display: block;color:#5ca5f6;font-size: 14px;}
a.option-container:hover {background: #333}
#customDomain {font-size: 12px; background: var(--inputbg); border: none;padding:8px;color: #e2e2e2;margin-top: 6px;border-radius: 7px; font-family: inherit;}
#customDomain {width: 100%; box-sizing: border-box}
#auto_dark_start, #auto_dark_end, #scheduledReminderTime {padding: 3px 5px; font-size:12px;background: var(--inputbg);color:#e2e2e2;border:none;border-radius: 7px;}
#auto_dark_start, #auto_dark_end {padding: 3px 5px; font-size:12px;background: var(--inputbg);color:#e2e2e2;border:none;border-radius: 7px;}
.options-left {margin-right: 8px;}
.options-left, .options-right {width: 50%;}
.customDomain {font-weight:600}
@ -252,4 +252,21 @@ input[type="checkbox"]:checked {background: #8dd28d;}
100% {
opacity: 1;
}
}
}
/* ===== Header feature search ===== */
.header-search { position: relative; margin-left: auto; display: flex; align-items: center; gap: 8px; width: 170px; max-width: 42%; background: var(--inputbg); border-radius: 8px; padding: 7px 10px; box-sizing: border-box; }
.header-search-icon { color: #9a9a9a; flex: none; }
.header-search:focus-within { box-shadow: 0 0 0 1.5px #56Caf0; }
#feature-search { flex: 1; min-width: 0; background: none; border: none; outline: none; color: #e2e2e2; font-family: inherit; font-size: 13px; font-weight: 600; }
#feature-search::placeholder { color: #8d8d8d; font-weight: 500; }
.search-results { position: absolute; top: calc(100% + 6px); right: 0; min-width: 240px; max-width: 320px; max-height: 320px; overflow-y: auto; background: #0e0e0ee0; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); border-radius: 8px; padding: 5px; z-index: 1000; display: none; box-shadow: 0 8px 24px rgba(0,0,0,.45); }
.search-results.open { display: block; }
.search-result { display: flex; flex-direction: column; gap: 1px; padding: 8px 10px; border-radius: 6px; cursor: pointer; }
.search-result.active, .search-result:hover { background: #ffffff14; }
.search-result-title { font-size: 13px; font-weight: 600; color: #f0f0f0; }
.search-result-category { font-size: 11px; color: #9a9a9a; }
.search-result.empty { text-align: center; color: #8d8d8d; font-size: 12px; cursor: default; padding: 12px; }
.search-result.empty:hover { background: none; }
.search-highlight { outline: 2px solid #56Caf0; outline-offset: 2px; border-radius: 8px; animation: search-pulse 1.8s ease-out; }
@keyframes search-pulse { 0% { box-shadow: 0 0 0 0 #56Caf099; } 40% { box-shadow: 0 0 0 6px #56Caf000; } 100% { box-shadow: 0 0 0 0 #56Caf000; } }

View File

@ -24,6 +24,11 @@
<div class="header">
<img id="bclogo" src="../icon/icon-128.png">
<h1 data-i18n="Canvas_Refined">Canvas Refined</h1>
<div class="header-search" id="header-search">
<svg class="header-search-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" /><path d="M21 21l-6 -6" /></svg>
<input type="text" id="feature-search" data-i18n-placeholder="search_features" placeholder="Search features..." autocomplete="off" spellcheck="false">
<div class="search-results" id="feature-search-results"></div>
</div>
</div>
<div class="more-options-container">
<button id="customize-dark-btn" class="big-button tab-btn">
@ -184,18 +189,6 @@
</div>
<span class="option-name" data-i18n="todo_remind">Todo Reminders</span>
</div>
<div class="sub-options">
<div class="sub-option" style="margin-top:5px">
<input type="checkbox" id="scheduledReminder" name="scheduledReminder">
<label for="scheduledReminder" class="sub-text">scheduled reminders</label>
</div>
<div class="timesets">
<div class="timeset">
<input type="time" id="scheduledReminderTime" step="60"></input>
<span class="sub-text" data-i18n="scheduledReminderTime">Show reminders at specific time</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -37,6 +37,7 @@ chrome.runtime.onInstalled.addListener(function () {
"assignment_date_format": false,
"dashboard_notes": false,
"dashboard_notes_text": "",
"dashboard_notes_mode": "edit",
"better_todo": false,
"todo_hr24": false,
"todo_separate_scrollbar": false,
@ -89,8 +90,6 @@ chrome.runtime.onInstalled.addListener(function () {
"reminders": [],
"reminder_count": 1,
"multi_remind": false,
// "scheduledReminder": false,
// "scheduledReminderTime": { "hour": "09", "minute": "00" },
"id": "",
"new_browser": null,
"gpa_calc_cumulative": false,
@ -150,7 +149,7 @@ chrome.runtime.onInstalled.addListener(function () {
});
// The NASA APOD API with the demo key is limited to 30 requests/hour and 50/day.
// All calls are serialized through this worker and gated against those limits.
// Calls are serialized through this worker; the API's own 429 responses handle limiting.
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message?.type === "getNasaBackground") {
getNasaBackground().then(sendResponse);
@ -203,15 +202,6 @@ async function getNasaBackground() {
}
async function callNasaApi(dateStr) {
const now = Date.now();
const { nasa_api_calls = [] } = await chrome.storage.local.get("nasa_api_calls");
const lastHour = nasa_api_calls.filter(t => now - t < 3600 * 1000);
const lastDay = nasa_api_calls.filter(t => now - t < 24 * 3600 * 1000);
if (lastHour.length >= 30 || lastDay.length >= 50) {
console.warn("[CanvasRefined] NASA API rate limit reached, skipping request");
return "ratelimited";
}
let response;
try {
response = await fetch(`https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&thumbs=true&date=${dateStr}`);
@ -220,9 +210,6 @@ async function callNasaApi(dateStr) {
return null;
}
// The request was made, so it counts against the quota
await chrome.storage.local.set({ nasa_api_calls: [...lastDay, now] });
if (response.status === 429) return "ratelimited";
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
@ -234,5 +221,3 @@ async function callNasaApi(dateStr) {
return await response.json().catch(() => null);
}
// chrome.runtime.setUninstallURL("https://diditupe.dev/canvasrefined/goodbye");

View File

@ -258,7 +258,7 @@ function createNasaInfoOverlay() {
const icon = nasaInfoOverlayEl.querySelector("#nasa-info-icon");
const panel = nasaInfoOverlayEl.querySelector("#nasa-info-panel");
const showPanel = async () => {
const populatePanel = async () => {
const dateStr = new Date().toISOString().slice(0, 10);
const cacheKey = `nasa_apod_${dateStr}`;
const cached = await chrome.storage.local.get(cacheKey);
@ -266,23 +266,41 @@ function createNasaInfoOverlay() {
const metadataKey = `nasa_apod_meta_${metaDate}`;
const metadata = await chrome.storage.local.get(metadataKey);
const meta = metadata[metadataKey];
if (meta) {
document.getElementById("nasa-info-title").textContent = meta.title || "";
document.getElementById("nasa-info-date").textContent = `Date: ${meta.date}`;
document.getElementById("nasa-info-credit").textContent = meta.copyright ? `Credit: ${meta.copyright}` : "";
document.getElementById("nasa-info-explanation").textContent = meta.explanation || "No description available.";
panel.style.display = "block";
}
if (!meta) return false;
document.getElementById("nasa-info-title").textContent = meta.title || "";
document.getElementById("nasa-info-date").textContent = `Date: ${meta.date}`;
document.getElementById("nasa-info-credit").textContent = meta.copyright ? `Credit: ${meta.copyright}` : "";
document.getElementById("nasa-info-explanation").textContent = meta.explanation || "No description available.";
return true;
};
let pinned = false;
const showPanel = async () => {
if (pinned) return;
if (await populatePanel()) panel.style.display = "block";
};
const hidePanel = () => {
if (pinned) return;
panel.style.display = "none";
};
const togglePanel = async () => {
if (pinned) {
pinned = false;
panel.style.display = "none";
} else {
pinned = true;
if (await populatePanel()) panel.style.display = "block";
}
};
icon.addEventListener("mouseenter", showPanel);
icon.addEventListener("mouseleave", hidePanel);
panel.addEventListener("mouseenter", showPanel);
panel.addEventListener("mouseleave", hidePanel);
icon.addEventListener("click", togglePanel);
contentMain.appendChild(nasaInfoOverlayEl);
}
@ -322,78 +340,11 @@ let timeCheck = null;
let reminderCheck = null;
let betterSidebarLoading = false;
let dashboardReadyTimer = null;
//let assignmentData = null;
/*
Start
*/
/*
// only works if a course has no quizzes...
function getClassAverages() {
if (true) { // check if option is enabled
let match = current_page.match(/courses\/(?<id>\d*)\/grades/);
if (match) {
let course_grades = getData(`${domain}/api/v1/courses/${match.groups.id}/assignments?include[]=score_statistics&include[]=submission`);
let course_quizzes = getData(`${domain}/api/v1/courses/${match.groups.id}/quizzes`);
let course_groups = getData(`${domain}/api/v1/courses/${match.groups.id}/assignment_groups`);
course_grades.then(grades => {
course_groups.then(groups => {
course_quizzes.then(quizzes => {
let total_weight = 0;
let total_points = 0;
let weights = {};
groups.forEach(group => {
weights[group.id] = group.group_weight;
total_weight += group.group_weight;
});
groups.forEach(group => {
weights[group.id] = total_weight === 0 ? 1 : weights[group.id] / total_weight;
});
let min = 0, lowq = 0, mean = 0, median = 0, upq = 0, max = 0, earned = 0;
grades.forEach(grade => {
if (!grade.score_statistics) return;
console.log("\nthis:", grade.name, grade.score_statistics.lower_q, grade.score_statistics.mean, grade.score_statistics.upper_q);
console.log("totals:", lowq, upq, total_points);
min += grade.score_statistics.min * weights[grade.assignment_group_id];
lowq += grade.score_statistics.lower_q * weights[grade.assignment_group_id];
mean += grade.score_statistics.mean * weights[grade.assignment_group_id];
median += grade.score_statistics.median * weights[grade.assignment_group_id];
upq += grade.score_statistics.upper_q * weights[grade.assignment_group_id];
max += grade.score_statistics.max * weights[grade.assignment_group_id];
total_points += grade.points_possible * weights[grade.assignment_group_id];
earned += grade.submission.score * weights[grade.assignment_group_id];
});
course_quizzes.forEach(quiz => {
// is it even possible to get quiz statistics?
});
// absolute minimum is if the same student got the lowest score on every assignment
// absolute maximum is if the same student got the highest score on every assignment
// it doesn't really tell you much because both are unlikely
console.log("\nabsolute minimum:", min / total_points, "\nabsolute maximum:", max / total_points, "\nlower quartile:", lowq / total_points, "\nmean:", mean / total_points, "\nupper quartile:", upq / total_points);
min = (min / total_points);
lowq = (lowq / total_points);
mean = (mean / total_points);
upq = (upq / total_points);
max = (max / total_points);
earned = (earned / total_points);
console.log(weights);
const width = 150;
let inner = `<td colspan="6" style="padding-bottom: 20px;"><table id="" class=""><thead><tr><th colspan="5">Class Averages</th><th></th></tr></thead><tbody><tr><td>Mean: ${(mean * 100).toFixed(2)}</td><td>Upper Quartile: ${(upq * 100).toFixed(2)}</td><td>Lower Quartile: ${(lowq * 100).toFixed(2)}</td><td colspan="3"><svg viewBox="-1 0 160 30" xmlns="http://www.w3.org/2000/svg" style="float: right; height: 30px; margin-20px; width: 161px; position: relative; margin-right: 30px;" aria-hidden="true"><line class="zero" x1="0" y1="3" x2="0" y2="27" stroke="#556572"></line><line class="possible" x1="150.0" y1="3" x2="150.0" y2="27" stroke="#556572"></line><line class="min" x1="${min * width}" y1="6" x2="${min * width}" y2="24" stroke="#556572" stroke-width="2"></line><line class="bottomQ" x1="${min * width}" y1="15" x2="${lowq * width}" y2="15" stroke="#556572" stroke-width="2"></line><line class="topQ" x1="${upq * width}" y1="15" x2="${max * width}" y2="15" stroke="#556572" stroke-width="2"></line><line class="max" x1="${max * width}" y1="6" x2="${max * width}" y2="24" stroke="#556572" stroke-width="2"></line><rect class="mid50" x="${lowq * width}" y="3" width="22.499999999999986" height="24" stroke="#556572" stroke-width="2" rx="3" fill="none"></rect><line class="median" x1="${mean * width}" y1="3" x2="${mean * width}" y2="27" stroke="#556572" stroke-width="2"></line><rect class="myScore" x="${(earned * width) - 7}" y="8" width="14" height="14" stroke="#224488" stroke-width="2" rx="3" fill="#aabbdd"></rect></svg></td></tr></tbody></table></td>`;
makeElement("tr", document.querySelector("#grades_summary tbody"), { "innerHTML": inner });
});
});
});
}
}
}
*/
/*
Todo Reminders
@ -503,38 +454,11 @@ function showExampleReminder() {
example.querySelector(".canvasrefined-reminder-due").textContent = "This notification will pop up in other pages to remind you of incomplete assignments that are due in less than 6 hours." /*It will notify again at 2 hours if the 'Remind 2x' option is on."*/;
}
// async function ScheduledReminderCheck() {
// let date = new Date();
// let currentHour = date.getHours();
// let currentMinute = date.getMinutes();
// if (options.scheduledReminderTime) {
// let [hour, minute] = options.scheduledReminderTime.split(":");
// if (parseInt(hour) == currentHour && parseInt(minute) == currentMinute) {
// const container = document.getElementById("canvasrefined-reminders") || makeElement("div", document.body, { "id": "canvasrefined-reminders" });
// container.style.display = "flex";
// container.textContent = "";
// const storage = await chrome.storage.sync.get("reminders");
// const now = (new Date()).getTime();
// storage["reminders"].forEach(reminder => {
// if (reminder.d >= now) {
// createReminder(reminder, container);
// }
// });
// }
// }
// }
// function toggleScheduledReminders() {
// clearInterval(reminderCheck);
// if (options.scheduledReminder !== true) return;
// ScheduledReminderCheck();
// reminderCheck = setInterval(ScheduledReminderCheck, 60000);
// }
isDomainCanvasPage();
function isDomainCanvasPage() {
chrome.storage.sync.get(['custom_domain', 'dark_mode', 'dark_preset', 'device_dark', 'remind'/*, 'scheduledReminder', 'scheduledReminderTime'*/], result => {
chrome.storage.sync.get(['custom_domain', '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++) {
@ -547,15 +471,10 @@ function isDomainCanvasPage() {
// if the code reaches this point, its not a canvas page so run the reminders
setTimeout(reminderWatch, 2000);
setInterval(reminderWatch, 60000);
// toggleScheduledReminders();
// turn the reminders on/off if the option is changed
chrome.storage.onChanged.addListener((changes) => {
Object.keys(changes).forEach(key => {
if (key === "remind") reminderWatch();
if (key === "scheduledReminder" || key === "scheduledReminderTime") {
options[key] = changes[key].newValue;
// toggleScheduledReminders();
}
})
})
} else {
@ -599,7 +518,6 @@ function startExtension() {
watchSubmissionPageButton();
watchProfileLogoutPageButton();
//getClassAverages();
setTimeout(() => runDarkModeFixer(false), 800);
setTimeout(() => runDarkModeFixer(false), 4500);
@ -639,6 +557,8 @@ function applyOptionsChanges(changes) {
changeGradientCards();
break;
case "dashboard_notes":
case "dashboard_notes_text":
case "dashboard_notes_mode":
loadDashboardNotes();
break;
case "dashboard_grades":
@ -726,16 +646,9 @@ function applyOptionsChanges(changes) {
case "fitImageToScreen":
applyCustomBackground();
break;
// case "show_updates":
// showUpdateMsg();
// break;
case "remind":
showExampleReminder();
break;
// case "scheduledReminder":
// case "scheduledReminderTime":
// toggleScheduledReminders();
// break;
case "imageSize":
case "cardRoundness":
case "cardSpacing":
@ -1021,10 +934,6 @@ async function applyCustomBackground() {
document.documentElement.appendChild(style);
}
function clearCustomBackground() {
let style = document.querySelector("#canvasrefined-background");
if (style) style.remove();
}
function applyBetterSidebarLayoutFix() {
let style = document.querySelector("#canvasrefined-sidebar-layout-fix") || document.createElement("style");
@ -1151,11 +1060,6 @@ function inspectDarkMode(withOutput = false) {
const r = parseInt(bgcolor.groups["r"]);
const g = parseInt(bgcolor.groups["g"]);
const b = parseInt(bgcolor.groups["b"]);
/*
if (el.classList.contains("no-touch")) {
console.log({ "r": r, "g": g, "b": b }, { "r": r === bg0.r, "g": g === bg0.g, "b": b === bg0.b });
}
*/
if (r > 245 && g > 245 && b > 245 && !(r === bg0.r && g === bg0.g && b === bg0.b) && !(r === lnk.r && g === lnk.g && b === lnk.b)) {
el.style.cssText = (";background:" + options.dark_preset["background-0"] + "!important;color" + options.dark_preset["text-0"] + "!important;") + el.style.cssText;
if (withOutput === true) output += selector + "{background: background-0, color: text-0}\n";
@ -1354,123 +1258,6 @@ async function getCards(api = null) {
Better todo list
*/
// function setAssignmentState(id, updates) {
// let states = options.assignment_states;
// let length = JSON.stringify(states).length;
// // remove the oldest states if the size is approaching the storage limit
// if (length > 7400) {
// let keys = Object.keys(states).sort((a, b) => states[b].expire - states[a].expire);
// keys.splice(-5);
// let newStates = {};
// keys.forEach(key => {
// newStates[key] = states[key];
// });
// states = newStates;
// }
// states[id] = states[id] ? { ...states[id], ...updates } : updates;
// chrome.storage.sync.set({ assignment_states: states }).then(() => { cardAssignments = preloadAssignmentEls(); loadBetterTodo(); loadCardAssignments(); });
// }
function createTodoCreateBtn(location) {
let confirmButton = makeElement("button", location, { "className": "canvasrefined-custom-btn", "textContent": "Create" });
confirmButton.addEventListener("click", () => {
chrome.storage.sync.get("custom_assignments_overflow", overflow => {
chrome.storage.sync.get(overflow["custom_assignments_overflow"], storage => {
let course_id = parseInt(location.querySelector("#canvasrefined-custom-course").value);
const assignment = {
"plannable_id": new Date().getTime(),
"context_name": options.custom_cards[location.querySelector("#canvasrefined-custom-course").value].default,
"plannable": { "title": location.querySelector("#canvasrefined-custom-name").value },
"plannable_date": location.querySelector("#canvasrefined-custom-date").value + "T" + location.querySelector("#canvasrefined-custom-time").value + ":00",
"planner_override": { "marked_complete": false, "custom": true },
"plannable_type": "assignment",
"submissions": { "submitted": false },
"course_id": course_id,
"html_url": `/courses/${course_id}/assignments`
};
/* handling overflow since the limit is 8kb per key */
let found = false;
let reload = () => {
location.classList.toggle("canvasrefined-custom-open");
loadBetterTodo();
loadCardAssignments();
}
/* find the first available overflow with space */
/* or create a new one if all are full */
let findOpenOverflow = (num) => {
let current_overflow = overflow["custom_assignments_overflow"][num];
storage[current_overflow].push(assignment);
chrome.storage.sync.set({ [current_overflow]: storage[current_overflow] }, () => {
/* assuming any error is because the limit is exceeded */
if (chrome.runtime.lastError) {
if (num === overflow["custom_assignments_overflow"].length - 1) {
console.log("all overflows are full! creating new overflow " + (overflow["custom_assignments_overflow"].length + 1));
let new_overflow = "custom_assignments_" + (overflow["custom_assignments_overflow"].length + 1);
overflow["custom_assignments_overflow"].push(new_overflow);
chrome.storage.sync.set({ [new_overflow]: [assignment], "custom_assignments_overflow": overflow["custom_assignments_overflow"] }).then(reload);
} else {
console.log("overflow " + (num + 1) + " full...");
findOpenOverflow(num + 1);
}
} else {
console.log("overflow " + (num + 1) + " has space!");
reload();
}
});
}
findOpenOverflow(0);
});
})
});
}
// better todo html layer 1
// function createTodoHeader(location) {
// let todoHeader = makeElement("h2", location, { "className": "todo-list-header", "style": "display: flex; align-items:center; justify-content:space-between;" });
// //todoHeader.style = "display: flex; align-items:center; justify-content:space-between;";
// if (!options.custom_cards || Object.keys(options.custom_cards).length === 0) return;
// let addFillout = makeElement("div", location, { "className": "canvasrefined-add-assignment" });
// let now = new Date();
// let year = now.getFullYear();
// let month = now.getMonth() + 1;
// let day = now.getDate();
// month = month < 10 ? "0" + month : month;
// day = day < 10 ? "0" + day : day;
// addFillout.innerHTML = '<input type="text" placeholder="Name" id="canvasrefined-custom-name" class="canvasrefined-custom-input"></input><select id="canvasrefined-custom-course" class="canvasrefined-custom-input"><option value="" disabled selected>Select course</option></select><div style="display: flex;gap:5px"><input type="date" id="canvasrefined-custom-date" class="canvasrefined-custom-input"></input><input type="time" id="canvasrefined-custom-time" class="canvasrefined-custom-input" value="23:59"></input></div>';
// addFillout.querySelector("#canvasrefined-custom-date").value = year + "-" + month + "-" + day;
// let selectCourse = document.querySelector("#canvasrefined-custom-course");
// Object.keys(options.custom_cards).forEach(id => {
// let card = options.custom_cards[id];
// let courseName = makeElement("option", selectCourse, { "className": "canvasrefined-select-course-option", "textContent": card.default });
// courseName.value = id;
// });
// createTodoCreateBtn(addFillout);
// let headerText = makeElement("span", todoHeader, { "className": "canvasrefined-todo-header", "textContent": "To Do" });
// let addButton = makeElement("button", todoHeader, { "className": "canvasrefined-custom-btn", "textContent": "+ Add" });
// addButton.addEventListener("click", () => {
// addFillout.classList.toggle("canvasrefined-custom-open");
// });
// headerText.addEventListener("click", () => {
// if (filter === "todo") {
// filter = "done";
// headerText.textContent = "Done";
// } else {
// filter = "todo";
// headerText.textContent = "To Do";
// }
// moreAssignmentCount = 0;
// moreAnnouncementCount = 0;
// loadBetterTodo();
// });
// }
function convertToDueDate(dueAt) {
final = "due ";
@ -2898,20 +2685,6 @@ function updateSidebar(expanded, sidebarList, expander) {
}
}
}
function getCourseLinks() {
const linkList = document.getElementById("section-tabs");
if (!linkList) return [];
const links = linkList.querySelectorAll("a");
const courseLinks = [];
links.forEach(link => {
const url = new URL(link.href).pathname;
courseLinks.push({
name: link.textContent.trim(),
url: url
});
})
return courseLinks;
}
let delay;
let moreAssignmentCount = 0;
@ -3101,84 +2874,8 @@ async function loadBetterTodo() {
});
});
});
} /*else {
// set the item as complete through api
fetch(domain + '/api/v1/planner/overrides' + (item.planner_override ? "/" + item.planner_override.id : ""),
{
method: item.planner_override ? "PUT" : "POST",
headers: {
"content-type": "application/json",
'accept': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify({ id: item.planner_override ? item.planner_override.id : null, marked_complete: true, plannable_id: item.plannable_id, plannable_type: item.plannable_type })
}).then(resp => {
if (resp.status === 200 || resp.status === 201) {
let container = listItemContainer.parentElement;
container.removeChild(listItemContainer);
assignments.forEach(assignment => {
if (assignment.plannable_id === item.plannable_id) {
item.planner_override = { "marked_complete": true };
}
});
loadBetterTodo();
loadCardAssignments();
}
});
}*/
});
/*
// remove item button
listItemContainer.querySelector(".canvasrefined-todo-complete-btn").addEventListener('click', function () {
if (item.planner_override && item.planner_override.custom && item.planner_override.custom === true) {
// set item as complete locally
chrome.storage.sync.get("custom_assignments_overflow", overflow => {
chrome.storage.sync.get(overflow["custom_assignments_overflow"], storage => {
overflow["custom_assignments_overflow"].forEach(overflow => {
for (let i = 0; i < storage[overflow].length; i++) {
if (storage[overflow][i].plannable_id === item.plannable_id) {
storage[overflow].splice(i, 1);
chrome.storage.sync.set({ [overflow]: storage[overflow] }).then(() => {
let container = listItemContainer.parentElement;
container.removeChild(listItemContainer);
loadBetterTodo();
loadCardAssignments();
});
break;
}
}
});
});
});
} else {
// set the item as complete through api
fetch(domain + '/api/v1/planner/overrides' + (item.planner_override ? "/" + item.planner_override.id : ""),
{
method: item.planner_override ? "PUT" : "POST",
headers: {
"content-type": "application/json",
'accept': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify({ id: item.planner_override ? item.planner_override.id : null, marked_complete: true, plannable_id: item.plannable_id, plannable_type: item.plannable_type })
}).then(resp => {
if (resp.status === 200 || resp.status === 201) {
let container = listItemContainer.parentElement;
container.removeChild(listItemContainer);
assignmentData.forEach(assignment => {
if (assignment.plannable_id === item.plannable_id) {
item.planner_override = { "marked_complete": true };
}
});
loadBetterTodo();
loadCardAssignments();
}
});
}
});
*/
if (item.plannable_type === "announcement") {
announcementsToInsert.push(listItemContainer);
@ -3188,8 +2885,6 @@ async function loadBetterTodo() {
listItemContainer.classList.add("canvasrefined-todo-item-completed");
}
}
//}
//}
});
@ -3352,13 +3047,6 @@ function toggleDarkMode() {
style.textContent = options.dark_mode === true || options.device_dark ? css : "";
style.className = options.dark_mode === true || options.device_dark ? "canvasrefined-darkmode-enabled" : "";
}
/*
if (options.dark_mode === true || options.device_dark) {
document.body.classList.add("canvasrefined--darkmode--enabled");
} else {
document.body.classList.remove("canvasrefined--darkmode--enabled");
}
*/
runiframeChecker();
}
@ -3394,25 +3082,6 @@ function autoDarkModeCheck() {
}
}
// async function ScheduledReminderCheck() {
// let date = new Date();
// let currentHour = date.getHours();
// let currentMinute = date.getMinutes();
// if (options.scheduledReminderTime) {
// let [hour, minute] = options.scheduledReminderTime.split(":");
// if (parseInt(hour) == currentHour && parseInt(minute) == currentMinute) {
// const container = document.getElementById("canvasrefined-reminders") || makeElement("div", document.body, { "id": "canvasrefined-reminders" });
// container.style.display = "flex";
// container.textContent = "";
// const storage = await chrome.storage.sync.get("reminders");
// const now = (new Date()).getTime();
// storage["reminders"].forEach(reminder => {
// if (reminder.d >= now) {
// createReminder(reminder, container);
// }
// });
// }
// }
// }
@ -3423,12 +3092,6 @@ function toggleAutoDarkMode() {
timeCheck = setInterval(autoDarkModeCheck, 60000);
}
// function toggleScheduledReminders() {
// clearInterval(reminderCheck);
// if (options.scheduled_reminders === false) return; //TODO: add it to the options thing
// ScheduledReminderCheck();
// reminderCheck = setInterval(ScheduledReminderCheck, 60000);
// }
let iframeObserver;
function runiframeChecker() {
@ -3506,18 +3169,6 @@ function insertGrades() {
Card assignments
*/
/*
function setAssignmentStatus(id, status, assignments_done = []) {
if (assignments_done.length > 50) assignments_done = [];
if (status === true) {
assignments_done.push(id);
} else {
const pos = assignments_done.indexOf(id);
if (pos > -1) assignments_done.splice(pos, 1);
}
chrome.storage.sync.set({ assignments_done: assignments_done });
}
*/
function createCardAssignment(assignment) {
let assignmentContainer = document.createElement("div");
@ -3668,59 +3319,6 @@ function loadCardAssignments() {
});
}
/*
function loadCardAssignments2(c = null) {
if (options.assignments_due === true) {
try {
assignments.then(data => {
//assignmentData = assignmentData === null ? data : assignmentData; ????
let items = combineAssignments(data);
let cards = c ? c : document.querySelectorAll('.ic-DashboardCard');
const now = new Date();
cards.forEach(card => {
let count = 0;
let course_id = parseInt(card.querySelector(".ic-DashboardCard__link").href.split("courses/")[1]);
let cardContainer = card.querySelector('.canvasrefined-card-container');
cardContainer.textContent = "";
cardContainer.parentElement.style.display = "block";
items.forEach(assignment => {
let due = new Date(assignment.plannable_date);
// lots of checks to make
// 1. item belongs to card
// 2. haven't exceeded item limit
// 3. assignment hasn't been submitted (if hide completed option is on)
// 4. disallow overdue and item not past due/allow overdue and item hasn't been submitted
// 5. correct item type
// 6. no planner override marking item complete
if (course_id !== assignment.course_id) return;
if (count >= options.num_assignments) return;
if (options.hide_completed === true && assignment.submissions.submitted === true) return;
if ((options.card_overdues !== true && now >= due) || (options.card_overdues === true && assignment.submissions.submitted === true)) return;
if ((assignment.plannable_type !== "assignment" && assignment.plannable_type !== "quiz" && assignment.plannable_type !== "discussion_topic")) return;
if (assignment.planner_override && assignment.planner_override.marked_complete === true) return;
createCardAssignment(cardContainer, assignment, now >= due);
count++;
});
if (count === 0) {
let assignmentContainer = makeElement("div", "canvasrefined-assignment-container", cardContainer);
let assignmentDivLink = makeElement("a", "canvasrefined-assignment-link", assignmentContainer, "None");
}
});
});
} catch (e) {
logError(e);
}
} else {
document.querySelectorAll(".canvasrefined-card-assignment").forEach(card => {
card.style.display = "none";
});
}
}
*/
function setupCardAssignments() {
if (options.assignments_due !== true) return;
@ -3907,14 +3505,6 @@ function calculateGPA2() {
letter = "F";
gpa = options.gpa_calc_bounds["F"].gpa;
}
/*
if (course.id === "cumulative-gpa") {
//gpa = parseFloat(options["cumulative_gpa"]["gr"]);
gpa = 0;
cumulativePoints += parseFloat(options["cumulative_gpa"]["gr"]) * credits;
cumulativeCredits = credits;
} else {
*/
course.querySelector(".canvasrefined-gpa-letter-grade").textContent = letter;
let weightMultiplier = 0;
@ -3927,8 +3517,6 @@ function calculateGPA2() {
qualityPoints += gpa * credits;
weightedQualityPoints += (gpa + weightMultiplier) * credits;
numCredits += credits;
//}
});
@ -4115,7 +3703,6 @@ Dashboard notes
*/
let dashboardNotesTimer;
let dashboardNotesResizeFrame;
function delayDashboardNotesStorage(text) {
clearTimeout(dashboardNotesTimer);
dashboardNotesTimer = setTimeout(() => {
@ -4123,39 +3710,278 @@ function delayDashboardNotesStorage(text) {
}, 250);
}
function resizeDashboardNotes(notes) {
if (!notes) return;
notes.style.height = "auto";
notes.style.height = `${notes.scrollHeight + 5}px`;
/*
Built-in fallback Markdown renderer. Used only if js/markdown.js failed to load
(window.renderMarkdown missing) so the notes still render formatted output instead
of showing raw markdown text. Covers the common subset: headings, bold/italic/strike,
inline + fenced code, links, images, lists, task lists, blockquotes, hr, paragraphs.
All user text is HTML-escaped before formatting.
*/
function crRenderMarkdownFallback(src) {
if (src == null) return "";
const escapeHtml = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
const sanitizeUrl = (u) => {
const v = String(u == null ? "" : u).trim();
if (!v) return "";
if (/^(https?:|mailto:|ftp:|tel:)/i.test(v)) return v;
if (/^(javascript:|vbscript:|file:|data:)/i.test(v)) return "#";
if (/^[#/?]/.test(v)) return v;
if (/^[a-z][a-z0-9+.-]*:/i.test(v)) return "#";
return v;
};
let text = String(src).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
const out = [];
const lines = text.split("\n");
let i = 0;
const inline = (t) => {
let h = escapeHtml(t);
h = h.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (m, alt, url, title) =>
`<img src="${sanitizeUrl(url)}" alt="${alt}"${title ? ` title="${title}"` : ""}>`);
h = h.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (m, txt, url, title) =>
`<a href="${sanitizeUrl(url)}" target="_blank" rel="noopener noreferrer"${title ? ` title="${title}"` : ""}>${txt}</a>`);
h = h.replace(/`([^`\n]+)`/g, (m, c) => `<code>${c}</code>`);
h = h.replace(/\*\*([^*]+?)\*\*/g, "<strong>$1</strong>");
h = h.replace(/~~([^~]+?)~~/g, "<del>$1</del>");
h = h.replace(/(^|[^*])\*([^*]+?)\*(?!\*)/g, "$1<em>$2</em>");
return h;
};
while (i < lines.length) {
const line = lines[i];
if (/^\s*$/.test(line)) { i++; continue; }
const h = line.match(/^(#{1,6})\s+(.*)$/);
if (h) { const l = h[1].length; out.push(`<h${l}>${inline(h[2])}</h${l}>`); i++; continue; }
if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) { out.push("<hr>"); i++; continue; }
if (/^>\s?/.test(line)) {
const q = []; while (i < lines.length && /^>\s?/.test(lines[i])) { q.push(inline(lines[i].replace(/^>\s?/, ""))); i++; }
out.push(`<blockquote>${q.join("<br>")}</blockquote>`); continue;
}
if (/^\s*[-*+]\s+/.test(line)) {
const items = []; while (i < lines.length) { const m = lines[i].match(/^\s*[-*+]\s+(.*)$/); if (!m) break; const tk = m[1].match(/^\[([ xX])\]\s+(.*)$/); if (tk) { items.push(`<li class="cr-task"><input type="checkbox" disabled${/x/i.test(tk[1]) ? " checked" : ""}> ${inline(tk[2])}</li>`); } else { items.push(`<li>${inline(m[1])}</li>`); } i++; } out.push(`<ul>${items.join("")}</ul>`); continue;
}
if (/^\s*\d+\.\s+/.test(line)) {
const items = []; while (i < lines.length) { const m = lines[i].match(/^\s*\d+\.\s+(.*)$/); if (!m) break; items.push(`<li>${inline(m[1])}</li>`); i++; } out.push(`<ol>${items.join("")}</ol>`); continue;
}
const para = [line]; i++; while (i < lines.length && !/^\s*$/.test(lines[i]) && !/^(#{1,6}\s|\s*[-*+]\s|\s*\d+\.\s|>)/.test(lines[i])) { para.push(lines[i]); i++; }
out.push(`<p>${para.map(inline).join("<br>")}</p>`);
}
return out.join("\n");
}
function scheduleDashboardNotesResize(notes) {
if (dashboardNotesResizeFrame) cancelAnimationFrame(dashboardNotesResizeFrame);
dashboardNotesResizeFrame = requestAnimationFrame(() => {
dashboardNotesResizeFrame = null;
resizeDashboardNotes(notes);
function renderDashboardNotesPreview(preview, text) {
if (!preview) return;
// Skip identical re-renders: writing innerHTML is a childList mutation that the
// dashboard MutationObserver picks up, which re-calls loadDashboardNotes, which
// re-renders... Without this guard the notes box drives a tight self-sustaining
// loop (setTimeout 0) that starves the main thread so the render never paints.
if (preview._crLastText === text) return;
preview._crLastText = text;
const renderer = (typeof window.renderMarkdown === "function") ? window.renderMarkdown : crRenderMarkdownFallback;
preview.innerHTML = renderer(text);
}
/*
Insert/wrap Markdown formatting in the notes editor at the current selection.
Dispatches a synthetic `input` event so the live preview + storage handler runs.
*/
function notesApplyFormat(editor, action) {
if (!editor) return;
const start = editor.selectionStart;
const end = editor.selectionEnd;
const value = editor.value;
const fire = () => {
editor.dispatchEvent(new Event("input", { bubbles: true }));
editor.focus();
};
const wrap = (before, after, placeholder) => {
const had = end > start;
const sel = had ? value.slice(start, end) : (placeholder || "");
editor.setRangeText(before + sel + after, start, end, "end");
editor.selectionStart = start + before.length;
editor.selectionEnd = start + before.length + sel.length;
fire();
};
// Range covering every line touched by the selection.
const lineBlock = () => {
const ls = start === 0 ? 0 : value.lastIndexOf("\n", start - 1) + 1;
let le = value.indexOf("\n", end);
if (le === -1) le = value.length;
return { ls, le, block: value.slice(ls, le) };
};
const togglePrefix = (prefix) => {
const { ls, le, block } = lineBlock();
const lines = block.split("\n");
const allHave = lines.every((l) => l.startsWith(prefix));
const newBlock = lines.map((l) => allHave ? l.slice(prefix.length) : prefix + l).join("\n");
editor.setRangeText(newBlock, ls, le, "end");
editor.selectionStart = ls;
editor.selectionEnd = ls + newBlock.length;
fire();
};
switch (action) {
case "bold": wrap("**", "**", "bold"); break;
case "italic": wrap("*", "*", "italic"); break;
case "strike": wrap("~~", "~~", "strikethrough"); break;
case "code": wrap("`", "`", "code"); break;
case "h1": togglePrefix("# "); break;
case "h2": togglePrefix("## "); break;
case "list": togglePrefix("- "); break;
case "numbered": togglePrefix("1. "); break;
case "quote": togglePrefix("> "); break;
case "task": {
const { ls, le, block } = lineBlock();
const marker = block.match(/^-\s*\[([ xX])\]\s+/);
const bullet = block.match(/^[-*+]\s+/);
let newBlock;
if (marker) {
newBlock = block.slice(marker[0].length);
} else if (bullet) {
newBlock = "- [ ] " + block.slice(bullet[0].length);
} else {
newBlock = "- [ ] " + block;
}
editor.setRangeText(newBlock, ls, le, "end");
editor.selectionStart = ls;
editor.selectionEnd = ls + newBlock.length;
fire();
break;
}
case "link": {
const had = end > start;
const sel = had ? value.slice(start, end) : "text";
editor.setRangeText("[" + sel + "](url)", start, end, "end");
const urlStart = start + 1 + sel.length + 2; // after "]("
editor.selectionStart = urlStart;
editor.selectionEnd = urlStart + 3; // select "url"
fire();
break;
}
case "hr": {
const lead = start > 0 && value[start - 1] !== "\n" ? "\n" : "";
editor.setRangeText(lead + "---\n", start, start, "end");
fire();
break;
}
case "codeblock": {
const had = end > start;
const sel = had ? value.slice(start, end) : "code";
const lead = start > 0 && value[start - 1] !== "\n" ? "\n" : "";
editor.setRangeText(lead + "```\n" + sel + "\n```", start, end, "end");
fire();
break;
}
}
}
const DASHBOARD_NOTES_HTML = `
<div class="canvasrefined-notes-toolbar" role="toolbar" aria-label="Format notes">
<button type="button" class="cr-fmt" data-action="bold" title="Bold (Ctrl/Cmd+B)"><strong>B</strong></button>
<button type="button" class="cr-fmt" data-action="italic" title="Italic (Ctrl/Cmd+I)"><em>I</em></button>
<button type="button" class="cr-fmt" data-action="strike" title="Strikethrough"><s>S</s></button>
<button type="button" class="cr-fmt" data-action="code" title="Inline code"><code>&lt;/&gt;</code></button>
<span class="cr-fmt-sep"></span>
<button type="button" class="cr-fmt" data-action="h1" title="Heading 1">H1</button>
<button type="button" class="cr-fmt" data-action="h2" title="Heading 2">H2</button>
<span class="cr-fmt-sep"></span>
<button type="button" class="cr-fmt" data-action="list" title="Bullet list">&bull;</button>
<button type="button" class="cr-fmt" data-action="numbered" title="Numbered list">1.</button>
<button type="button" class="cr-fmt" data-action="task" title="Task list">&#9744;</button>
<button type="button" class="cr-fmt" data-action="quote" title="Quote">&ldquo;</button>
<span class="cr-fmt-sep"></span>
<button type="button" class="cr-fmt" data-action="link" title="Insert link">Link</button>
<button type="button" class="cr-fmt" data-action="hr" title="Horizontal rule">&mdash;</button>
<button type="button" class="cr-fmt" data-action="codeblock" title="Code block">&#96;&#96;&#96;</button>
</div>
<div class="canvasrefined-notes-surface">
<div class="canvasrefined-notes-rendered" tabindex="0" aria-label="Dashboard notes — click to edit" title="Click to edit"></div>
<textarea class="canvasrefined-notes-editor" placeholder="Type Markdown — click away to render" spellcheck="false"></textarea>
</div>
`;
function wireDashboardNotes(notes) {
const editor = notes.querySelector(".canvasrefined-notes-editor");
const rendered = notes.querySelector(".canvasrefined-notes-rendered");
editor.value = options.dashboard_notes_text || "";
renderDashboardNotesPreview(rendered, editor.value);
const enterEdit = () => {
if (notes.classList.contains("is-editing")) return;
notes.classList.add("is-editing");
editor.focus();
const len = editor.value.length;
editor.setSelectionRange(len, len);
};
const exitEdit = () => {
notes.classList.remove("is-editing");
renderDashboardNotesPreview(rendered, editor.value);
};
rendered.addEventListener("click", enterEdit);
rendered.addEventListener("focus", enterEdit);
editor.addEventListener("blur", exitEdit);
editor.addEventListener("input", function () {
options.dashboard_notes_text = this.value;
delayDashboardNotesStorage(this.value);
});
// Toolbar buttons: keep focus in the editor (mousedown preventDefault stops the
// button from stealing focus and collapsing back to the rendered view), then apply
// the formatting. Enters edit mode first if the user formats from the rendered view.
notes.querySelectorAll(".cr-fmt").forEach(btn => {
btn.addEventListener("mousedown", e => e.preventDefault());
btn.addEventListener("click", () => {
if (!notes.classList.contains("is-editing")) enterEdit();
notesApplyFormat(editor, btn.dataset.action);
});
});
editor.addEventListener("keydown", function (e) {
if (e.key === "Escape") { e.preventDefault(); editor.blur(); return; } // Esc: render
const mod = e.ctrlKey || e.metaKey;
if (!mod) return;
const k = e.key.toLowerCase();
if (k === "b") { e.preventDefault(); notesApplyFormat(editor, "bold"); }
else if (k === "i") { e.preventDefault(); notesApplyFormat(editor, "italic"); }
else if (k === "enter") { e.preventDefault(); editor.blur(); } // Ctrl/Cmd+Enter: render
});
}
function loadDashboardNotes() {
const container = document.querySelector("#DashboardCard_Container");
if (options.dashboard_notes === true) {
if (!container) return;
let notes = document.querySelector('.canvasrefined-dashboard-notes');
// Rebuild older (split edit/preview) markup into the new single-surface layout.
if (notes && !notes.querySelector(".canvasrefined-notes-surface")) {
notes.remove();
notes = null;
}
if (!notes) {
notes = document.createElement("textarea");
notes = document.createElement("div");
notes.classList.add("canvasrefined-dashboard-notes");
notes.placeholder = "Enter notes here";
document.querySelector("#DashboardCard_Container").prepend(notes);
notes.value = options.dashboard_notes_text;
notes.style.display = "block";
resizeDashboardNotes(notes);
notes.addEventListener('input', function () {
options.dashboard_notes_text = this.value;
delayDashboardNotesStorage(this.value);
scheduleDashboardNotesResize(this);
});
notes.innerHTML = DASHBOARD_NOTES_HTML;
// Mount as a full-width sibling above the card grid. Prepending inside the
// DashboardCard_Container makes the notes a masonry/grid cell (narrow & broken).
const parent = container.parentNode;
if (parent) parent.insertBefore(notes, container);
else container.prepend(notes);
wireDashboardNotes(notes);
} else {
notes.style.display = "block";
resizeDashboardNotes(notes);
notes.style.display = "";
const editor = notes.querySelector(".canvasrefined-notes-editor");
const rendered = notes.querySelector(".canvasrefined-notes-rendered");
// While editing, the textarea is the source of truth: don't clobber it from
// storage and don't waste a render on the hidden rendered view (which would
// also feed the observer loop). Only sync + render in view mode.
if (!notes.classList.contains("is-editing")) {
if (editor && editor.value !== (options.dashboard_notes_text || "")) {
editor.value = options.dashboard_notes_text || "";
}
renderDashboardNotesPreview(rendered, editor ? editor.value : "");
}
}
} else {
let notes = document.querySelector('.canvasrefined-dashboard-notes');
@ -4231,16 +4057,6 @@ function applyAestheticChanges() {
document.documentElement.appendChild(style);
}
/*
function changeFullWidth() {
if (options.full_width == null) return;
if (options.full_width === true) {
document.body.classList.add("full-width");
} else {
document.body.classList.remove("full-width");
}
}
*/
function changeGradientCards() {
if (options.gradient_cards === true) {
@ -4426,14 +4242,6 @@ function makeElement(element, location, options, prepend = false) {
}
function makeElement2(element, elclass, location, text) {
let creation = document.createElement(element);
creation.classList.add(elclass);
creation.textContent = text;
location.appendChild(creation);
return creation
}
async function getData(url) {
let response = await fetch(url, {
method: 'GET',
@ -4446,10 +4254,6 @@ async function getData(url) {
return data
}
function hexToHsl(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return rgbToHsl(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
}
function rgbToHex(rgb) {
try {
@ -4541,4 +4345,4 @@ function logError(e) {
const CSRFtoken = function () {
return decodeURIComponent((document.cookie.match('(^|;) *_csrf_token=([^;]*)') || '')[2])
}
}

203
js/markdown.js Normal file
View File

@ -0,0 +1,203 @@
/*
CanvasRefined - lightweight Markdown renderer for dashboard notes.
Renders a small, notes-friendly subset of Markdown to HTML:
headings, bold, italic, strikethrough, inline code, fenced code blocks,
links, images, autolinks, blockquotes, ordered/unordered lists, task
lists, horizontal rules, and paragraphs.
All user-supplied text is HTML-escaped before any formatting is applied,
and links/images are URL-sanitized, so the returned HTML is safe to assign
via innerHTML. Exposes window.renderMarkdown(text) -> html.
*/
(function () {
"use strict";
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function sanitizeUrl(url) {
const u = String(url == null ? "" : url).trim();
if (u === "") return "";
if (/^(https?:|mailto:|ftp:|tel:)/i.test(u)) return u;
if (/^(javascript:|vbscript:|file:|data:)/i.test(u)) return "#";
if (/^[#/?]/.test(u)) return u;
// Block any other explicit protocol we did not whitelist.
if (/^[a-z][a-z0-9+.\-]*:/i.test(u)) return "#";
return u;
}
function renderMarkdown(src) {
if (src == null) return "";
src = String(src).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
// Stash protected HTML (code blocks/spans, autolinks) behind NUL-delimited
// tokens so they survive escaping and inline formatting untouched.
const stash = [];
const keep = (html) => {
stash.push(html);
return "\u0000" + (stash.length - 1) + "\u0000";
};
// 1. Fenced code blocks: ```lang\n code ```
src = src.replace(/```([\w-]*)\n?([\s\S]*?)```/g, (m, lang, code) => {
code = code.replace(/^\n/, "").replace(/\n$/, "");
const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : "";
return keep(`<pre><code${langAttr}>${escapeHtml(code)}</code></pre>`);
});
// 2. Inline code: `code`
src = src.replace(/`([^`\n]+)`/g, (m, code) => keep(`<code>${escapeHtml(code)}</code>`));
// 3. Autolinks: <https://example.com>
src = src.replace(/<(https?:\/\/[^\s<>]+)>/g, (m, url) => {
const safe = escapeHtml(sanitizeUrl(url));
return keep(`<a href="${safe}" target="_blank" rel="noopener noreferrer">${escapeHtml(url)}</a>`);
});
// 4. Inline formatting, applied to escaped text so nothing the user
// typed can become live HTML. The URL capture allows one level of
// balanced parentheses, e.g. https://en.wikipedia.org/wiki/Foo_(bar).
const inline = (text) => {
let t = escapeHtml(text);
// images: ![alt](url) or ![alt](url "title")
t = t.replace(/!\[([^\]]*)\]\(((?:[^()\s]|\([^)\s]*\))*)(?:\s+"([^"]*)")?\)/g,
(m, alt, url, title) => {
const tAttr = title ? ` title="${title}"` : "";
return `<img src="${sanitizeUrl(url)}" alt="${alt}"${tAttr}>`;
});
// links: [text](url) or [text](url "title")
t = t.replace(/\[([^\]]+)\]\(((?:[^()\s]|\([^)\s]*\))*)(?:\s+"([^"]*)")?\)/g,
(m, txt, url, title) => {
const tAttr = title ? ` title="${title}"` : "";
return `<a href="${sanitizeUrl(url)}" target="_blank" rel="noopener noreferrer"${tAttr}>${txt}</a>`;
});
// bold: **text** or __text__
t = t.replace(/\*\*([^*]+?)\*\*/g, "<strong>$1</strong>");
t = t.replace(/__([^_]+?)__/g, "<strong>$1</strong>");
// strikethrough: ~~text~~
t = t.replace(/~~([^~]+?)~~/g, "<del>$1</del>");
// italic: *text* (avoid *** by requiring non-star borders)
t = t.replace(/(^|[^*])\*([^*]+?)\*(?!\*)/g, "$1<em>$2</em>");
// italic: _text_ (skip word-internal underscores like file_name)
t = t.replace(/(^|[^_\w])_([^_]+?)_(?!\w)/g, "$1<em>$2</em>");
return t;
};
const lines = src.split("\n");
const out = [];
const n = lines.length;
let i = 0;
const isToken = (s) => /^\u0000\d+\u0000$/.test(s);
while (i < n) {
const line = lines[i];
// Blank line separates blocks.
if (/^\s*$/.test(line)) { i++; continue; }
// A stashed block (e.g. a fenced code block) sitting on its own line.
if (isToken(line)) { out.push(line); i++; continue; }
// ATX heading: # Title (optional closing hashes)
const h = line.match(/^(#{1,6})\s+(.*?)(?:\s+#{1,6})?$/);
if (h) {
const lvl = h[1].length;
out.push("<h" + lvl + ">" + inline(h[2]) + "</h" + lvl + ">");
i++; continue;
}
// Horizontal rule: --- / *** / ___
if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
out.push("<hr>");
i++; continue;
}
// Blockquote: > ...
if (/^>{1}\s?/.test(line)) {
const quote = [];
while (i < n && /^>{1}\s?/.test(lines[i])) {
quote.push(inline(lines[i].replace(/^>{1}\s?/, "")));
i++;
}
out.push("<blockquote>" + quote.join("<br>") + "</blockquote>");
continue;
}
// Unordered list: - / * / + (supports task lists: - [ ] / - [x])
if (/^\s*[-*+]\s+/.test(line)) {
const items = [];
while (i < n) {
const m = lines[i].match(/^\s*[-*+]\s+(.*)$/);
if (m) {
const task = m[1].match(/^\[([ xX])\]\s+(.*)$/);
if (task) {
const checked = /x/i.test(task[1]);
items.push(
'<li class="cr-task"><input type="checkbox" disabled' +
(checked ? " checked" : "") + "> " + inline(task[2])
);
} else {
items.push("<li>" + inline(m[1]));
}
i++; continue;
}
// Lazy continuation (indented) of the previous item.
if (/^\s+\S/.test(lines[i]) && items.length) {
items[items.length - 1] += "<br>" + inline(lines[i].trim());
i++; continue;
}
break;
}
out.push("<ul>" + items.map((li) => li + "</li>").join("") + "</ul>");
continue;
}
// Ordered list: 1.
if (/^\s*\d+\.\s+/.test(line)) {
const items = [];
while (i < n) {
const m = lines[i].match(/^\s*\d+\.\s+(.*)$/);
if (m) { items.push("<li>" + inline(m[1])); i++; continue; }
if (/^\s+\S/.test(lines[i]) && items.length) {
items[items.length - 1] += "<br>" + inline(lines[i].trim());
i++; continue;
}
break;
}
out.push("<ol>" + items.map((li) => li + "</li>").join("") + "</ol>");
continue;
}
// Paragraph: gather consecutive lines until a block boundary.
const para = [line];
i++;
while (i < n) {
const l = lines[i];
if (/^\s*$/.test(l)) break;
if (isToken(l)) break;
if (/^#{1,6}\s+/.test(l)) break;
if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(l)) break;
if (/^>{1}\s?/.test(l)) break;
if (/^\s*[-*+]\s+/.test(l)) break;
if (/^\s*\d+\.\s+/.test(l)) break;
para.push(l);
i++;
}
out.push("<p>" + para.map(inline).join("<br>") + "</p>");
}
let html = out.join("\n");
// Restore stashed HTML.
html = html.replace(/\u0000(\d+)\u0000/g, (m, idx) => stash[+idx] || "");
return html;
}
window.renderMarkdown = renderMarkdown;
})();

View File

@ -20,8 +20,6 @@ const syncedSubOptions = [
// "hide_completed",
"num_todo_items",
"hover_preview",
// "scheduledReminder",
// "scheduledReminderTime",
"customCardStyles",
"imageSize",
"cardRoundness",
@ -38,8 +36,6 @@ const syncedSubOptions = [
const localSwitches = [];
const fontsDropdownStateKey = "fonts_dropdown_open";
//const apiurl = "http://localhost:3000";
// const apiurl = "https://canvasrefined.diditupe.dev";
const apiurl = "none";
const defaultOptions = {
@ -77,6 +73,7 @@ const defaultOptions = {
"assignment_date_format": false,
"dashboard_notes": false,
"dashboard_notes_text": "",
"dashboard_notes_mode": "edit",
"better_todo": false,
"better_sidebar": false,
"sidebar_scale": 100,
@ -128,8 +125,6 @@ const defaultOptions = {
"card_method_date": false,
"card_method_dashboard": true,
"card_limit": 25,
// "scheduledReminder": false,
// "scheduledReminderTime": { "hour": "09", "minute": "00" },
"imageSize": 100,
"cardRoundness": 5,
"cardSpacing": 0,
@ -229,14 +224,6 @@ function setupAutoDarkInput(initial, time) {
});
}
// 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");
@ -433,6 +420,299 @@ function toggleSubOptionsVisibility(option, isOn) {
}
}
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 = {
@ -462,7 +742,6 @@ function setup() {
"customBackgroundDaily",
"customBackgroundNasaDaily",
"fitImageToScreen",
// "scheduledReminder",
"customCardStyles",
],
tabs: {
@ -528,10 +807,6 @@ function setup() {
identifier: "custom_styles",
setup: (initial) => setupCustomStyle(initial),
},
// {
// identifier: "scheduledReminderTime",
// setup: (initial) => setupScheduledReminderInput(initial),
// },
{
identifier: "imageSize",
setup: (initial) => setupImageSizeInput(initial),
@ -616,12 +891,6 @@ function setup() {
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();
});
@ -637,15 +906,6 @@ function setup() {
});
})
/*
// 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 => {
@ -671,6 +931,13 @@ function setup() {
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 () {
@ -743,7 +1010,6 @@ function setup() {
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) {
@ -776,7 +1042,6 @@ function setup() {
}
}
document.querySelector("#export-output").value = JSON.stringify(final);
//});
});
});
});
@ -843,14 +1108,6 @@ function setup() {
})
});
/*
['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 => {
@ -879,8 +1136,6 @@ function setup() {
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++) {
@ -896,7 +1151,6 @@ function setup() {
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");
@ -929,15 +1183,8 @@ function setup() {
});
// 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) => {
@ -955,12 +1202,7 @@ function setup() {
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", () => {
@ -1082,37 +1324,6 @@ function displayThemeSubmissionForm() {
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 = {};
@ -1188,7 +1399,6 @@ function themeSortFn(method) {
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) => {
@ -1244,110 +1454,8 @@ 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"]);
@ -1391,234 +1499,8 @@ function saveCurrentTheme() {
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 += `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6.979 3.074a6 6 0 0 1 4.988 1.425l.037 .033l.034 -.03a6 6 0 0 1 4.733 -1.44l.246 .036a6 6 0 0 1 3.364 10.008l-.18 .185l-.048 .041l-7.45 7.379a1 1 0 0 1 -1.313 .082l-.094 -.082l-7.493 -7.422a6 6 0 0 1 3.176 -10.215z" /></svg>`;
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 = `<div id="themes-empty">Nothing here</div>`;
}
document.getElementById("premade-themes-pagenum").textContent = current_page_num + " of " + maxPage;
// set the submit theme button to the first custom card image
try {
const sync = await chrome.storage.sync.get("custom_cards");
const exports = await getExport(sync, ["custom_cards"]);
document.getElementById("theme-button-img").style.background = `linear-gradient(#00000070, #00000070), url(${exports["custom_cards"][0]}) no-repeat center center / cover`;
} catch (e) {
console.log(e);
}
displaySavedThemes();
}
function displayThemeListOld(pageDir = 0) {
//const keys = Object.keys(themes);
document.getElementById("theme-current-sort").textContent = current_sort;
const perPage = 24;
const maxPage = Math.ceil(allThemes.length / perPage);
if (pageDir === -1 && current_page_num > 1) current_page_num--;
if (pageDir === 1 && current_page_num < maxPage) current_page_num++;
let container = document.getElementById("premade-themes");
container.textContent = "";
let start = (current_page_num - 1) * perPage, end = start + perPage;
allThemes.forEach((theme, index) => {
if (index < start || index >= end) return;
let themeBtn = makeElement("button", container, { "className": "theme-button" });
themeBtn.classList.add("customization-button");
if (!themeBtn.style.background) themeBtn.style.backgroundImage = "linear-gradient(#00000070, #00000070), url(" + theme.preview + ")";
let split = theme.title.split(" by ");
makeElement("p", themeBtn, {"className": "theme-button-title", "textContent": split[0] });
makeElement("p", themeBtn, {"className": "theme-button-creator", "textContent": split[1] });
themeBtn.addEventListener("click", () => {
const allOptions = syncedSwitches.concat(syncedSubOptions).concat(["dark_preset", "custom_cards", "custom_font", "gpa_calc_bounds", "card_colors"]);
chrome.storage.sync.get(allOptions, sync => {
chrome.storage.local.get(["previous_theme"], async local => {
if (local["previous_theme"] === null) {
let previous = await getExport(sync, allOptions);
chrome.storage.local.set({ "previous_theme": previous });
}
importTheme(theme.exports);
});
});
});
});
document.getElementById("premade-themes-pagenum").textContent = current_page_num + " of " + maxPage;
displaySavedThemes();
}
function displayThemeSearchList(themesToShow, pageDir = 0) {
document.getElementById("theme-current-sort").textContent = current_sort;
@ -1852,73 +1734,7 @@ function setCustomImage(key, val) {
function displayAdvancedCards() {
sendFromPopup("getCards");
chrome.storage.sync.get(["custom_cards", "custom_cards_2"], storage => {
// document.querySelector(".advanced-cards").innerHTML = '<div id="advanced-current"></div><div id="advanced-past"><h2>Past Courses</h2></div>';
// const keys = storage["custom_cards"] ? Object.keys(storage["custom_cards"]) : [];
// if (keys.length > 0) {
// let currentEnrollment = keys.reduce((max, key) => storage["custom_cards"][key]?.eid > max ? storage["custom_cards"][key].eid : max, -1);
// keys.forEach(key => {
// let term = document.querySelector("#advanced-past");
// if (storage["custom_cards"][key].eid === currentEnrollment) {
// term = document.querySelector("#advanced-current");
// }
// let card = storage["custom_cards"][key];
// let card_2 = storage["custom_cards_2"][key] || {};
// if (!card || !card_2 || !card_2["links"] || card_2["links"]["custom"]) {
// console.log(key + " error...");
// console.log("card = ", card, "card_2", card_2, "links", card_2["links"]);
// } else {
// let container = makeElement("div", term, { "className": "custom-card" });
// container.classList.add("option-container");
// container.innerHTML = '<div class="custom-card-header"><p class="custom-card-title"></p><div class="custom-card-hide"><p class="custom-key">Hide</p></div></div><div class="custom-card-inputs"><div class="custom-card-left"><div class="custom-card-image"><span class="custom-key">Image</span></div><div class="custom-card-name"><span class="custom-key">Name</span></div><div class="custom-card-code"><span class="custom-key">Code</span></div></div><div class="custom-links-container"><p class="custom-key">Links</p><div class="custom-links"></div></div></div>';
// let imgInput = makeElement("input", container.querySelector(".custom-card-image"), { "className": "card-input" });
// let nameInput = makeElement("input", container.querySelector(".custom-card-name"), { "className": "card-input" });
// let codeInput = makeElement("input", container.querySelector(".custom-card-code"), { "className": "card-input" });
// let hideInput = makeElement("input", container.querySelector(".custom-card-hide"), { "className": "card-input-checkbox" });
// imgInput.placeholder = "Image url";
// nameInput.placeholder = "Custom name";
// codeInput.placeholder = "Custom code";
// hideInput.type = "checkbox";
// imgInput.value = card.img;
// nameInput.value = card.name;
// codeInput.value = card.code;
// hideInput.checked = card.hidden;
// if (card.img && card.img !== "") container.style.background = "linear-gradient(155deg, #1e1e1eeb 20%, #1e1e1ecc), url(\"" + card.img + "\") center / cover no-repeat";
// imgInput.addEventListener("change", e => {
// setCustomImage(key, e.target.value);
// container.style.background = e.target.value === "" ? "var(--containerbg)" : "linear-gradient(155deg, #1e1e1eeb 20%, #1e1e1ecc), url(\"" + e.target.value + "\") center / cover no-repeat";
// });
// nameInput.addEventListener("change", function (e) { updateCards(key, { "name": e.target.value }) });
// codeInput.addEventListener("change", function (e) { updateCards(key, { "code": e.target.value }) });
// hideInput.addEventListener("change", function (e) { updateCards(key, { "hidden": e.target.checked }) });
// container.querySelector(".custom-card-title").textContent = card.default;
// for (let i = 0; i < 4; i++) {
// let customLink = makeElement("input", container.querySelector(".custom-links"), { "className": "card-input" });
// customLink.value = card_2.links[i].is_default ? "default" : card_2.links[i].path;
// customLink.addEventListener("change", function (e) {
// chrome.storage.sync.get("custom_cards_2", storage => {
// let newLinks = storage.custom_cards_2[key].links;
// if (e.target.value === "" || e.target.value === "default") {
// console.log("this value is empty....")
// //newLinks[i] = { "type": storage.custom_cards_2[key].links.default[i].type, "default": true };
// newLinks[i] = { "default": newLinks[i].default, "is_default": true, "path": newLinks[i].default };
// customLink.value = "default";
// } else {
// //newLinks[i] = { "type": getLinkType(e.target.value), "path": e.target.value, "default": false };
// let val = e.target.value;
// if (!e.target.value.includes("https://") && e.target.value !== "none") val = "https://" + val;
// newLinks[i] = { "default": newLinks[i].default, "is_default": false, "path": val };
// customLink.value = val;
// }
// chrome.storage.sync.set({ "custom_cards_2": { ...storage.custom_cards_2, [key]: { ...storage.custom_cards_2[key], "links": newLinks } } })
// });
// });
// }
// };
// });
// } else {
// document.querySelector(".advanced-cards").innerHTML = `<div class="option-container"><h3>Couldn't find your cards!<br/>You may need to refresh your Canvas page and/or this menu page.<br/><br/>If you're having issues please contact me - sandlerguy5@gmail.com</h3></div>`;
// }
const cardGrid = document.getElementById("card-grid");
if (!cardGrid) {
@ -2333,4 +2149,4 @@ async function sendFromPopup(message, options = {}) {
})
return await response;
}
}

View File

@ -25,7 +25,7 @@
"content_scripts": [
{
"matches": ["https://*/*"],
"js": ["css/darkmodecss.js", "js/backgrounds.js", "js/content.js"],
"js": ["css/darkmodecss.js", "js/backgrounds.js", "js/markdown.js", "js/content.js"],
"css": ["css/content.css"],
"run_at": "document_start"
}