Adds floating sort buttons to Special:Search and Category pages on Wikimedia Commons
// ==UserScript==
// @name SearchJoy
// @namespace https://commons.wikimedia.org/wiki/User:RoyZuo/SearchJoy.js
// @version 0.1.0
// @description Adds floating sort buttons to Special:Search and Category pages on Wikimedia Commons
// @author Roy Zuo
// @homepage https://commons.wikimedia.org/wiki/User:RoyZuo/SearchJoy.js
// @supportURL https://commons.wikimedia.org/wiki/User talk:RoyZuo
// @license CC BY-NC-SA 4.0
// @match https://commons.wikimedia.org/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
function injectStyles() {
if (document.getElementById("searchjoy-styles")) return;
const style = document.createElement("style");
style.id = "searchjoy-styles";
style.textContent = `
#searchjoy-container button {
transition: all 0.2s ease;
text-align: center;
position: relative;
}
#searchjoy-container button:hover {
transform: translateY(-2px) scale(1.1);
box-shadow: 0 6px 14px rgba(0,0,0,0.3);
filter: brightness(1.1);
z-index: 3;
}
`;
document.head.appendChild(style);
}
function init() {
if (typeof mw === "undefined" || !mw.config) return;
if (document.getElementById("searchjoy-container")) return;
injectStyles();
const sorts = [
{ key: "create_timestamp_desc", label: "Upload (new first)", type: "created" },
{ key: "create_timestamp_asc", label: "Upload (old first)", type: "created" },
{ key: "last_edit_desc", label: "Edit (new first)", type: "edit" },
{ key: "last_edit_asc", label: "Edit (old first)", type: "edit" }
];
const extraSorts = [
{ key: "relevance", label: "Relevance", type: "yellow" },
{ key: "random", label: "Random", type: "yellow" }
];
const pageName = mw.config.get("wgPageName");
const isSearch = mw.config.get("wgCanonicalSpecialPageName") === "Search";
const isCategory = pageName && pageName.startsWith("Category:");
if (!isSearch && !isCategory) return;
const currentUrl = new URL(window.location.href);
const currentSort = currentUrl.searchParams.get("sort");
function buildUrl(sortKey) {
const url = new URL(window.location.href);
if (isSearch) {
url.searchParams.set("sort", sortKey);
return url.toString();
}
if (isCategory) {
const categoryName = decodeURIComponent(pageName.replace("Category:", ""));
const newUrl = new URL(window.location.origin + "/w/index.php");
newUrl.searchParams.set("title", "Special:Search");
newUrl.searchParams.set("search", "incategory:" + categoryName);
newUrl.searchParams.set("sort", sortKey);
currentUrl.searchParams.forEach((value, key) => {
if (key.startsWith("ns")) {
newUrl.searchParams.set(key, value);
}
});
return newUrl.toString();
}
return window.location.href;
}
const container = document.createElement("div");
container.id = "searchjoy-container";
container.style.position = "fixed";
container.style.bottom = "5%";
container.style.left = "5%";
container.style.zIndex = "9999";
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.gap = "1px";
container.style.fontFamily = "sans-serif";
const title = document.createElement("div");
title.textContent = "SORT BY";
title.style.fontWeight = "700";
title.style.fontSize = "14px";
title.style.textAlign = "center";
title.style.padding = "2px 2px";
title.style.borderRadius = "6px";
title.style.background = "var(--background-color-neutral-subtle, #f8f9fa)";
title.style.color = "var(--color-base, #202122)";
container.appendChild(title);
function styleButton(btn, type, isActive) {
btn.style.padding = "5px 3px";
btn.style.cursor = "pointer";
btn.style.borderRadius = "6px";
btn.style.border = "1px solid rgba(0,0,0,0.2)";
btn.style.fontWeight = "600";
btn.style.fontSize = "16px";
btn.style.textAlign = "center";
btn.style.position = "relative";
if (type === "created") {
btn.style.background = "rgba(255, 200, 0, 0.8)";
btn.style.color = "#1a1a1a";
} else if (type === "edit") {
btn.style.background = "rgba(0, 120, 255, 0.8)";
btn.style.color = "#ffffff";
} else {
btn.style.background = "rgba(255, 200, 0, 0.8)";
btn.style.color = "#1a1a1a";
}
if (isActive) {
btn.style.outline = "5px solid #00ff00";
btn.style.zIndex = "10";
}
btn.tabIndex = 0;
}
sorts.forEach(({ key, label, type }) => {
const btn = document.createElement("button");
btn.textContent = label;
styleButton(btn, type, currentSort === key);
btn.onclick = () => {
window.location.href = buildUrl(key);
};
container.appendChild(btn);
});
const row = document.createElement("div");
row.style.display = "flex";
row.style.gap = "1px";
extraSorts.forEach(({ key, label, type }) => {
const btn = document.createElement("button");
btn.textContent = label;
btn.style.flex = "1";
styleButton(btn, type, currentSort === key);
btn.onclick = () => {
window.location.href = buildUrl(key);
};
row.appendChild(btn);
});
container.appendChild(row);
document.body.appendChild(container);
}
function safeInit() {
try { init(); } catch (e) { console.error(e); }
}
if (document.readyState === "complete" || document.readyState === "interactive") {
safeInit();
} else {
window.addEventListener("DOMContentLoaded", safeInit);
}
if (typeof mw !== "undefined" && mw.hook) {
mw.hook("wikipage.content").add(() => {
setTimeout(safeInit, 50);
});
}
const observer = new MutationObserver(() => {
if (!document.getElementById("searchjoy-container")) {
safeInit();
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();