diff --git a/Compass QoL Enhancer.user.js b/Compass QoL Enhancer.user.js
index ae5b0ca..d17eb3d 100644
--- a/Compass QoL Enhancer.user.js
+++ b/Compass QoL Enhancer.user.js
@@ -2,7 +2,7 @@
// @name Compass QoL Enhancer
// @namespace blankie-scripts
// @match http*://*.compass.education/*
-// @version 1.12.0
+// @version 1.13.0
// @author blankie
// @description A userscript that adds small but useful features for Compass, such as the ability to close windows by clicking on the background
// @inject-into page
@@ -12,13 +12,12 @@
"use strict";
let qolTabOpened = false;
-// we make a copy of window.location.hash because the dashboard tab on a user's page would set the fragment to #dsh for some reason
-let hashCopy = window.location.hash;
+let qolLearningTaskOpened = false;
// needed because .toString() adds a trailing = for empty values
-function serializeURLSearchParams(query) {
+URLSearchParams.prototype.laxToString = function() {
let out = "";
- for (let [key, value] of query) {
+ for (let [key, value] of this) {
if (out) {
out += "&";
}
@@ -30,9 +29,18 @@ function serializeURLSearchParams(query) {
}
return out;
}
+function getHashSearch() {
+ return new URLSearchParams(window.location.hash.substring(1));
+}
+function getExtClass(className) {
+ if (!unsafeWindow.Ext) {
+ return null;
+ }
+ return unsafeWindow.Ext.ClassManager.get(className);
+}
function getPanelItemHash(panelId, isDefault) {
if (window.location.pathname === "/Organise/Activities/Activity.aspx") {
- let query = new URLSearchParams(window.location.hash.substring(1));
+ let query = getHashSearch();
if (panelId === "learningtasks") {
query.delete("qol_open_tab");
query.set("openLearningTaskTab", "");
@@ -43,12 +51,52 @@ function getPanelItemHash(panelId, isDefault) {
query.set("qol_open_tab", panelId);
query.delete("openLearningTaskTab");
}
- return `#${serializeURLSearchParams(query)}`;
+ query.delete("qol_learning_task");
+ return `#${query.laxToString()}`;
}
return `#${encodeURIComponent(panelId)}`;
}
+
+
+// Prevent the dashboard tab in the user page being "dsh"
+let UserProfileNewWidget = getExtClass("Compass.widgets.UserProfileNewWidget");
+if (UserProfileNewWidget) {
+ let original = UserProfileNewWidget.prototype.createItems;
+ UserProfileNewWidget.prototype.createItems = function() {
+ let res = original.apply(this, arguments);
+ delete res[0].listeners;
+ return res;
+ };
+}
+
+// Automatically open learning tasks specified in the URL
+let LearningTasksDetailsWidgetNew = getExtClass("Compass.widgets.LearningTasksDetailsWidgetNew");
+if (LearningTasksDetailsWidgetNew) {
+ let original = LearningTasksDetailsWidgetNew.prototype.forceOpenLearningTaskOnLoad;
+ LearningTasksDetailsWidgetNew.prototype.forceOpenLearningTaskOnLoad = function() {
+ // stole this from the function above
+ let toOpen = this.initialLoadComplete && !this.firstLoad;
+ let exit = !toOpen || this.openLearningTaskId || qolLearningTaskOpened;
+ original.apply(this, arguments);
+ if (exit) {
+ return;
+ }
+
+ qolLearningTaskOpened = true;
+ let learningTask = parseInt(getHashSearch().get("qol_learning_task"), 10);
+ if (isNaN(learningTask)) {
+ return;
+ }
+ let learningTaskRecord = this.taskStore.findRecord("id", learningTask);
+ if (!learningTaskRecord) {
+ return;
+ }
+ this.launchLearningTasksSubmissionWidget(learningTaskRecord.data.gridRecordId, false);
+ };
+}
+
function updateInstanceButton(instanceDetails, instanceButton, offset) {
// Make previous/next session buttons links
let index = instanceDetails.instanceStore.indexOfId(instanceDetails.m_instanceId);
@@ -60,42 +108,64 @@ function updateInstanceButton(instanceDetails, instanceButton, offset) {
instanceButton.el.dom.removeAttribute("href");
}
}
-
-function handleInstanceButtonClick(event) {
- if (event.ctrlKey) {
- event.stopImmediatePropagation();
- }
-}
-
-function handleActivityManager(element) {
- let instanceDetails = unsafeWindow.Ext.getCmp(element.id).m_InstanceDetailsWidget;
- let instanceNavigatorToolbar = instanceDetails.getInstanceNavigatorToolbar();
- let previousInstanceButton = instanceNavigatorToolbar.getComponent("previousInstanceButton");
- let nextInstanceButton = instanceNavigatorToolbar.getComponent("nextInstanceButton");
- let comboSelectInstance = instanceDetails.getCmbSelectIntance(); // not a typo :)
-
- let realUpdateInstanceHeader = instanceDetails.updateInstanceHeader;
- instanceDetails.updateInstanceHeader = function() {
- realUpdateInstanceHeader.apply(this, arguments);
- updateInstanceButton(instanceDetails, previousInstanceButton, -1);
- updateInstanceButton(instanceDetails, nextInstanceButton, 1);
+let InstanceDetailsWidget = getExtClass("Compass.widgets.InstanceDetailsWidget");
+if (InstanceDetailsWidget) {
+ let originalUpdateInstanceHeader = InstanceDetailsWidget.prototype.updateInstanceHeader;
+ InstanceDetailsWidget.prototype.updateInstanceHeader = function() {
+ originalUpdateInstanceHeader.apply(this, arguments);
+ let toolbar = this.getInstanceNavigatorToolbar();
+ updateInstanceButton(this, toolbar.getComponent("previousInstanceButton"), -1);
+ updateInstanceButton(this, toolbar.getComponent("nextInstanceButton"), 1);
};
- for (let button of [previousInstanceButton, nextInstanceButton]) {
- button.el.dom.addEventListener("click", handleInstanceButtonClick, {passive: true});
- // move all previous handlers back to the front
- // new relic's browser agent would've borked this, but it did not??
- // i'll take what i can get i guess
- // (note to self: unsafeWindow.NREUM = true if this breaks)
- for (let handler of unsafeWindow.Ext.EventManager.getEventListenerCache(button, "click")) {
- button.el.dom.removeEventListener("click", handler.wrap);
- button.el.dom.addEventListener("click", handler.wrap);
+ let originalGetInstanceNavigatorToolbar = InstanceDetailsWidget.prototype.getInstanceNavigatorToolbar;
+ InstanceDetailsWidget.prototype.getInstanceNavigatorToolbar = function() {
+ if (this.instanceNavigatorToolbar) {
+ return this.instanceNavigatorToolbar;
}
- }
+ let toolbar = originalGetInstanceNavigatorToolbar.apply(this, arguments);
+ let previousButton = toolbar.getComponent("previousInstanceButton");
+ let nextButton = toolbar.getComponent("nextInstanceButton");
- comboSelectInstance.tpl.html = comboSelectInstance.tpl.html.replace("
(.+?)<\/div>/,
+ "
$3"
+ );
+ return combo;
+ };
}
+
+
function handleNewCalendarEvent(element, calendar) {
// Turn each calendar event into a link so that Link Hints can recognize it
let a = document.createElement("a");
@@ -107,26 +177,27 @@ function handleNewCalendarEvent(element, calendar) {
// fix weird aboveline (underline but above the text) on hover
a.style.textDecoration = "none";
a.replaceChildren(...element.childNodes);
- let preventCompassHandler = false;
let calendarData = calendar.getEventRecordFromEl(element).data;
if (a.classList.contains("activity-type-1")) {
// Add a link for activities/"standard classes"
a.href = `/Organise/Activities/Activity.aspx?targetUserId=${calendarData.targetStudentId}#session/${calendarData.instanceId}`;
- preventCompassHandler = true;
} else if (a.classList.contains("activity-type-2")) {
// Add a link for action centre events
a.href = `/Organise/Activities/Events/Event.aspx?eventId=${calendarData.activityId}`;
if (calendarData.targetStudentId) {
a.href += `&userId=${calendarData.targetStudentId}`;
}
- preventCompassHandler = true;
} else if (a.classList.contains("activity-type-10")) {
// Add a link for learning tasks
let calendarPanel = calendar.ownerCalendarPanel;
- a.href = calendarPanel.targetUserId !== undefined ? `/Records/User.aspx?userId=${calendarPanel.targetUserId}#learningTasks` : "/Records/User.aspx#learningTasks";
- preventCompassHandler = true;
+ a.href = "/Records/User.aspx"
+ if (calendarPanel.targetUserId !== undefined) {
+ a.href += `?userId=${calendarPanel.targetUserId}`;
+ }
+ // Link specifically to the learning task
+ a.href += `#learningTasks&qol_learning_task=${calendarData.learningTaskId}`;
}
// Show the finish time for applicable calendar events
@@ -140,7 +211,7 @@ function handleNewCalendarEvent(element, calendar) {
// prevent ctrl-clicking from changing the current tab
// it seems like the link thing actually effectively killed the default handler anyway
- if (preventCompassHandler) {
+ if (a.href) {
a.addEventListener("click", function(event) {
event.stopImmediatePropagation();
}, {passive: true});
@@ -151,10 +222,6 @@ function handleNewCalendarEvent(element, calendar) {
function handlePanelItem(panel, panelItem, isDefault, tabToOpen) {
let panelId = panelItem.itemId || panelItem.id;
- // i don't know why but dashboard is dsh
- if (window.location.pathname === "/Records/User.aspx" && panelId === "dashboard") {
- panelId = "dsh";
- }
let panelItemHash = getPanelItemHash(panelId, isDefault);
// Automatically open tab specified in fragment
@@ -164,10 +231,6 @@ function handlePanelItem(panel, panelItem, isDefault, tabToOpen) {
// it does look like that this bug only manifests if the tab is activated while the initial loading thing is shown
setTimeout(function() {
panel.setActiveTab(panelItem);
- // reference the now active tab in the fragment if necessary (cough cough the reason why hashCopy exists)
- if (panelItemHash !== window.location.hash) {
- history.replaceState("", "", panelItemHash);
- }
}, 1000);
}
@@ -186,7 +249,7 @@ function handlePanelItem(panel, panelItem, isDefault, tabToOpen) {
}
// prevent the browser from scrolling to the body
event.preventDefault();
- if (panelItemHash !== window.location.hash) {
+ if (window.location.hash !== panelItemHash && (window.location.pathname === "/Organise/Activities/Activity.aspx" || !window.location.hash.startsWith(`${panelItemHash}&`))) {
// Automatically add a reference to the tab when it is clicked
history.pushState("", "", panelItemHash);
}
@@ -200,29 +263,24 @@ function handleLearningTasksTable(element) {
event.stopImmediatePropagation();
}, {passive: true});
}
-}
-function handleSessionItem(element) {
- // Make sessions in the sessions dropdown links
- let style = getComputedStyle(element);
+ // Add links to learning tasks themselves
+ let gridView = Ext.getCmp(element.parentElement.id);
+ for (let item of gridView.store.data.items) {
+ let data = item.data;
+ let a = element.querySelector(`[gridrecordid="${data.gridRecordId}"]`);
- let a = document.createElement("a");
- a.href = `#session/${element.dataset.qolSession}`;
- a.style.display = "block";
- a.style.textDecoration = "none";
- a.style.padding = style.padding;
- a.style.color = style.color;
- a.append(...element.childNodes);
- a.addEventListener("click", function(event) {
- if (event.ctrlKey) {
- event.stopImmediatePropagation();
- } else {
- event.preventDefault();
- }
- });
-
- element.style.padding = 0;
- element.append(a);
+ let query = getHashSearch();
+ query.set("qol_learning_task", data.id);
+ a.href = `#${query.laxToString()}`;
+ a.addEventListener("click", function(event) {
+ if (event.ctrlKey) {
+ event.stopImmediatePropagation();
+ } else {
+ event.preventDefault();
+ }
+ });
+ }
}
function handleCKEditor(instance) {
@@ -252,10 +310,6 @@ function handleNewNode(node, observer) {
return;
}
- if (node.id === "CompassManagersActivityDefaultManager") {
- handleActivityManager(node);
- }
-
if (node.parentElement && (node.classList.contains("ext-cal-hd-ct") || node.classList.contains("ext-cal-bg-tbl") || node.classList.contains("ext-cal-inner-ct") || node.classList.contains("ext-cal-mdv"))) {
let calendar = unsafeWindow.Ext.getCmp(node.closest(".x-component").id);
if (calendar.view) {
@@ -266,11 +320,10 @@ function handleNewNode(node, observer) {
}
} else if (!qolTabOpened && node.classList.contains("x-panel")) {
qolTabOpened = true;
- let tabToOpen = hashCopy.substring(1) || null;
- if (window.location.pathname === "/Organise/Activities/Activity.aspx") {
- tabToOpen = (new URLSearchParams(tabToOpen)).get("qol_open_tab");
- }
let panel = unsafeWindow.Ext.getCmp(node.id);
+ let tabToOpen = window.location.pathname === "/Organise/Activities/Activity.aspx"
+ ? getHashSearch().get("qol_open_tab")
+ : (window.location.hash.replace(/^#(.+?)&.*$/, "$1") || null);
for (let i = 0; i < panel.items.items.length; i++) {
handlePanelItem(panel, panel.items.items[i], i === 0, tabToOpen);
}
@@ -284,8 +337,6 @@ function handleNewNode(node, observer) {
}
} else if (node.localName === "table" && node.closest(".sel-learning-tasks-widget")) {
handleLearningTasksTable(node);
- } else if (node.classList.contains("x-boundlist-item") && node.dataset.qolSession) {
- handleSessionItem(node);
} else if (node.classList.contains("cke")) {
let instance = unsafeWindow.CKEDITOR.instances[node.id.substring(4)];
handleCKEditor(instance);
diff --git a/README.md b/README.md
index 76e86d1..21b88d6 100644
--- a/README.md
+++ b/README.md
@@ -37,11 +37,13 @@ A userscript that adds small but useful features for Compass. Features include:
creating a new tab
- Ctrl-clicking an activity in a user's learning tasks tab no longer collapses
everything else
+- Learning tasks now being links (you can ctrl-click them)
- The previous/next buttons and sessions dropdown are now links (you can now
use [Link Hints] and ctrl-click to open them)
- News feed items can now be opened by simply clicking on their background
- The context menu that only says "Copy" is now suppressed
- The option to remember logins is unchecked by default
+- The dashboard tab in a user's profile no longer points you to #dsb
[Link Hints]: https://lydell.github.io/LinkHints/