diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 6ecce09..7099be0 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -54,7 +54,7 @@ "message": "Max. Anzahl Elemente: " }, "hover_preview": { - "message": "Bewegen Sie den Mauszeiger, um Details in der Vorschau anzuzeigen" + "message": "Vorschau beim Überfahren" }, "hide_completed": { "message": "Abgeschlossene ausblenden" diff --git a/_locales/en/messages.json b/_locales/en/messages.json index ea03c2b..d8bb7f1 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -84,7 +84,7 @@ "message": "Max Items: " }, "hover_preview": { - "message": "Hover To Preview Details" + "message": "Previews on hover" }, "hide_completed": { "message": "Hide Completed" diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 2d289ec..4c10c45 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -54,7 +54,7 @@ "message": "Artículos máximos: " }, "hover_preview": { - "message": "Pasa el cursor para obtener una vista previa de los detalles" + "message": "Vista previa al pasar el cursor" }, "hide_completed": { "message": "Ocultar completado" diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 8b0cd44..28220ca 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -54,7 +54,7 @@ "message": "Nombre maximal d'articles : " }, "hover_preview": { - "message": "Survolez pour prévisualiser les détails" + "message": "Aperçu au survol" }, "hide_completed": { "message": "Masquer terminé" diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 39e7871..48c2d4e 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -54,7 +54,7 @@ "message": "Numero massimo di articoli: " }, "hover_preview": { - "message": "Passa il mouse per visualizzare l'anteprima dei dettagli" + "message": "Anteprima al passaggio del mouse" }, "hide_completed": { "message": "Nascondi Completato" diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 84096b7..4b28470 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -54,7 +54,7 @@ "message": "最大アイテム数: " }, "hover_preview": { - "message": "詳細をプレビューするにはマウスを移動します" + "message": "ホバーでプレビュー" }, "hide_completed": { "message": "完了を非表示" diff --git a/_locales/pt_PT/messages.json b/_locales/pt_PT/messages.json index 907d6b2..413210f 100644 --- a/_locales/pt_PT/messages.json +++ b/_locales/pt_PT/messages.json @@ -54,7 +54,7 @@ "message": "Itens máximos: " }, "hover_preview": { - "message": "Passe o mouse para visualizar os detalhes" + "message": "Pré-visualização ao passar o cursor" }, "hide_completed": { "message": "Ocultar Concluído" diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 5ac7051..182088d 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -54,7 +54,7 @@ "message": "Максимальное количество предметов: " }, "hover_preview": { - "message": "Наведите курсор, чтобы просмотреть детали" + "message": "Предпросмотр при наведении" }, "hide_completed": { "message": "Скрыть Завершено" diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index b4caba0..028e859 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -54,7 +54,7 @@ "message": "Max artiklar: " }, "hover_preview": { - "message": "Håll muspekaren för att förhandsgranska detaljer" + "message": "Förhandsgranskning vid hover" }, "hide_completed": { "message": "Göm slutförd" diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 41deb67..27ee93e 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -54,7 +54,7 @@ "message": "最大物品数: " }, "hover_preview": { - "message": "将鼠标悬停以预览详细信息" + "message": "悬停预览" }, "hide_completed": { "message": "隐藏已完成" diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 41846b6..d6656d4 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -54,7 +54,7 @@ "message": "最大物品数: " }, "hover_preview": { - "message": "将鼠标悬停以预览详细信息" + "message": "懸停預覽" }, "hide_completed": { "message": "隐藏已完成" diff --git a/css/content.css b/css/content.css index 5145a70..9936391 100644 --- a/css/content.css +++ b/css/content.css @@ -125,10 +125,10 @@ #canvasrefined-announcement-list {margin-bottom:1.0625rem;} .canvasrefined-todo-item-loader {margin-top: 18px;} .canvasrefined-todo-item-loader .canvasrefined-skeleton-text {height:12px;margin-top: 8px;margin-bottom:0;margin-left:25px} -.canvasrefined-hover-preview {position: absolute;top: -6px;left: -400px;display: none; background: #fff; color: #000; max-height: 300%; width: 350px; padding:12px;border-radius:5px; overflow: hidden;box-shadow: 0 2px 5px rgba(0,0,0,.3)} -.canvasrefined-preview-text {text-overflow: ellipsis;} -.canvasrefined-preview-title {font-weight:bold;font-size:14px;} -.canvasrefined-hover-preview::after {content:'';position:absolute;width:100%;height: 18px;bottom:0;left:0;background:linear-gradient(0deg, #fff 50%, transparent)} +.canvasrefined-hover-preview {position: absolute;top: -6px;left: -400px;display: none; background: var(--bcbackground-0, #fff); color: var(--bctext-0, #000); max-height: 300%; width: 350px; padding:12px;border-radius:5px; overflow: hidden;box-shadow: 0 2px 5px rgba(0,0,0,.3)} +.canvasrefined-preview-text {text-overflow: ellipsis;color: var(--bctext-0, #000);} +.canvasrefined-preview-title {font-weight:bold;font-size:14px;color: var(--bctext-0, #000);} +.canvasrefined-hover-preview::after {content:'';position:absolute;width:100%;height: 18px;bottom:0;left:0;background:linear-gradient(0deg, var(--bcbackground-0, #fff) 50%, transparent)} .canvasrefined-link-image {height: 21px; width: 21px;padding: 0px} .canvasrefined-link-image:hover {filter: brightness(66%)} .canvasrefined-gpa-header {font-size:16px;font-weight: bold;padding: 0;margin: 0;} diff --git a/html/popup.html b/html/popup.html index 5f125e5..c59ee22 100644 --- a/html/popup.html +++ b/html/popup.html @@ -215,11 +215,6 @@ Better Todo List
-
@@ -232,6 +227,10 @@
+
+ + +
diff --git a/js/content.js b/js/content.js index cc0c14e..57ecdd0 100644 --- a/js/content.js +++ b/js/content.js @@ -2746,6 +2746,128 @@ function applyTodoAlternateColors() { todoAltStyleEl.textContent = `:root{--cr-todo-icon:${color};}`; } +// --- Hover preview (Better Todo List: "Previews on hover") --- +// The todo list is rendered by createTodoSections -> populateAssignments / +// populateAnnouncements (the old loadBetterTodo renderer is no longer called). +// A single shared, body-level tooltip is reused across items so it is never +// clipped by the sidebar's scroll/overflow containers. It reuses the +// .canvasrefined-hover-preview class so existing light/dark styling applies. +let todoPreviewDelay = null; +let todoPreviewToken = 0; +const todoPreviewCache = new Map(); + +function stripHtmlPreview(html) { + if (!html) return ""; + return String(html).replace(/<\/?[^>]+(>|$)/g, " ").replace(/ /g, " ").replace(/\s+/g, " ").trim(); +} + +function getTodoPreviewEl() { + let el = document.getElementById("canvasrefined-todo-preview"); + if (el) return el; + el = document.createElement("div"); + el.id = "canvasrefined-todo-preview"; + el.className = "canvasrefined-hover-preview"; + el.innerHTML = '

'; + el.style.position = "fixed"; + el.style.zIndex = "100001"; + el.style.width = "300px"; + el.style.maxWidth = "90vw"; + el.style.maxHeight = "260px"; + document.body.append(el); + return el; +} + +function positionTodoPreview(el, anchor) { + const r = anchor.getBoundingClientRect(); + const gap = 10; + const pw = el.offsetWidth || 300; + const ph = el.offsetHeight || 160; + let left = r.left - pw - gap; + let top = r.top; + if (left < gap) { + left = r.right + gap; + if (left + pw > window.innerWidth - gap) left = Math.max(gap, window.innerWidth - pw - gap); + } + if (top + ph > window.innerHeight - gap) top = Math.max(gap, window.innerHeight - ph - gap); + el.style.left = left + "px"; + el.style.top = top + "px"; +} + +async function getTodoPreviewText(item) { + const type = item.plannable_type; + const id = item.plannable_id; + const key = type + ":" + id; + if (todoPreviewCache.has(key)) return todoPreviewCache.get(key); + // Custom task (planner note): the description is already on the planner + // item as item.plannable.details, so no API call is needed. + if (type === "planner_note" || (item.planner_override && item.planner_override.custom === true)) { + const raw = item.plannable && item.plannable.details ? item.plannable.details : ""; + const text = stripHtmlPreview(raw) || "No details given"; + todoPreviewCache.set(key, text); + return text; + } + let url = null; + let field = "description"; + if (type === "assignment") { + url = `${domain}/api/v1/courses/${item.course_id}/assignments/${id}`; + } else if (type === "quiz") { + url = `${domain}/api/v1/courses/${item.course_id}/quizzes/${id}`; + } else if (type === "discussion_topic" || type === "announcement") { + url = `${domain}/api/v1/courses/${item.course_id}/discussion_topics/${id}`; + field = "message"; + } + if (!url) { + const text = "No preview available"; + todoPreviewCache.set(key, text); + return text; + } + try { + const data = await getData(url); + const raw = data && data[field] ? data[field] : ""; + const text = stripHtmlPreview(raw) || "No details given"; + todoPreviewCache.set(key, text); + return text; + } catch (e) { + return "Couldn't load preview"; + } +} + +function hideTodoPreview() { + todoPreviewToken++; // cancel any pending async text update + const el = document.getElementById("canvasrefined-todo-preview"); + if (el) el.style.display = "none"; +} + +async function showTodoPreview(anchor, item) { + const token = ++todoPreviewToken; + const el = getTodoPreviewEl(); + const title = el.querySelector(".canvasrefined-preview-title"); + const text = el.querySelector(".canvasrefined-preview-text"); + title.textContent = item.plannable && item.plannable.title ? item.plannable.title : ""; + text.textContent = "Loading…"; + el.style.display = "block"; + positionTodoPreview(el, anchor); + const content = await getTodoPreviewText(item); + if (token !== todoPreviewToken) return; // a newer hover (or hide) superseded this one + if (el.style.display !== "block") return; // user already moved away + text.textContent = content; + positionTodoPreview(el, anchor); // reposition now that the height is known +} + +function attachTodoHoverPreview(anchor, item) { + if (options.hover_preview !== true) return; + anchor.addEventListener("mouseenter", () => { + clearTimeout(todoPreviewDelay); + todoPreviewDelay = setTimeout(() => { + if (anchor.matches(":hover")) showTodoPreview(anchor, item); + }, 250); + }); + anchor.addEventListener("mouseleave", () => { + clearTimeout(todoPreviewDelay); + hideTodoPreview(); + }); +} + function populateAssignments(iscompleted = false) { const today = new Date(); today.setHours(0,0,0,0); @@ -2870,6 +2992,7 @@ function populateAssignments(iscompleted = false) { openTaskForEdit(item); }); } + attachTodoHoverPreview(assignment, item); }); if (document.getElementById("better-todo-see-more")) { @@ -2949,6 +3072,7 @@ function populateAnnouncements() {
`; + attachTodoHoverPreview(announcement, item); }); } @@ -3634,9 +3758,10 @@ async function loadBetterTodo() { delay = setTimeout(async () => { if (listItem.classList.contains("canvasrefined-todo-hover")) { previewTitle.textContent = item.plannable.title; - // custom assignment + // custom assignment (planner note): preview its description/details if (customItem) { - previewText.textContent = "Custom assignment"; + const details = item.plannable && item.plannable.details ? item.plannable.details : ""; + previewText.textContent = details === "" ? "No details given" : details.replace(/<\/?[^>]+(>|$)/g, " "); } else { console.log(item); let found = false; diff --git a/js/popup.js b/js/popup.js index 104c320..cfb606f 100644 --- a/js/popup.js +++ b/js/popup.js @@ -49,7 +49,7 @@ const exportCardColorToggles = ["gradient_cards", "disable_color_overlay"]; const exportCardStyles = ["customCardStyles", "imageSize", "cardRoundness", "cardSpacing", "cardWidth", "cardHeight"]; const exportLayout = ["full_width", "center_cards", "condensed_cards", "equal_height_cards", "remlogo", "hide_new_canvas", "hide_feedback", "tab_icons"]; const exportSidebar = ["better_sidebar", "sidebar_scale"]; -const exportTodo = ["better_todo", "todo_hide_feedback", "todo_full_height", "todo_confetti", "todo_progress_rings", "todo_hr24", "todo_separate_scrollbar", "todo_alternate_colors"]; +const exportTodo = ["better_todo", "todo_hide_feedback", "todo_full_height", "todo_confetti", "todo_progress_rings", "todo_hr24", "todo_separate_scrollbar", "todo_alternate_colors", "hover_preview"]; const exportGpa = ["gpa_calc", "gpa_calc_prepend", "gpa_calc_cumulative", "gpa_calc_weighted"]; const exportBackground = ["customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "customBackgroundNasaDaily", "fitImageToScreen", "bg_opacity", "sidebar_opacity", "bg_blur", "sidebar_blur"]; // Master "On/off toggles" = every visual toggle (no GPA, no dark-mode schedule,