Copy HTML to Anki (Refresh Before Each Copy)

Always refresh the page before copying HTML to Anki. You can choose normal or random ID via floating menu or keyboard shortcut. After reload, it auto-copies once then clears the flag so you can repeat when needed.

// ==UserScript==
// @name         Copy HTML to Anki (Refresh Before Each Copy)
// @namespace    http://tampermonkey.net/
// @version      5.0
// @description  Always refresh the page before copying HTML to Anki. You can choose normal or random ID via floating menu or keyboard shortcut. After reload, it auto-copies once then clears the flag so you can repeat when needed.
// @author       nabe
// @match        https://cards.ucalgary.ca/card/*
// @grant        GM_xmlhttpRequest
// @connect      localhost
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // LOCALSTORAGE KEY used to store whether we want to run "copyHtmlToAnki"
    // after the page reloads, and whether we want random ID or not.
    const REFRESH_COPY_KEY = 'doRefreshCopyMode';
    // possible values: "normal" or "random" (or null/undefined if not set)

    /********************************************************
     * 1) Check localStorage on load. If set => do the copy.
     ********************************************************/
    const pendingMode = localStorage.getItem(REFRESH_COPY_KEY);
    if (pendingMode) {
        console.log('[RefreshAnki] Detected pendingMode =', pendingMode);

        // Clear so we only do it once
        localStorage.removeItem(REFRESH_COPY_KEY);

        // Actually do the copy with or without random ID
        if (pendingMode === 'random') {
            copyHtmlToAnki(true);
        } else {
            copyHtmlToAnki(false);
        }
    }

    /********************************************************
     * 2) MAIN FUNCTION - Copy HTML to Anki
     ********************************************************/
    function copyHtmlToAnki(generateRandomID = false) {
        console.log(`[RefreshAnki] copyHtmlToAnki() called. generateRandomID = ${generateRandomID}`);

        // Helper: convert relative URLs to absolute
        function makeAbsolute(url) {
            return new URL(url, document.baseURI).href;
        }

        // Helper: random numeric string
        function generateRandomString(length) {
            let result = '';
            const characters = '0123456789';
            for (let i = 0; i < length; i++) {
                result += characters.charAt(Math.floor(Math.random() * characters.length));
            }
            return result;
        }

        // Clone the entire document
        let docClone = document.documentElement.cloneNode(true);

        // Convert all [src] and [href] to absolute
        let elements = docClone.querySelectorAll('[src], [href]');
        elements.forEach(el => {
            if (el.hasAttribute('src')) {
                el.setAttribute('src', makeAbsolute(el.getAttribute('src')));
            }
            if (el.hasAttribute('href')) {
                el.setAttribute('href', makeAbsolute(el.getAttribute('href')));
            }
        });

        // Example extractions (adjust selectors as needed):
        let frontElement = docClone.querySelector('.container.card');
        let frontField = frontElement ? frontElement.innerHTML : '';

        let frontImageUrl = docClone.querySelector('.group.img a.img-fill')?.getAttribute('href') || '';

        let photoQuestionElement = docClone.querySelector('.solution.container .photo-question');
        let photoQuestion = photoQuestionElement ? photoQuestionElement.outerHTML : '';

        let questionField = docClone.querySelector('form.question h3')?.innerText.trim() || '';

        let optionField = Array.from(docClone.querySelectorAll('.options .option'))
            .map(opt => opt.innerText.trim())
            .filter(txt => txt)
            .map(txt => `<li>${txt}</li>`)
            .join('') || '';

        let backField = Array.from(docClone.querySelectorAll('.options .option.correct'))
            .map(opt => opt.innerText.trim())
            .filter(txt => txt)
            .map(txt => `<li>${txt}</li>`)
            .join('') || '';

        let extraElement = docClone.querySelector('.results.container.collected .feedback-container .text');
        let extraField = extraElement ? extraElement.innerHTML : '';
        let extraFieldText = extraElement ? extraElement.innerText.trim() : '';

        let webpageURL = window.location.href;

        // Build unique ID
        let uniqueID = generateRandomID
            ? generateRandomString(10)
            : questionField + backField + extraField + photoQuestion;

        // Construct note fields
        let noteFields = {
            "Front": frontField + "<br>" + photoQuestion,
            "Question": questionField,
            "Options": `<div class="psol">${optionField}</div>`,
            "Back": `<div class="psol">${backField}</div>`,
            "Feedback": extraFieldText,
            "Extra": extraField,
            "Link": `<a href="${webpageURL}">Link To Card</a>`,
            "UniqueID": uniqueID,
            "Front Image": `<img src="${frontImageUrl}">`
        };

        console.log('[RefreshAnki] Sending note fields to AnkiConnect:', noteFields);

        // Send to AnkiConnect
        GM_xmlhttpRequest({
            method: "POST",
            url: "http://localhost:8765",
            data: JSON.stringify({
                action: "addNote",
                version: 6,
                params: {
                    note: {
                        deckName: "Default",
                        modelName: "uofcCard",
                        fields: noteFields,
                        tags: [webpageURL]
                    }
                }
            }),
            headers: { "Content-Type": "application/json" },
            onload: function(response) {
                console.log("Response from AnkiConnect:", response);
                if (response.status === 200) {
                    console.log("Successfully added note to Anki!");
                } else {
                    console.error("Failed to send note fields to Anki.");
                }
            }
        });
    }

    /********************************************************
     * 3) REFRESH-THEN-COPY HELPER
     ********************************************************/
    // Instead of copying immediately, we set localStorage
    // so after reload the script calls copyHtmlToAnki().

    function refreshThenCopy(generateRandomID) {
        const mode = generateRandomID ? 'random' : 'normal';
        console.log(`[RefreshAnki] refreshThenCopy() was called, setting doRefreshCopyMode=${mode} then reloading...`);
        localStorage.setItem(REFRESH_COPY_KEY, mode);
        location.reload();
    }

    /********************************************************
     * 4) OPTIONAL KEYBOARD SHORTCUT => Refresh & Copy
     ********************************************************/
    // If you prefer a direct copy (without refresh) on the shortcut,
    // then just call copyHtmlToAnki(true). But you specifically want
    // a refresh each time, so let's do it that way:
    document.addEventListener('keydown', function(e) {
        if (e.ctrlKey && e.shiftKey && e.code === 'KeyY') {
            // Refresh page, then copy with random ID
            refreshThenCopy(true);
        }
    });

    /********************************************************
     * 5) FLOATING MENU => Refresh & Copy
     ********************************************************/
    const menuContainer = document.createElement('div');
    menuContainer.style.position = 'fixed';
    menuContainer.style.top = '10px';
    menuContainer.style.left = '10px';
    menuContainer.style.zIndex = 9999;
    menuContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
    menuContainer.style.border = '1px solid #ccc';
    menuContainer.style.padding = '5px';
    menuContainer.style.borderRadius = '5px';
    menuContainer.style.fontFamily = 'sans-serif';
    menuContainer.style.fontSize = '14px';

    const title = document.createElement('div');
    title.textContent = 'Anki Refresh Menu';
    title.style.fontWeight = 'bold';
    title.style.marginBottom = '5px';
    menuContainer.appendChild(title);

    // Button 1: Refresh + Copy (No Random ID)
    const btnNormal = document.createElement('button');
    btnNormal.textContent = 'Refresh & Copy (No Random ID)';
    btnNormal.style.display = 'block';
    btnNormal.style.marginBottom = '5px';
    btnNormal.onclick = () => refreshThenCopy(false);
    menuContainer.appendChild(btnNormal);

    // Button 2: Refresh + Copy (Random ID)
    const btnRandom = document.createElement('button');
    btnRandom.textContent = 'Refresh & Copy (Random ID)';
    btnRandom.style.display = 'block';
    btnRandom.onclick = () => refreshThenCopy(true);
    menuContainer.appendChild(btnRandom);

    document.body.appendChild(menuContainer);

})();