···
2
+
// @name Hack Club Summer Votes Helper
3
+
// @namespace http://tampermonkey.net/
5
+
// @description Enhance the voting experience on Hack Club Summer votes page
7
+
// @match https://summer.hackclub.com/votes/new
8
+
// @match https://summer.hackclub.com/votes/*
13
+
console.log("Hack Club Summer Votes Helper loaded");
15
+
function addHackClubSummerVotesHelperButton() {
16
+
// Prevent adding multiple buttons
17
+
if (document.getElementById("hackclub-summer-votes-helper-btn")) {
18
+
console.log("Button already exists, skipping");
22
+
// Find the <p class="text-center"> element
23
+
const p = document.querySelector("p.text-center");
25
+
console.log("Target element not found yet");
29
+
console.log("Adding helper button");
31
+
// Create a new button
32
+
const openButton = document.createElement("button");
33
+
openButton.textContent = "Open Project Links";
34
+
openButton.style.margin = "10px";
35
+
openButton.style.display = "block";
36
+
openButton.style.marginLeft = "auto";
37
+
openButton.style.marginRight = "auto";
38
+
openButton.className = "som-button-primary";
39
+
openButton.id = "hackclub-summer-votes-helper-btn";
41
+
// Add click event to open the URLs
42
+
function openProjectLinks() {
43
+
console.log("Helper button clicked");
45
+
// Find all buttons with a div > span whose text is "Demo" or "Repository"
46
+
const allButtons = Array.from(
47
+
document.getElementsByClassName("som-button-primary"),
49
+
const filteredButtons = allButtons.filter((btn) => {
50
+
const spans = btn.querySelectorAll("div > span");
51
+
return Array.from(spans).some((span) => {
52
+
const text = span.textContent.trim();
53
+
return text === "Demo" || text === "Repository";
57
+
console.log(`Found ${filteredButtons.length} project buttons`);
59
+
filteredButtons.forEach((btn) => {
60
+
const url = btn.href || btn.getAttribute("data-href");
62
+
console.log(`Opening: ${url}`);
63
+
window.open(url, "_blank");
67
+
// Set the hidden inputs to true
69
+
"vote_project_1_demo_opened",
70
+
"vote_project_1_repo_opened",
71
+
"vote_project_2_demo_opened",
72
+
"vote_project_2_repo_opened",
75
+
inputIds.forEach((id) => {
76
+
const input = document.getElementById(id);
78
+
input.value = "true";
79
+
console.log(`Set ${id} to true`);
84
+
openButton.addEventListener("click", openProjectLinks);
86
+
p.appendChild(openButton);
87
+
console.log("Button added successfully");
90
+
function runHelperOnPageLoad() {
91
+
// Only run on the correct page
93
+
window.location.pathname === "/votes/new" ||
94
+
window.location.pathname.startsWith("/votes/")
96
+
console.log("Running helper on correct page");
97
+
addHackClubSummerVotesHelperButton();
102
+
runHelperOnPageLoad();
104
+
// Method 1: Intercept fetch/XMLHttpRequest to detect page changes
105
+
const originalFetch = window.fetch;
106
+
window.fetch = function (...args) {
107
+
return originalFetch.apply(this, args).then((response) => {
108
+
if (response.url.includes("/votes/new")) {
109
+
console.log("Detected fetch to votes page");
110
+
setTimeout(runHelperOnPageLoad, 100);
116
+
const originalXHROpen = XMLHttpRequest.prototype.open;
117
+
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
118
+
this.addEventListener("load", () => {
119
+
if (url.includes("/votes/new")) {
120
+
console.log("Detected XHR to votes page");
121
+
setTimeout(runHelperOnPageLoad, 100);
124
+
return originalXHROpen.call(this, method, url, ...rest);
127
+
// Method 2: Enhanced MutationObserver
128
+
let observerTimeout;
129
+
const observer = new MutationObserver((_mutations) => {
130
+
// Debounce to avoid excessive calls
131
+
clearTimeout(observerTimeout);
132
+
observerTimeout = setTimeout(() => {
133
+
console.log("DOM mutation detected");
134
+
runHelperOnPageLoad();
138
+
// Observe the entire document for changes
139
+
observer.observe(document.documentElement, {
145
+
// Method 3: Polling fallback (less elegant but very reliable)
146
+
setInterval(() => {
148
+
window.location.pathname === "/votes/new" ||
149
+
window.location.pathname.startsWith("/votes/")
151
+
if (!document.getElementById("hackclub-summer-votes-helper-btn")) {
152
+
console.log("Button missing, re-adding via polling");
153
+
addHackClubSummerVotesHelperButton();
158
+
// Method 4: Listen for history changes
159
+
const originalPushState = history.pushState;
160
+
const originalReplaceState = history.replaceState;
162
+
history.pushState = function (...args) {
163
+
originalPushState.apply(this, args);
164
+
setTimeout(runHelperOnPageLoad, 100);
167
+
history.replaceState = function (...args) {
168
+
originalReplaceState.apply(this, args);
169
+
setTimeout(runHelperOnPageLoad, 100);
172
+
window.addEventListener("popstate", () => {
173
+
setTimeout(runHelperOnPageLoad, 100);
176
+
// Method 5: Listen for focus events (when user returns to tab)
177
+
window.addEventListener("focus", () => {
178
+
setTimeout(runHelperOnPageLoad, 100);
181
+
// Add listener for Shift + S shortcut
182
+
window.addEventListener("keydown", (e) => {
183
+
// Ignore if input/textarea/select is focused
184
+
const tag = document.activeElement.tagName;
187
+
tag === "TEXTAREA" ||
188
+
tag === "SELECT" ||
189
+
document.activeElement.isContentEditable
193
+
if (e.shiftKey && (e.key === "s" || e.key === "S")) {
194
+
// Only run on the correct page
196
+
window.location.pathname === "/votes/new" ||
197
+
window.location.pathname.startsWith("/votes/")
199
+
const btn = document.getElementById("hackclub-summer-votes-helper-btn");
203
+
// If button not present, run the logic directly
204
+
// (duplicate logic from openButton click)
205
+
// Find all buttons with a div > span whose text is "Demo" or "Repository"
206
+
const allButtons = Array.from(
207
+
document.getElementsByClassName("som-button-primary"),
209
+
const filteredButtons = allButtons.filter((btn) => {
210
+
const spans = btn.querySelectorAll("div > span");
211
+
return Array.from(spans).some((span) => {
212
+
const text = span.textContent.trim();
213
+
return text === "Demo" || text === "Repository";
217
+
filteredButtons.forEach((btn) => {
218
+
const url = btn.href || btn.getAttribute("data-href");
220
+
window.open(url, "_blank");
224
+
// Set the hidden inputs to true
226
+
"vote_project_1_demo_opened",
227
+
"vote_project_1_repo_opened",
228
+
"vote_project_2_demo_opened",
229
+
"vote_project_2_repo_opened",
232
+
inputIds.forEach((id) => {
233
+
const input = document.getElementById(id);
235
+
input.value = "true";
239
+
// Prevent default browser behavior
240
+
e.preventDefault();
245
+
// Add listener for Ctrl+Enter to submit vote, even in a textbox
246
+
window.addEventListener("keydown", (e) => {
247
+
if (e.ctrlKey && (e.key === "Enter" || e.keyCode === 13)) {
248
+
// Only run on the correct page
250
+
window.location.pathname === "/votes/new" ||
251
+
window.location.pathname.startsWith("/votes/")
253
+
// Find the submit vote button
254
+
// <button name="button" type="submit" class="som-button-primary " data-form-target="submitButton">
255
+
// <div class="flex items-center justify-center gap-2">
256
+
// <span class="flex items-center gap-1">Submit Vote</span>
259
+
const submitButtons = Array.from(
260
+
document.querySelectorAll(
261
+
'button.som-button-primary[data-form-target="submitButton"]',
265
+
for (const btn of submitButtons) {
266
+
// Check if the button contains a span with text "Submit Vote"
267
+
const span = btn.querySelector("span");
268
+
if (span && span.textContent.trim() === "Submit Vote") {
275
+
e.preventDefault();
281
+
console.log("All event listeners and observers set up");