Canvas Quiz Answer Identifier (Subtle Shadow + Edge Hover)

Stealthily highlights correct Canvas quiz answers with a faint right-edge shadow on hover only. Won’t steal focus or trigger LMS cursor alerts. API-based & unobtrusive UX by design. Fully self-contained & auto-loaded on quiz pages only.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Canvas Quiz Answer Identifier (Subtle Shadow + Edge Hover)
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Stealthily highlights correct Canvas quiz answers with a faint right-edge shadow on hover only. Won’t steal focus or trigger LMS cursor alerts. API-based & unobtrusive UX by design. Fully self-contained & auto-loaded on quiz pages only.
// @author       You
// @match        *://*.instructure.com/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    window.addEventListener('load', function () {
        const texts = document.getElementsByClassName("text");

        if (!texts.length) return;

        let questions = [];
        let optionsList = [];

        // Step 1: Extract questions and options
        for (let i = 0; i < texts.length; i++) {
            const questionDiv = texts[i].querySelector("div:nth-of-type(2)");
            const questionText = questionDiv?.innerText?.trim() || "";

            const optionLabels = texts[i].getElementsByClassName("answer_label");
            let optionsText = [];
            for (let j = 0; j < optionLabels.length; j++) {
                optionsText.push(optionLabels[j].innerText.trim());
            }

            questions.push(questionText);
            optionsList.push(optionsText);
        }

        // Step 2: Send questions to the API and get answers
        for (let i = 0; i < questions.length; i++) {
            const payload = {
                id: i,
                question: questions[i],
                options: optionsList[i].join(", "),
            };

            fetch("https://canvasquiz-new.uc.r.appspot.com/generate", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(payload),
            })
                .then((res) => {
                    if (!res.ok) throw new Error("Network response error");
                    return res.json();
                })
                .then((data) => {
                    const correctAnswer = data.answer?.trim();
                    if (!correctAnswer) return;

                    const answerLabels = texts[i].getElementsByClassName("answer_label");
                    for (let j = 0; j < answerLabels.length; j++) {
                        const label = answerLabels[j];
                        const labelText = label.innerText.trim();

                        if (labelText === correctAnswer) {
                            label.style.position = "relative";

                            label.addEventListener("mouseenter", () => {
                                let shadowMarker = document.createElement("div");
                                shadowMarker.className = "hover-shadow-marker";
                                shadowMarker.style.position = "absolute";
                                shadowMarker.style.top = "50%";
                                shadowMarker.style.left = "100%"; // outside the label
                                shadowMarker.style.transform = "translateY(-50%) translateX(2px)";
                                shadowMarker.style.width = "8px";
                                shadowMarker.style.height = "20px";
                                shadowMarker.style.boxShadow = "0 0 6px rgba(0, 0, 0, 0.1)";
                                shadowMarker.style.borderRadius = "3px";
                                shadowMarker.style.pointerEvents = "none"; // don't interfere
                                label.appendChild(shadowMarker);
                            });

                            label.addEventListener("mouseleave", () => {
                                const marker = label.querySelector(".hover-shadow-marker");
                                if (marker) marker.remove();
                            });
                        }
                    }
                })
                .catch((err) => {
                    console.warn("Answer API error:", err);
                });
        }
    });
})();