mirror of
https://github.com/GuySandler/CanvasRefined.git
synced 2026-09-24 05:45:10 +02:00
todolist preview
This commit is contained in:
parent
f26cac0e9c
commit
765ecbf2fa
@ -54,7 +54,7 @@
|
|||||||
"message": "Max. Anzahl Elemente: "
|
"message": "Max. Anzahl Elemente: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "Bewegen Sie den Mauszeiger, um Details in der Vorschau anzuzeigen"
|
"message": "Vorschau beim Überfahren"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "Abgeschlossene ausblenden"
|
"message": "Abgeschlossene ausblenden"
|
||||||
|
|||||||
@ -84,7 +84,7 @@
|
|||||||
"message": "Max Items: "
|
"message": "Max Items: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "Hover To Preview Details"
|
"message": "Previews on hover"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "Hide Completed"
|
"message": "Hide Completed"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "Artículos máximos: "
|
"message": "Artículos máximos: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "Pasa el cursor para obtener una vista previa de los detalles"
|
"message": "Vista previa al pasar el cursor"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "Ocultar completado"
|
"message": "Ocultar completado"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "Nombre maximal d'articles : "
|
"message": "Nombre maximal d'articles : "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "Survolez pour prévisualiser les détails"
|
"message": "Aperçu au survol"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "Masquer terminé"
|
"message": "Masquer terminé"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "Numero massimo di articoli: "
|
"message": "Numero massimo di articoli: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "Passa il mouse per visualizzare l'anteprima dei dettagli"
|
"message": "Anteprima al passaggio del mouse"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "Nascondi Completato"
|
"message": "Nascondi Completato"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "最大アイテム数: "
|
"message": "最大アイテム数: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "詳細をプレビューするにはマウスを移動します"
|
"message": "ホバーでプレビュー"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "完了を非表示"
|
"message": "完了を非表示"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "Itens máximos: "
|
"message": "Itens máximos: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "Passe o mouse para visualizar os detalhes"
|
"message": "Pré-visualização ao passar o cursor"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "Ocultar Concluído"
|
"message": "Ocultar Concluído"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "Максимальное количество предметов: "
|
"message": "Максимальное количество предметов: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "Наведите курсор, чтобы просмотреть детали"
|
"message": "Предпросмотр при наведении"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "Скрыть Завершено"
|
"message": "Скрыть Завершено"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "Max artiklar: "
|
"message": "Max artiklar: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "Håll muspekaren för att förhandsgranska detaljer"
|
"message": "Förhandsgranskning vid hover"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "Göm slutförd"
|
"message": "Göm slutförd"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "最大物品数: "
|
"message": "最大物品数: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "将鼠标悬停以预览详细信息"
|
"message": "悬停预览"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "隐藏已完成"
|
"message": "隐藏已完成"
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"message": "最大物品数: "
|
"message": "最大物品数: "
|
||||||
},
|
},
|
||||||
"hover_preview": {
|
"hover_preview": {
|
||||||
"message": "将鼠标悬停以预览详细信息"
|
"message": "懸停預覽"
|
||||||
},
|
},
|
||||||
"hide_completed": {
|
"hide_completed": {
|
||||||
"message": "隐藏已完成"
|
"message": "隐藏已完成"
|
||||||
|
|||||||
@ -125,10 +125,10 @@
|
|||||||
#canvasrefined-announcement-list {margin-bottom:1.0625rem;}
|
#canvasrefined-announcement-list {margin-bottom:1.0625rem;}
|
||||||
.canvasrefined-todo-item-loader {margin-top: 18px;}
|
.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-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-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;}
|
.canvasrefined-preview-text {text-overflow: ellipsis;color: var(--bctext-0, #000);}
|
||||||
.canvasrefined-preview-title {font-weight:bold;font-size:14px;}
|
.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, #fff 50%, transparent)}
|
.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 {height: 21px; width: 21px;padding: 0px}
|
||||||
.canvasrefined-link-image:hover {filter: brightness(66%)}
|
.canvasrefined-link-image:hover {filter: brightness(66%)}
|
||||||
.canvasrefined-gpa-header {font-size:16px;font-weight: bold;padding: 0;margin: 0;}
|
.canvasrefined-gpa-header {font-size:16px;font-weight: bold;padding: 0;margin: 0;}
|
||||||
|
|||||||
@ -215,11 +215,6 @@
|
|||||||
<span class="option-name" data-i18n="better_todo">Better Todo List</span>
|
<span class="option-name" data-i18n="better_todo">Better Todo List</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sub-options">
|
<div class="sub-options">
|
||||||
<div class="sub-option" style="display:none;">
|
|
||||||
<input type="checkbox" id="hover_preview" name="hover_preview">
|
|
||||||
<label for="hover_preview" class="sub-text" data-i18n="hover_preview">Hover to preview
|
|
||||||
details</label> <!-- TODO: Later -->
|
|
||||||
</div>
|
|
||||||
<div class="sub-option">
|
<div class="sub-option">
|
||||||
<input type="checkbox" id="todo_separate_scrollbar" name="todo_separate_scrollbar">
|
<input type="checkbox" id="todo_separate_scrollbar" name="todo_separate_scrollbar">
|
||||||
<label for="todo_separate_scrollbar" class="sub-text" data-i18n="todo_separate_scrollbar">Separate Scrollbar</label>
|
<label for="todo_separate_scrollbar" class="sub-text" data-i18n="todo_separate_scrollbar">Separate Scrollbar</label>
|
||||||
@ -232,6 +227,10 @@
|
|||||||
<input type="checkbox" id="todo_full_height" name="todo_full_height">
|
<input type="checkbox" id="todo_full_height" name="todo_full_height">
|
||||||
<label for="todo_full_height" class="sub-text" data-i18n="todo_full_height">Force Full Height</label> <!-- TO REMOVE and replace with full height -->
|
<label for="todo_full_height" class="sub-text" data-i18n="todo_full_height">Force Full Height</label> <!-- TO REMOVE and replace with full height -->
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sub-option">
|
||||||
|
<input type="checkbox" id="hover_preview" name="hover_preview">
|
||||||
|
<label for="hover_preview" class="sub-text" data-i18n="hover_preview">Previews on hover</label>
|
||||||
|
</div>
|
||||||
<div class="sub-option">
|
<div class="sub-option">
|
||||||
<input type="checkbox" id="todo_hide_feedback" name="todo_hide_feedback">
|
<input type="checkbox" id="todo_hide_feedback" name="todo_hide_feedback">
|
||||||
<label for="todo_hide_feedback" class="sub-text" data-i18n="todo_hide_feedback">Hide Recent Feedback</label>
|
<label for="todo_hide_feedback" class="sub-text" data-i18n="todo_hide_feedback">Hide Recent Feedback</label>
|
||||||
|
|||||||
129
js/content.js
129
js/content.js
@ -2746,6 +2746,128 @@ function applyTodoAlternateColors() {
|
|||||||
todoAltStyleEl.textContent = `:root{--cr-todo-icon:${color};}`;
|
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 = '<p class="canvasrefined-preview-title"></p><p class="canvasrefined-preview-text"></p>';
|
||||||
|
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) {
|
function populateAssignments(iscompleted = false) {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0,0,0,0);
|
today.setHours(0,0,0,0);
|
||||||
@ -2870,6 +2992,7 @@ function populateAssignments(iscompleted = false) {
|
|||||||
openTaskForEdit(item);
|
openTaskForEdit(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
attachTodoHoverPreview(assignment, item);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (document.getElementById("better-todo-see-more")) {
|
if (document.getElementById("better-todo-see-more")) {
|
||||||
@ -2949,6 +3072,7 @@ function populateAnnouncements() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
attachTodoHoverPreview(announcement, item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3634,9 +3758,10 @@ async function loadBetterTodo() {
|
|||||||
delay = setTimeout(async () => {
|
delay = setTimeout(async () => {
|
||||||
if (listItem.classList.contains("canvasrefined-todo-hover")) {
|
if (listItem.classList.contains("canvasrefined-todo-hover")) {
|
||||||
previewTitle.textContent = item.plannable.title;
|
previewTitle.textContent = item.plannable.title;
|
||||||
// custom assignment
|
// custom assignment (planner note): preview its description/details
|
||||||
if (customItem) {
|
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 {
|
} else {
|
||||||
console.log(item);
|
console.log(item);
|
||||||
let found = false;
|
let found = false;
|
||||||
|
|||||||
@ -49,7 +49,7 @@ const exportCardColorToggles = ["gradient_cards", "disable_color_overlay"];
|
|||||||
const exportCardStyles = ["customCardStyles", "imageSize", "cardRoundness", "cardSpacing", "cardWidth", "cardHeight"];
|
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 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 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 exportGpa = ["gpa_calc", "gpa_calc_prepend", "gpa_calc_cumulative", "gpa_calc_weighted"];
|
||||||
const exportBackground = ["customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "customBackgroundNasaDaily", "fitImageToScreen", "bg_opacity", "sidebar_opacity", "bg_blur", "sidebar_blur"];
|
const exportBackground = ["customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "customBackgroundNasaDaily", "fitImageToScreen", "bg_opacity", "sidebar_opacity", "bg_blur", "sidebar_blur"];
|
||||||
// Master "On/off toggles" = every visual toggle (no GPA, no dark-mode schedule,
|
// Master "On/off toggles" = every visual toggle (no GPA, no dark-mode schedule,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user