Skip to content

Instantly share code, notes, and snippets.

@AndreasLonn
Last active February 13, 2025 14:53
Show Gist options
  • Save AndreasLonn/c5f12b1704c748a9bc18786ea295aedc to your computer and use it in GitHub Desktop.
Save AndreasLonn/c5f12b1704c748a9bc18786ea295aedc to your computer and use it in GitHub Desktop.
Tools for students and teachers at Jönköping University
// ==UserScript==
// @name JU Tools
// @namespace http://tampermonkey.net/
// @version 2.16
// @description Tools for students and teachers at Jönköping University
// @author AndreasLonn
// @downloadURL https://gist.github.com/AndreasLonn/c5f12b1704c748a9bc18786ea295aedc/raw/JUTools.user.js
// @updateURL https://gist.github.com/AndreasLonn/c5f12b1704c748a9bc18786ea295aedc/raw/JUTools.user.js
// @run-at document-start
// @match https://ju.instructure.com/*
// @match https://ju.quiz-lti-dub-prod.instructure.com/*
// @match https://ju.se/*
// @match https://canvadocs-prod-dub.inscloudgate.net/1/sessions/*
// @icon https://ju.se/favicon.ico
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_getTab
// @grant GM_saveTab
// ==/UserScript==
/*
Access the settings meny by appending "?jutools-settings" (or "&jutools-settings")
to the url of a matched website, e.g. "https://ju.se?jutools-settings".
If enabled (which it is by default), settings can also be accessed
by clicking "JUTools Settings" in the "Account" sidebar on Canvas.
*/
(function() {
'use strict';
// console.log('Origin:', window.origin);
function q(elem, parent=document) { return parent.querySelector(elem); }
function qs(elems, parent=document) { return parent.querySelectorAll(elems); }
function getUrlParam(key, search=location.search) {
return (new URLSearchParams(search)).get(key);
}
const matchingPage = {
hostname(regex) { return regex.test(location.hostname); },
path(regex) { return regex.test(location.pathname); },
param(key, value='') { return getUrlParam(key) === value; },
get ju() { return this.hostname(/^ju.se$/); },
get canvas() { return this.hostname(/^ju.instructure.com$/); },
get canvasDocument() { return this.hostname(/canvadocs\-prod\-dub\.inscloudgate\.net/) && this.path(/^\/1\/sessions\//); },
get canvasFile() { return this.canvas && this.path(/^\/courses\/[0-9]+\/files\/[0-9]+/); },
get canvasFileJutools() { return this.canvasFile && this.param('jutools-file_preview'); },
get canvasModules() { return this.canvas && this.path(/^\/courses\/[0-9]+\/modules/); },
get canvasGrades() { return this.canvas && this.path(/^\/courses\/[0-9]+\/gradebook$/); },
get canvasSpeedgrader() { return this.canvas && this.path(/^\/courses\/[0-9]+\/gradebook\/speed_grader/); },
get canvasAdjustQuiz() { return this.canvas && this.path(/^\/courses\/[0-9]+\/quizzes\/[0-9]+\/moderate/); },
get canvasAdjustQuizNew() { return this.canvas && this.path(/^\/courses\/[0-9]+\/assignments/) && this.param('display', 'full_width'); },
get canvasAdjustQuizNewIFrame() { return this.hostname(/ju\.quiz\-lti\-dub\-prod\.instructure\.com/); },
get canvasAdjustQuizNewModerate() { return this.path(/^\/moderation\/(\d)+/); },
get canvasProfile() { return this.canvas && this.path(/^\/profile/); },
get settings() { return this.param('jutools-settings'); },
};
/**
* Watches {@link elem} for changes and calls {@link onChange} when changes are detected.
* Passes "mutations" and "mutationObserver" to onChange
*/
function watchElem(elem, onChange, options={ subtree: true, childList: true }) {
new MutationObserver((mutations, mutationObserver) => {
onChange(mutations, mutationObserver);
}).observe((typeof elem === 'string') ? q(elem) : elem, options);
}
/**
* Wait for {@link elem} to appear in the document
*/
function waitForElem(elem) { // https://stackoverflow.com/a/61511955
return new Promise(resolve => {
{
const element = q(elem);
if (element) return resolve(element);
}
watchElem(document.documentElement, (mutations, observer) => {
const element = q(elem);
if (element) {
observer.disconnect();
resolve(element);
}
});
});
}
/**
* Wait for the "DOMContentLoaded" event
*/
function waitForLoad() {
return new Promise(resolve => {
document.addEventListener('DOMContentLoaded', () => resolve());
});
}
function waitForAndWatchElem(elem, onChange, options={ subtree: true, childList: true }) {
waitForElem(elem).then(element => {
onChange(element, []);
watchElem(element, mutations => onChange(element, mutations), options)
});
}
/**
* Waits for and clicks the elements in order
*
* @param {...string} elems - CSS selectors to click
*/
function waitForAndClick(...elems) {
return new Promise(async resolve => {
if(elems.length <= 0 || elems[0].length <= 0) return resolve();
(await waitForElem(elems.shift())).click();
await waitForAndClick(...elems);
resolve();
});
}
const settings = {
'canvasShowJuToolsSettingsInSidebar': true,
'canvasDocumentOpenButton': true,
'canvasGradesStudentInsertion': true,
'canvasSpeedgraderLRBtnAssignment': true,
'canvasModulesShortcuts': true,
'canvasSpeedgraderSearchStudents': true,
'canvasSpeedgraderSortStudents': 'firstname',
'canvasSpeedgraderSortStudentsGradedLast': true,
'canvasAdjustQuizSortStudents': 'score',
...JSON.parse(GM_getValue('jutools-settings', '{}'))
};
const inIFrame = (() => {
try { return window.self !== window.top; }
catch (e) { return true; }
})();
function sortChildren(elem, sortFunction) {
elem.replaceChildren(...[...elem.children].sort(sortFunction));
}
function addCSS(css, after=true) {
const newCssStyle = new CSSStyleSheet();
newCssStyle.replace(css);
document.adoptedStyleSheets = after ? [...document.adoptedStyleSheets, newCssStyle] : [newCssStyle, ...document.adoptedStyleSheets];
}
function clickIsInsideBoundaries(event, rect) {
return event.clientY >= rect.top
&& event.clientX <= rect.right
&& event.clientY <= rect.bottom
&& event.clientX >= rect.left;
}
/**
* Send message to other page
*
* To send from inside iframe, use {@link window.top} as {@link receiver}
*
* To send to iframe, use `q('iframe#my-iframe').contentWindow` as {@link receiver}
*/
function sendMessage(receiver, targetOrigin, subject, message) {
receiver.postMessage({'sender': 'jutools', 'subject': subject, 'message': message}, targetOrigin);
}
/**
* Receive message from {@link sendMessage}
*/
function handleMessages(subject, onMessage) {
window.addEventListener('message', event => {
if(event.data
&& event.data.sender && event.data.sender === 'jutools'
&& event.data.subject && event.data.subject === subject) {
onMessage(event.data.message);
}
});
}
/**
* Perform a regex match and return first match or undefined in no matches were found
*/
String.prototype.matchFirst = function (regex) { return (this.match(regex) || [undefined])[0] };
/**
* Get link to file preview. Link should be in form:
* [https://ju.instructure.com]/courses/{courseId}/files/{filesId}
* @param {boolean} jutoolsLink - Links to JUTools or directly to file preview
* @param {string} path - Path to the file. Defaults to location.pathname
* @returns string
*/
function getFilePreviewLink(jutoolsLink, path=location.pathname) {
const urlParts = path.split('/');
while(urlParts.length > 0 && urlParts[0] !== 'courses') urlParts.shift();
if(urlParts[0] !== 'courses' || urlParts[2] !== 'files') {
throw `path does not match "/courses/<courseId>/files/<filesId>". path: "${path}"`;
}
const courseId = urlParts[1];
const fileId = urlParts[3];
return `${location.origin}/courses/${courseId}/files/${fileId}/${jutoolsLink ? '?jutools-file_preview' : 'file_preview'}`;
}
/// Add Open-button to PDF:s and other documents in Canvas JU
if (settings.canvasDocumentOpenButton) {
/// Update title
if(!inIFrame && matchingPage.canvasDocument) {
// console.log('canvasDocumentOpenButton canvasDocument');
waitForLoad().then(() => {
try {
const title = decodeURIComponent(unsafeWindow.DocViewer.sessionData.pdfjs.documentName);
document.title = `${title} - ${document.title}`;
} catch {
console.error('Go back to Canvas and open again');
GM_getTab(tab => {
if(tab.jutoolsFilePreviewLink) location.replace(tab.jutoolsFilePreviewLink);
else if(confirm('Session needs to be refreshed. Go back to Canvas?')) window.history.back();
});
}
});
}
/// Normal files accessed through for example Modules
else if(matchingPage.canvasFile) {
// console.log('canvasDocumentOpenButton canvasFile');
if(matchingPage.canvasFileJutools) {
// console.log('canvasDocumentOpenButton canvasFileJutools');
GM_getTab(tab => {
const filePreviewLink = getFilePreviewLink(true);
tab.jutoolsFilePreviewLink = filePreviewLink;
GM_saveTab(tab);
location.replace(getFilePreviewLink(false));
});
}
waitForElem('a[download]').then(downloadElem => {
const docPreviewUrl = getFilePreviewLink(true);
const oldElem = downloadElem.parentElement.parentElement;
const newElem = oldElem.cloneNode(true);
newElem.childNodes[2].remove() // Remove file size text
q('a[download]', newElem).href = docPreviewUrl;
q('a[download]', newElem).textContent = 'Open';
q('a[download]', newElem).removeAttribute('download');
oldElem.parentElement.insertBefore(newElem, oldElem);
return;
});
}
/// Files accessed through the overlay
waitForElem('.ef-file-preview-overlay').then(async overlay => {
// console.log('canvasDocumentOpenButton overlay');
// Check if it already exists
if(q('a.jutools-open-button', overlay)) return;
// Find iframe and extract preview URL
const frame = await waitForElem('iframe.ef-file-preview-frame');
const filePreviewURL = getFilePreviewLink(true, frame.src);
const oldElem = q('a.ef-file-preview-button', overlay);
const newElem = oldElem.cloneNode(true);
newElem.href = filePreviewURL;
newElem.classList.add('jutools-open-button');
newElem.removeAttribute('download');
q('i', newElem).className = 'icon-circle-arrow-up';
q('span', newElem).textContent = 'Open';
oldElem.parentElement.insertBefore(newElem, oldElem);
});
}
// Insert students, from URL, into filter on the Grades page
if (settings.canvasGradesStudentInsertion) {
// console.log('canvasGradesStudentInsertion');
/// Uses URL hash to enter students on the Grades page
/// This allows for refreshing while keeping filtered students
if(matchingPage.canvasGrades) {
// Get student_ids from URL hash (with the leading '#' removed)
const initialStudent_ids = getUrlParam('jutools-student_ids', location.hash.substring(1));
let doneFillingStudents = false;
if(initialStudent_ids) {
const elemsToClick = [];
initialStudent_ids.split(',').forEach(student_id => {
// First click the filter element to bring up the list of students
elemsToClick.push('#student-names-filter:not([disabled])');
// Then click the correct student
elemsToClick.push(`[id="${student_id}"]`);
// Repeat for each student_id
});
waitForAndClick(...elemsToClick).then(() => {
doneFillingStudents = true
});
} else doneFillingStudents = true;
// Update URL hash when user adds students to filter
waitForAndWatchElem('#gradebook_grid .container_0 .canvas_0', () => {
// Check if we are done filling students from the hash
if(!doneFillingStudents) return;
// Check that we have selected at least one student
if(!q('#gradebook-student-search label[for="student-names-filter"] button')) return;
// Collect student_ids from the gradebook grid, as it is not stored in the filter section
const studentElems = Array.from(qs('#gradebook_grid .student-name [data-student_id]'));
const newHashValue = studentElems.map(student => student.dataset.student_id).join(',');
if(!newHashValue) return;
// Create a URLSearchParams object to ensure that we don't update other values
const newHashParams = new URLSearchParams(location.hash.substring(1));
newHashParams.set('jutools-student_ids', newHashValue);
location.hash = newHashParams.toString();
});
}
/// Add shortcut to the Grades page filtered on the current student in the Speedgrader
else if (matchingPage.canvasSpeedgrader) {
waitForElem('.content_box:last-child > h2').then(async elem => {
const linkElem = (await waitForElem('#breadcrumbs li:last-child a')).cloneNode(true);
linkElem.href = `${linkElem.href}#jutools-student_ids=${getUrlParam('student_id')}`;
elem.appendChild(linkElem);
});
}
}
/// Make Left and Right button in SpeedGrader go to previous and next assignment instead of student
if (settings.canvasSpeedgraderLRBtnAssignment && matchingPage.canvasSpeedgrader) {
// console.log('canvasSpeedgraderLRBtnAssignment');
const assignmentId = Number(getUrlParam('assignment_id'));
waitForElem('#prev-student-button').then(prevStudentButton => {
prevStudentButton.innerHTML = prevStudentButton.innerHTML;
prevStudentButton.addEventListener('click', event => {
event.stopImmediatePropagation();
location.href = location.href.replace(`assignment_id=${assignmentId}`, `assignment_id=${assignmentId - 1}`);
});
});
waitForElem('#next-student-button').then(nextStudentButton => {
nextStudentButton.innerHTML = nextStudentButton.innerHTML;
nextStudentButton.addEventListener('click', event => {
event.stopImmediatePropagation();
location.href = location.href.replace(`assignment_id=${assignmentId}`, `assignment_id=${assignmentId + 1}`);
});
});
}
/// Add link to SpeedGrader next to assignment in Modules
if (settings.canvasModulesShortcuts && matchingPage.canvasModules) {
// console.log('canvasModulesShortcuts');
const courseId = Number(location.pathname.matchFirst(/(?<=^\/courses\/)[0-9]+/));
waitForAndWatchElem('#context_modules', (modules, mutations) => {
// Only select assignments where user has access to admin actions, otherwise shortcut is useless
qs('li.attachment:not(:has(.jutools-shortcut-open))', modules).forEach(attachmentElem => {
const attachmentIdClass = [...attachmentElem.classList].find(className => /Attachment_(\d)+/.test(className));
if(!attachmentIdClass) return;
const attachmentId = Number(attachmentIdClass.substring('Attachment_'.length));
const openA = document.createElement('a');
openA.rel = 'noopener noreferrer';
openA.classList.add('jutools-shortcut-open', 'icon-circle-arrow-up');
openA.href = getFilePreviewLink(true, `/courses/${courseId}/files/${attachmentId}`);
openA.title = 'Open file';
q('div', attachmentElem).insertBefore(openA, q('.ig-admin', attachmentElem));
});
// Only select assignments where user has access to admin actions, otherwise shortcut is useless
qs('li.assignment:has(.ig-admin):not(:has(.jutools-shortcut-speedgrader))', modules).forEach(assignmentElem => {
const assignmentIdClass = [...assignmentElem.classList].find(className => /Assignment_(\d)+/.test(className));
if(!assignmentIdClass) return;
const assignmentId = Number(assignmentIdClass.substring('Assignment_'.length));
const speedGraderA = document.createElement('a');
speedGraderA.rel = 'noopener noreferrer';
speedGraderA.classList.add('jutools-shortcut-speedgrader', 'icon-speed-grader');
speedGraderA.href = `/courses/${courseId}/gradebook/speed_grader?assignment_id=${assignmentId}`;
q('div', assignmentElem).insertBefore(speedGraderA, q('.ig-admin', assignmentElem));
});
// Only select assignments where user has access to admin actions, otherwise shortcut is useless
qs('li.lti-quiz:has(.ig-admin):not(:has(.jutools-shortcut-quiz-moderate))', modules).forEach(quizElem => {
const assignmentIdClass = [...quizElem.classList].find(className => /Assignment_(\d)+/.test(className));
if(!assignmentIdClass) return;
const assignmentId = Number(assignmentIdClass.substring('Assignment_'.length));
const moderateA = document.createElement('a');
moderateA.rel = 'noopener noreferrer';
moderateA.classList.add('jutools-shortcut-quiz-moderate', 'icon-quiz');
moderateA.href = `/courses/${courseId}/assignments/${assignmentId}?display=full_width&jutools-moderate`;
q('div', quizElem).insertBefore(moderateA, q('.ig-admin', quizElem));
});
});
}
/// Sort the list of students in SpeedGrader in a set order
if (settings.canvasSpeedgraderSortStudents && matchingPage.canvasSpeedgrader) {
// console.log('canvasSpeedgraderSortStudents');
const sortFunctions = [(a,b,sortI) => 0];
switch(settings.canvasSpeedgraderSortStudents) {
case 'firstname':
sortFunctions.push((a, b, sortI) => {
const nameA = q('li a span.ui-selectmenu-item-header', a).textContent.replace(/\n( )+/g, '');
const nameB = q('li a span.ui-selectmenu-item-header', b).textContent.replace(/\n( )+/g, '');
return (nameA > nameB) ? 1 : (nameA < nameB) ? -1 : sortFunctions[sortI - 1](a, b, sortI - 1);
});
break;
}
if(settings.canvasSpeedgraderSortStudentsGradedLast) {
// console.log('canvasSpeedgraderSortStudentsGradedLast');
addCSS(`
#students_selectmenu-menu .resubmitted .speedgrader-selectmenu-icon {
color: #0cd;
}
`);
sortFunctions.push((a, b, sortI) => { // not_graded > resubmitted > not_submitted > graded
const aState = a.classList.contains('not_graded') ? 0 : a.classList.contains('resubmitted') ? 1 : a.classList.contains('not_submitted') ? 2 : 3;
const bState = b.classList.contains('not_graded') ? 0 : b.classList.contains('resubmitted') ? 1 : b.classList.contains('not_submitted') ? 2 : 3;
if(aState === bState) return sortFunctions[sortI - 1](a, b, sortI - 1);
return aState - bState;
});
}
waitForElem('#students_selectmenu-menu').then(listElem => {
if(sortFunctions.length > 1) sortChildren(listElem, (a,b) => sortFunctions[sortFunctions.length - 1](a,b,sortFunctions.length-1));
});
}
/// Search the list of students in SpeedGrader
if (settings.canvasSpeedgraderSearchStudents && matchingPage.canvasSpeedgrader) {
// console.log('canvasSpeedgraderSearchStudents');
addCSS(`
#students_selectmenu-menu > li.hidden {
display: none;
}
#students_selectmenu-menu > input:first-child {
position: sticky;
top: 0;
width: 100%;
height: 2em;
z-index: 1;
}
`);
waitForElem('#students_selectmenu-menu').then(listElem => {
const searchField = document.createElement('input');
searchField.placeholder = 'Sök...';
searchField.addEventListener('mousedown', event => {
searchField.select();
});
searchField.addEventListener('keydown', event => { // Stop Canvas from capturing keys
event.stopImmediatePropagation();
});
searchField.addEventListener('input', event => {
const searchQuery = searchField.value.toLowerCase();
qs('li', listElem).forEach((student) => {
const studentName = q('.ui-selectmenu-item-header', student).textContent.toLowerCase();
student.classList.toggle('hidden', studentName.indexOf(searchQuery) < 0);
});
});
listElem.prepend(searchField);
});
}
/// Sort the list of students in Adjust Quiz in a set order
if (settings.canvasAdjustQuizSortStudents) {
// console.log('canvasAdjustQuizSortStudents');
if(matchingPage.canvasAdjustQuiz) {
let sortFunction = undefined;
switch(settings.canvasAdjustQuizSortStudents) {
case 'firstname':
sortFunction = (a, b) => {
const nameAText = q('td.name', a).textContent.replace(/\n( )+/g, '');
const nameADivText = q('td.name div', a).textContent.replace(/\n( )+/g, '');
const nameAList = nameAText.replace(nameADivText, '').split(',').map(val => val.replace(/(^( )+)|(( )+$)/g, ''));
const nameA = nameAList.length > 1 ? `${nameAList[1]}, ${nameAList[0]}` : nameAList[0];
const nameBText = q('td.name', b).textContent.replace(/\n( )+/g, '');
const nameBDivText = q('td.name div', b).textContent.replace(/\n( )+/g, '');
const nameBList = nameBText.replace(nameBDivText, '').split(',').map(val => val.replace(/(^( )+)|(( )+$)/g, ''));
const nameB = nameBList.length > 1 ? `${nameBList[1]}, ${nameBList[0]}` : nameBList[0];
return (nameA > nameB) ? 1 : (nameA < nameB) ? -1 : 0;
}
break;
case 'score':
sortFunction = (a, b) => {
const scoreA = Number(q('td.score_holder span', a).textContent.replace(/\n( )+/g, ''));
const scoreB = Number(q('td.score_holder span', b).textContent.replace(/\n( )+/g, ''));
return scoreB - scoreA;
}
break;
}
waitForElem('table#students tbody').then(listElem => {
if(sortFunction) sortChildren(listElem, sortFunction);
});
}
else if(matchingPage.canvasAdjustQuizNew) {
if(matchingPage.param('jutools-moderate')) {
// console.log('jutools-moderate');
let hasChanged = false;
handleMessages('iniframe', message => {
if(message !== 'load') return;
const frame = q('iframe.tool_launch');
if(!frame) return;
const targetOrigin = 'https://ju.quiz-lti-dub-prod.instructure.com';
frame.addEventListener('load', event => {
if(!hasChanged) {
try {
sendMessage(frame.contentWindow, targetOrigin, 'quiz-navigate', 'moderate');
hasChanged = true;
} catch(error) {
console.error('JUTools unable to send message', error);
}
}
});
});
}
}
else if(matchingPage.canvasAdjustQuizNewIFrame) {
if(inIFrame) sendMessage(window.top, 'https://ju.instructure.com', 'iniframe', 'load');
handleMessages('quiz-navigate', message => {
// console.log('Message from JU Tools, "quiz-navigate"', message);
switch(message) {
case 'moderate': {
const quizId = location.pathname.matchFirst(/(\d)+/);
location.href = `/moderation/${quizId}?jutools-filter=noattemptsleft`;
break;
}
}
});
if(matchingPage.canvasAdjustQuizNewModerate) {
// console.log('Moderate');
if(matchingPage.param('jutools-filter', 'noattemptsleft')) {
waitForAndClick(
'input[data-automation=moderate-progress-filter]',
'#progress-select-option-no-attempts-left'
);
}
waitForAndWatchElem('[data-automation="moderate-page"]', (moderatePage, mutations) => {
// Make sure that we have a table to sort
const listElem = q('table > tbody');
if(!listElem) return;
let addedChildren = 0;
let removedChildren = 0;
mutations.forEach(mutation => {
addedChildren += mutation.addedNodes.length;
removedChildren += mutation.removedNodes.length;
});
// If addedChildren === removedChildren, elements might have just been reordered
if(addedChildren === removedChildren && addedChildren === listElem.children.length) return;
// Make sure that at least one changed element is inside table body, otherwise changes are irrelevant
if (!mutations.find(mutation => mutation.target.closest('table > tbody') !== null)) return;
let sortFunction = undefined;
switch(settings.canvasAdjustQuizSortStudents) {
case 'score':
sortFunction = (a, b) => {
const scoreA = Number(q('td:nth-child(3) div:last-of-type', a).textContent.match(/\d+/) || -1);
const scoreB = Number(q('td:nth-child(3) div:last-of-type', b).textContent.match(/\d+/) || -1);
return scoreB - scoreA;
}
break;
}
if(sortFunction) sortChildren(listElem, sortFunction);
});
}
}
}
/// Show settings page
if((settings.canvasShowJuToolsSettingsInSidebar && matchingPage.canvas) || matchingPage.settings) {
// console.log('jutools-settings');
const settingsDialog = document.createElement('dialog');
settingsDialog.innerHTML = `
<form style="display: flex; flex-direction: column;">
<h2 style="margin-top: 0;">Settings for JU Tools</h2>
<label>
<span>canvasShowJuToolsSettingsInSidebar</span>
<input type="checkbox" name="canvasShowJuToolsSettingsInSidebar" ${settings.canvasShowJuToolsSettingsInSidebar ? 'checked' : ''} />
</label>
<label>
<span>canvasDocumentOpenButton</span>
<input type="checkbox" name="canvasDocumentOpenButton" ${settings.canvasDocumentOpenButton ? 'checked' : ''} />
</label>
<label>
<span>canvasGradesStudentInsertion</span>
<input type="checkbox" name="canvasGradesStudentInsertion" ${settings.canvasGradesStudentInsertion ? 'checked' : ''} />
</label>
<label>
<span>canvasSpeedgraderLRBtnAssignment</span>
<input type="checkbox" name="canvasSpeedgraderLRBtnAssignment" ${settings.canvasSpeedgraderLRBtnAssignment ? 'checked' : ''} />
</label>
<label>
<span>canvasModulesShortcuts</span>
<input type="checkbox" name="canvasModulesShortcuts" ${settings.canvasModulesShortcuts ? 'checked' : ''} />
</label>
<label>
<span>canvasSpeedgraderSearchStudents</span>
<input type="checkbox" name="canvasSpeedgraderSearchStudents" ${settings.canvasSpeedgraderSearchStudents ? 'checked' : ''} />
</label>
<label>
<span>canvasSpeedgraderSortStudents</span>
<select name="canvasSpeedgraderSortStudents">
${['firstname', 'default'].map((val) => {
return `<option value="${val}" ${settings.canvasSpeedgraderSortStudents === val ? 'selected' : ''}>${val}</option>`;
}).join('')}
</select>
</label>
<label>
<span>canvasSpeedgraderSortStudentsGradedLast</span>
<input type="checkbox" name="canvasSpeedgraderSortStudentsGradedLast" ${settings.canvasSpeedgraderSortStudentsGradedLast ? 'checked' : ''} />
</label>
<label>
<span>canvasAdjustQuizSortStudents</span>
<select name="canvasAdjustQuizSortStudents">
${['firstname', 'score', 'default'].map((val) => {
return `<option value="${val}" ${settings.canvasAdjustQuizSortStudents === val ? 'selected' : ''}>${val}</option>`;
}).join('')}
</select>
</label>
<span>Reload page after saving to apply settings</span>
<button type="submit">Save</button>
</form>
`;
q('form', settingsDialog).addEventListener('submit', event => {
event.preventDefault();
settings.canvasShowJuToolsSettingsInSidebar = q('input[name=canvasShowJuToolsSettingsInSidebar]', settingsDialog).checked;
settings.canvasDocumentOpenButton = q('input[name=canvasDocumentOpenButton]', settingsDialog).checked;
settings.canvasGradesStudentInsertion = q('input[name=canvasGradesStudentInsertion]', settingsDialog).checked;
settings.canvasSpeedgraderLRBtnAssignment = q('input[name=canvasSpeedgraderLRBtnAssignment]', settingsDialog).checked;
settings.canvasModulesShortcuts = q('input[name=canvasModulesShortcuts]', settingsDialog).checked;
settings.canvasSpeedgraderSearchStudents = q('input[name=canvasSpeedgraderSearchStudents]', settingsDialog).checked;
settings.canvasSpeedgraderSortStudents = q('select[name=canvasSpeedgraderSortStudents]', settingsDialog).value;
settings.canvasSpeedgraderSortStudentsGradedLast = q('input[name=canvasSpeedgraderSortStudentsGradedLast]', settingsDialog).checked;
settings.canvasAdjustQuizSortStudents = q('select[name=canvasAdjustQuizSortStudents]', settingsDialog).value;
GM_setValue('jutools-settings', JSON.stringify(settings));
settingsDialog.close();
});
document.addEventListener('click', event => {
if(event.target === settingsDialog) {
if (!clickIsInsideBoundaries(event, event.target.getBoundingClientRect())) {
event.target.close();
}
}
});
if(settings.canvasShowJuToolsSettingsInSidebar && matchingPage.canvas) {
waitForAndWatchElem('#nav-tray-portal', (navTrayDiv, mutations) => {
const sectionTabs = q('.navigation-tray-container.profile-tray ul', navTrayDiv);
if(sectionTabs && !q('#jutools-settings-button', sectionTabs)) {
const juToolsSettingsMenuItem = q('li', sectionTabs).cloneNode(true);
if(!q('a', juToolsSettingsMenuItem)) return;
q('a', juToolsSettingsMenuItem).id = 'jutools-settings-button';
q('a', juToolsSettingsMenuItem).href = '?jutools-settings';
q('a', juToolsSettingsMenuItem).textContent = 'JUTools Settings';
q('a', juToolsSettingsMenuItem).addEventListener('click', event => {
event.preventDefault();
settingsDialog.showModal();
});
sectionTabs.appendChild(juToolsSettingsMenuItem);
}
});
}
waitForLoad().then(() => {
document.body.appendChild(settingsDialog);
if(matchingPage.settings) settingsDialog.showModal();
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment