diff --git a/README.md b/README.md index 9437edf..ef7cb09 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,10 @@ Actually Better Canvas adds more with more to come! - Popup UI revamp - NEW Better todo list - better sidebar +- simplified UI ## Planned Features (by priority) - widgets (music, timer) -- better notes - auto rotate theme + theme history + fix theme submissions - mail assistent + ui revamp - better calender (+ calender sync) @@ -73,8 +73,6 @@ Actually Better Canvas adds more with more to come! - fix darkmode fixer - make sidebar and todo list work on all pages that need them - grade history with graph -- preview font -- button to remove all card images ## Extra features that might be added: - card grade position, card outline @@ -89,7 +87,8 @@ Actually Better Canvas adds more with more to come! - flashcards - goals - Scheduled Reminder Popups -- simplified UI +- preview font +- button to remove all card images and undo ## Community suggestions (maybe will be done at some point) - when opening assignments it will show you "if you get a 0 on this your grade will be _" diff --git a/css/content.css b/css/content.css index 55f34a8..05169d6 100644 --- a/css/content.css +++ b/css/content.css @@ -47,7 +47,20 @@ } .bettercanvas-add-assignment {max-height: 0; overflow:hidden;transition: .3s max-height;} .bettercanvas-custom-open {max-height: 250px} +#better-todo-add-task-menu.bettercanvas-custom-open {max-height: 420px;} .bettercanvas-custom-input {box-sizing: border-box!important; width: 100%!important; height: 32px!important} +#better-todo-new-task-date, +#better-todo-new-task-time { + flex: 1 1 0; + min-width: 0; + width: auto !important; + font-size: 12px; + padding-inline: 6px; +} +#better-todo-new-task-date::-webkit-calendar-picker-indicator { + filter: invert(1) brightness(2); + opacity: .95; +} .bettercanvas-custom-btn {background: #f5f5f5; border: 1px solid #c7cdd1; border-radius:6px; padding: 2px 8px;} .bettercanvas-viewmore-btn {display: block;margin:0 auto;margin-top: 8px;} .bettercanvas-course-percent, .bettercanvas-course-credit {border: 1px solid #ccc; color: var(--ic-brand-font-color-dark)} diff --git a/js/content.js b/js/content.js index fe7b864..3deeeaa 100644 --- a/js/content.js +++ b/js/content.js @@ -1229,6 +1229,241 @@ function updateIndicator(element) { // better todo html betterTodoFilter = "tasks"; let domContainers = {}; + +function formatDateForInput(date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +function formatTimeForInput(date) { + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + return `${hours}:${minutes}`; +} + +function buildPlannerNotePayload(form) { + const title = form.querySelector("#better-todo-new-task-title")?.value?.trim(); + const details = form.querySelector("#better-todo-new-task-details")?.value?.trim(); + const courseIdRaw = form.querySelector("#better-todo-new-task-course")?.value; + const dateValue = form.querySelector("#better-todo-new-task-date")?.value; + const timeValue = form.querySelector("#better-todo-new-task-time")?.value; + + if (!title) { + throw new Error("Task title is required."); + } + + if (!dateValue || !timeValue) { + throw new Error("Please choose both a date and time."); + } + + const localDateTime = new Date(`${dateValue}T${timeValue}:00`); + if (Number.isNaN(localDateTime.getTime())) { + throw new Error("Invalid task date."); + } + + return { + title, + details, + courseId: courseIdRaw ? parseInt(courseIdRaw) : null, + // Canvas accepts local timestamp strings more reliably than UTC ISO strings for planner notes. + todoDate: `${dateValue}T${timeValue}:00`, + }; +} + +async function createCanvasPlannerNote(payload) { + const csrfToken = CSRFtoken(); + const plannerNote = { + title: payload.title, + todo_date: payload.todoDate, + }; + if (payload.details) plannerNote.details = payload.details; + if (payload.courseId) plannerNote.course_id = payload.courseId; + + const attempts = [ + { + headers: { + "content-type": "application/json", + "accept": "application/json", + "X-CSRF-Token": csrfToken, + }, + body: JSON.stringify({ planner_note: plannerNote }), + }, + { + headers: { + "content-type": "application/json", + "accept": "application/json", + "X-CSRF-Token": csrfToken, + }, + body: JSON.stringify(plannerNote), + }, + { + headers: { + "content-type": "application/x-www-form-urlencoded; charset=UTF-8", + "accept": "application/json", + "X-CSRF-Token": csrfToken, + }, + body: (() => { + const formBody = new URLSearchParams(); + formBody.set("planner_note[title]", plannerNote.title); + formBody.set("planner_note[todo_date]", plannerNote.todo_date); + if (plannerNote.details) formBody.set("planner_note[details]", plannerNote.details); + if (plannerNote.course_id) formBody.set("planner_note[course_id]", plannerNote.course_id); + return formBody.toString(); + })(), + }, + ]; + + let lastError = "Canvas rejected task creation."; + for (const attempt of attempts) { + const response = await fetch(domain + "/api/v1/planner_notes", { + method: "POST", + headers: attempt.headers, + body: attempt.body, + }); + + if (response.status === 200 || response.status === 201) { + return response.json(); + } + + try { + const errData = await response.json(); + if (errData?.errors?.length) { + lastError = errData.errors.join(" "); + } else if (errData?.message) { + lastError = errData.message; + } + } catch (_) { + // Keep prior error text when body is not JSON. + } + } + + throw new Error(lastError || "Canvas rejected task creation."); +} + +function fillTaskCourseOptions(courseSelect) { + const cards = options.custom_cards || {}; + const courseColors = options.custom_cards_3 || {}; + const currentCourseId = getCurrentCourseId(); + const entries = Object.entries(cards) + .map(([id, card]) => ({ + id, + label: card?.default || `Course ${id}`, + color: + courseColors?.[String(id)]?.color ?? + courseColors?.[id]?.color ?? + "#c7cdd1", + })) + .sort((a, b) => a.label.localeCompare(b.label)); + + courseSelect.innerHTML = ''; + courseSelect.options[0].dataset.color = "#c7cdd1"; + entries.forEach(entry => { + const option = makeElement("option", courseSelect, { + value: entry.id, + textContent: entry.label, + }); + option.dataset.color = entry.color; + option.style.color = entry.color; + if (currentCourseId && String(currentCourseId) === String(entry.id)) { + option.selected = true; + } + }); +} + +function updateTaskCourseSelectColor(courseSelect) { + const selectedOption = courseSelect?.options?.[courseSelect.selectedIndex]; + const color = selectedOption?.dataset?.color || "#c7cdd1"; + courseSelect.style.borderLeft = `4px solid ${color}`; + courseSelect.style.paddingLeft = "8px"; +} + +function ensureTodoTaskMenu(location, feedbackElement) { + let actionsRow = location.querySelector("#better-todo-actions-row"); + + if (!actionsRow) { + actionsRow = makeElement("div", location, { + id: "better-todo-actions-row", + style: "display:flex;flex-direction:column;gap:8px;margin-top:14px;", + }); + + const addTaskButton = makeElement("button", actionsRow, { + id: "better-todo-add-task-btn", + className: "bettercanvas-custom-btn", + textContent: "+ Add Task", + style: "width:100%;padding:6px 8px;cursor:pointer;", + }); + + const menu = makeElement("div", actionsRow, { + id: "better-todo-add-task-menu", + className: "bettercanvas-add-assignment", + }); + + menu.innerHTML = ` +