SLV LAB Code Club, 2 October 2025
transforms the effectivities of socio-technical systems, making them work, or un-work, often in new and unexpected ways
— Mark Olson (2013), "Hacking the humanities: Twenty-first-century literacies and the ‘becoming other' of the humanities".
Hacking the systems that construct and control access to our cultural collections is at the core of humanities practice in the early 21st century. As online collections continue to expand, we need to carve out spaces that resist the weight of scale and foster alternative perspectives. As interfaces grow in sophistication and complexity, we need to stage playful and pointed interventions that reveal their limits and empower critique. We do not all have to be coders, but we do have to take code seriously. We have to take what we are given by collection databases and change it.
looking under the hood
right click
→ Developer tools
→ Inspect
examine HTML code
check for errors
track what gets loaded
all ways of running javascript in your browser to change the content or behaviour of websites
javascript:let%20handle=document.querySelector("input.handle");window.open("https://commons.wikimedia.org/w/index.php?title=Special:LinkSearch&target="+handle.value.replace("http://",""));
//Get the handle url from the input box
let handle = document.querySelector("input.handle");
// Construct a search url, then open it
window.open("https://commons.wikimedia.org/w/index.php?title=Special:LinkSearch&target=" + handle.value.replace("http://", ""));
spaces and linebreaks removed or replaced
tells the browser to run this as code
javascript:let%20handle=document.querySelector("input.handle");window.open("https://commons.wikimedia.org/w/index.php?title=Special:LinkSearch&target="+handle.value.replace("http://",""));
added by a userscript
// ==UserScript==
// @name SLV add Wikimedia Commons links
// @namespace wraggelabs.com/slv_add_wikimedia_commons_links
// @match *://viewer.slv.vic.gov.au/?entity=*
// @connect commons.wikimedia.org
// @grant GM_xmlhttpRequest
// @version 1.0
// @author -
// @description 29/09/2025, 10:37:18
// @require https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.3/waitForKeyElements.js
// ==/UserScript==
waitForKeyElements("#handleURL", (handleInput) => {
// Get the handle url
const handle = document.querySelector("input.handle");
// Create a url to search for the handle in Wikimedia Commons
const searchUrl = "https://commons.wikimedia.org/w/api.php?action=query&format=json&list=exturlusage&euquery=" + handle.value.replace("http://", "");
const headers = {"User-Agent": "Userscript/SLV add Wikimedia Commons links (tim@timsherratt.au)"}
// Query the Wikimedia API
GM_xmlhttpRequest({
method: "GET",
url: searchUrl,
responseType: "json",
headers: headers,
onload: function(response) {
// Extract page ids from the results (if any)
let pageIds = [];
for (let page of response.response["query"]["exturlusage"]) {
pageIds.push(page["pageid"]);
}
// If there are results, we'll get more info using the page ids
if (pageIds.length > 0) {
// Create a url using the page ids to get more info
let infoUrl = "https://commons.wikimedia.org/w/api.php?action=query&pageids=" + pageIds.join("|") + "&prop=info&inprop=url&format=json";
GM_xmlhttpRequest({
method: "GET",
url: infoUrl,
responseType: "json",
headers: headers,
onload: function(response) {
// Get the titles and urls from the results and put them in an HTML list
let linkList = document.createElement("ul");
for (const [key, value] of Object.entries(response.response["query"]["pages"])) {
let pageUrl = value["canonicalurl"];
let pageTitle = value["title"];
let listItem = document.createElement("li");
listItem.innerHTML = "<a href='" + pageUrl + "' style='color: #1779ba;'>" + pageTitle + "</a>";
linkList.appendChild(listItem);
}
// Add a new row in the further details section of the page and add the list
// New heading in dt
let dt = document.createElement("dt");
dt.className = "cell small-6 medium-2 large-2";
dt.innerHTML = "Wikimedia Commons";
// New dd to contain list
let dd = document.createElement("dd");
dd.className = "cell small-6 medium-10 large-10";
dd.appendChild(linkList);
// Get the current dl list
let infoList = document.querySelector("dl#moreInfoList");
// Add the new row
infoList.appendChild(dt);
infoList.appendChild(dd);
}
});
}
}
});
});
To install:
seeing people in the archives
?!
// ==UserScript==
// @name SLV My Favs menu creator
// @namespace Violentmonkey Scripts
// @match *://www.slv.vic.gov.au/*
// @grant none
// @version 1.0
// @author -
// @description 28/09/2025, 11:19:01
// ==/UserScript==
// Edit this list to ad your own links
myLinks = [
{
"href": "https://find.slv.vic.gov.au/discovery/collectionDiscovery?vid=61SLV_INST:SLV&collectionId=81274079160007636",
"text": "Melbourne and Metropolitan Board of Works plans"
}
]
// Create a new top-level nav menu heading
let menuItem = document.createElement("li");
menuItem.className = "site-navigation__item site-navigation__item--get-involved";
let menuHead = document.createElement("a");
menuHead.className = "site-navigation__link js-site-navigation__link";
menuHead.href = "#";
menuHead.innerHTML = "My favourites"
// Display the megamenu when you hover over the heading
menuItem.addEventListener("mouseenter", (event) => {
event.target.className = "site-navigation__item site-navigation__item--get-involved site-navigation__item--dropdown";
});
// Hide the megamenu when you move the mouse away
menuItem.addEventListener("mouseleave", (event) => {
event.target.className = "site-navigation__item site-navigation__item--get-involved";
});
// Create the megamenu
// Lots of layers of divs in the MegaMenu!
let megaMenu = document.createElement("div");
megaMenu.className = "megamenu";
let megaMenuContainer = document.createElement("div");
megaMenuContainer.className = "megamenu__container";
let megaMenuNav = document.createElement("nav");
megaMenuNav.className ="megamenu-navigation";
let megaMenuSection = document.createElement("section");
megaMenuSection.className = "megamenu__section";
let megaMenuList = document.createElement("ul");
megaMenuList.className = "megamenu__list"
// Add links to the megamenu
for (link of myLinks) {
let megaMenuItem = document.createElement("li");
let megaMenuLink = document.createElement("a");
megaMenuLink.className = "megamenu__link";
megaMenuLink.href = link["href"];
megaMenuLink.innerHTML = link["text"];
megaMenuItem.appendChild(megaMenuLink);
megaMenuList.appendChild(megaMenuItem);
}
// Get the nav menu container
let menuBar = document.getElementsByClassName("nav navbar-nav site-navigation__list")[0];
// Add everything in the hierarchy
megaMenuSection.appendChild(megaMenuList);
megaMenuNav.appendChild(megaMenuSection);
megaMenuContainer.appendChild(megaMenuNav);
megaMenu.appendChild(megaMenuContainer);
menuItem.appendChild(menuHead);
menuItem.appendChild(megaMenu);
menuBar.appendChild(menuItem);
myLinks = [
{
"href": "https://yourfav1.com",
"text": "Title of YourFav1"
},
{
"href": "https://yourfav2.com",
"text": "Title of YourFav2"
},
{
"href": "https://yourfav3.com",
"text": "Title of YourFav3"
}
]
watch out for commas and curly brackets!
find the easter eggs!
// ==UserScript==
// @name SLV easter egg hunt
// @namespace wraggelabs.com/slv_easter_egg_hunt
// @match *://viewer.slv.vic.gov.au/?entity=*
// @grant none
// @version 1.0
// @author -
// @description 28/09/2025, 14:41:35
// @require https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.3/waitForKeyElements.js
// ==/UserScript==
// These are the pages on which the eggs will appear
const pages = [
"https://viewer.slv.vic.gov.au/?entity=IE741728&mode=browse",
"https://viewer.slv.vic.gov.au/?entity=IE4152304&mode=browse",
"https://viewer.slv.vic.gov.au/?entity=IE223183&mode=browse"
];
// The message on page 1 gives clues to page 2 etc
const messages = [
"WELL DONE!\n\nYou've found the first egg\n\nHere's some clues to help you find the next one:\n\n\t• Part of the anti-conscription campaign in WWI\n\t• 'The Death ______'\n\n",
"GREAT JOB!\n\nYou've found the second egg\n\nHere's some clues to help you find the next one:\n\n\t• Copyright registration\n\t• full of 'vegetable bitters'\n\n",
"SMASHED IT!\n\nYou've found all the eggs, but why not keep exploring the Library's digitised collections?\n\n",
]
// Check to see if the current page needs an egg
function getOrder() {
for (let i=0; i<pages.length; i++) {
if (window.location.href == pages[i]) {
console.log("found", i);
return i;
}
}
return false;
}
function addEgg(order) {
let footerList = document.querySelector("nav.footer-navigation ul");
let egg = document.createElement("li");
egg.className = "footer-navigation__item";
egg.innerHTML = "🥚";
egg.onclick = function() { alert(messages[order]);}
egg.style.cursor = "pointer";
footerList.appendChild(egg);
}
waitForKeyElements("#handleURL", (handleInput) => {
const order = getOrder();
if (order !== false) {
addEgg(order);
}
});
// These are the pages on which the eggs will appear
const pages = [
"https://viewer.slv.vic.gov.au/?entity=IE741728&mode=browse",
"https://viewer.slv.vic.gov.au/?entity=IE4152304&mode=browse",
"https://viewer.slv.vic.gov.au/?entity=IE223183&mode=browse"
];
// The message on page 1 gives clues to page 2 etc
const messages = [
"WELL DONE!\n\nYou've found the first egg\n\nHere's some clues to help you find the next one:\n\n\t• Part of the anti-conscription campaign in WWI\n\t• 'The Death ______'\n\n",
"GREAT JOB!\n\nYou've found the second egg\n\nHere's some clues to help you find the next one:\n\n\t• Copyright registration\n\t• full of 'vegetable bitters'\n\n",
"SMASHED IT!\n\nYou've found all the eggs, but why not keep exploring the Library's digitised collections?\n\n",
]
pages the eggs appear on
clues when you click on the egg
// ==UserScript==
// @name SLV add catalogue info
// @namespace Violentmonkey Scripts
// @match *://find.slv.vic.gov.au/discovery/*
// @grant none
// @version 1.0
// @author -
// @description 28/09/2025, 14:41:35
// @require https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.3/waitForKeyElements.js
// ==/UserScript==
function changeDisplay() {
if (document.location.href.includes("fulldisplay")) {
waitForKeyElements("#full-view-container", (container) => {
let details = document.querySelector("#item-details div.spaced-rows");
let newRow = document.createElement("div");
newRow.className="layout-block-xs layout-xs-column layout-row";
let newLabel = document.createElement("div");
newLabel.className = "flex-gt-xs-25 flex-gt-sm-20 flex bold-text";
newLabel.innerHTML = "New info";
let newValue = document.createElement("div");
newValue.className = "flex";
newValue.innerHTML = "Some additional piece of information";
newRow.appendChild(newLabel);
newRow.appendChild(newValue);
details.appendChild(newRow);
});
}
}
// This function looks for clicks in search results that show full item details in an overlay.
// These clicks don't actually open a new page, even though the url changes.
function watchHistoryEvents() {
const { pushState, replaceState } = window.history;
window.history.pushState = function (...args) {
pushState.apply(window.history, args);
window.dispatchEvent(new Event('pushState'));
};
window.addEventListener('pushState', changeDisplay);
}
watchHistoryEvents();
changeDisplay();