Crowdmark Assignment Saver

Save your crowdmark assignments for local access. Works well with ctrl+P (if it doesn't fit, try changing your paper size/orientation/scale).

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Crowdmark Assignment Saver
// @namespace    local
// @version      1.3
// @description  Save your crowdmark assignments for local access. Works well with ctrl+P (if it doesn't fit, try changing your paper size/orientation/scale).
// @match        https://app.crowdmark.com/*
// @grant        none
// @run-at       document-end
// @license      unlicense
// ==/UserScript==

(function () {
"use strict";

let lastUrl = null;
let btn = null;
function callback() {
    if (lastUrl !== location.href) {
      lastUrl = location.href;
      if(location.pathname.startsWith("/student/assessments")) {
        addButton();
      } else {
        removeButton();
      }
    }
}

window.addEventListener("load", callback);

new MutationObserver(callback).observe(document, { subtree: true, childList: true });

function exportQuestions() {

    const elements = [...document.getElementsByClassName("processed-text")];

    if (!elements.length) {
        alert("No .processed-text elements found.");
        return;
    }

    const combined = elements.map((el, i) => `
<section class="question">
  <h2>Question ${i + 1}</h2>
  ${el.innerHTML}
</section>
`).join("<hr>");

    const html = `
<html>
<head>
${document.head.innerHTML}

<style>
:root{
  --base:#fff;
  --mantle:#fff;
  --surface0:#fff;
  --text:#000;
}

body{
  background:var(--base);
  color:var(--text);
  font-family:system-ui, sans-serif;
  line-height:1.65;
  margin:40px;
}

.question{
  background:var(--base);
  border: none;
  border-radius:10px;
  padding:24px;
  margin-bottom:36px;

  break-inside: avoid;
  page-break-inside: avoid;
}

h2{
  margin-top:0;
  margin-bottom:12px;
  color:var(--text);   /* dark instead of blue */
}

/* PRINT STYLES */
@media print {

  @page{
    margin:12mm;   /* tighter PDF margins */
  }

  body{
    background:white;
    color:black;
    margin:0;      /* avoid double margins */
  }
  h2 {
    color: black;
  }
  hr {
    display: none;
  }

  .question{
    background:none;
    border: none;
    box-shadow:none;

    break-inside: avoid;
    page-break-inside: avoid;
  }

  p, li{
    orphans:3;
    widows:3;
  }

  mjx-container[display="true"]{
    break-inside: avoid;
    overflow:visible;
    max-width:100%;
    transform:scale(0.95);
    transform-origin:left top;
  }
}
</style>
</head>
<body>
${combined}
</body>
</html>
`;

    const blob = new Blob([html], { type: "text/html" });
    const url = URL.createObjectURL(blob);

    window.open(url, "_blank");
}

function addButton() {

    btn = document.createElement("button");
    btn.textContent = "Export Questions";

    Object.assign(btn.style, {
        position: "fixed",
        bottom: "20px",
        left: "20px",
        zIndex: "9999",
        padding: "10px 16px",
        borderRadius: "8px",
        border: "none",
        background: "#8caaee",
        color: "#303446",
        fontWeight: "600",
        cursor: "pointer"
    });

    btn.addEventListener("click", exportQuestions);

    document.body.appendChild(btn);
}
function removeButton() {
  if (btn) {
   btn.remove();
  }
}
})();