// ==UserScript==
// @name GitHub Upload Button for Subfolders
// @description Adds an upload button to GitHub repository subfolder pages, enabling direct file uploads into specific folders.
// @icon https://github.githubassets.com/favicons/favicon-dark.svg
// @version 1.1
// @author afkarxyz
// @namespace https://github.com/afkarxyz/misc-scripts/
// @supportURL https://github.com/afkarxyz/misc-scripts/issues
// @license MIT
// @match https://github.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const style = document.createElement('style');
style.textContent = `
.upload-icon {
display: inline-block;
cursor: pointer;
margin-left: 6px;
vertical-align: text-bottom;
transition: transform 0.1s ease;
}
.upload-icon:hover {
transform: scale(1.1);
}
.commit-age-wrapper {
display: flex !important;
align-items: center;
justify-content: space-between;
width: 100%;
min-width: 120px;
padding-right: 1rem;
}
.time-wrapper {
flex: 0 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon-wrapper {
flex: 0 0 auto;
}
`;
document.head.appendChild(style);
const createUploadIcon = () => {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("viewBox", "0 0 16 16");
svg.setAttribute("width", "16");
svg.setAttribute("height", "16");
svg.setAttribute("fill", "currentColor");
svg.classList.add('upload-icon');
svg.setAttribute("aria-label", "Upload to this folder");
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
path1.setAttribute("d", "M7.4,11.4c-0.3,0.3-0.3,0.8,0,1.1c0.3,0.3,0.8,0.3,1.1,0L11,10c0.3-0.3,0.3-0.8,0-1.1L8.5,6.3C8.2,6,7.7,6,7.4,6.3s-0.3,0.8,0,1.1l1.2,1.2l-7.9,0C0.3,8.7,0,9,0,9.4c0,0.4,0.3,0.8,0.8,0.8h7.9L7.4,11.4z");
const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path");
path2.setAttribute("d", "M15.5,3.4l-2.9-2.9C12.2,0.2,11.8,0,11.3,0H4.8C3.8,0,3,0.8,3,1.8v5.2h1.5V1.8c0-0.1,0.1-0.2,0.2-0.2H10v2.8c0,1,0.8,1.8,1.8,1.8h2.8v8.2c0,0.1-0.1,0.2-0.2,0.2H4.8c-0.1,0-0.2-0.1-0.2-0.2v-2.3H3v2.3c0,1,0.8,1.8,1.8,1.8h9.5c1,0,1.8-0.8,1.8-1.8V4.7C16,4.2,15.8,3.8,15.5,3.4z M11.8,4.5c-0.1,0-0.2-0.1-0.2-0.2V1.6l0,0l2.9,2.9l0,0H11.8z");
g.appendChild(path1);
g.appendChild(path2);
svg.appendChild(g);
return svg;
};
const getUploadUrl = (row) => {
try {
const link = row.querySelector('.react-directory-truncate a');
if (link) {
const currentPath = link.getAttribute('title');
const baseUrl = window.location.pathname.split('/tree/')[0];
return `https://github.com${baseUrl}/upload/main/${currentPath}`;
}
} catch (error) {
console.error('Error getting upload URL:', error);
}
return null;
};
const insertUploadIcon = () => {
try {
const rows = document.querySelectorAll('.react-directory-row');
rows.forEach(row => {
const isFolder = row.querySelector('.icon-directory');
if (isFolder && !row.querySelector('.upload-icon')) {
const commitAgeDiv = row.querySelector('.react-directory-commit-age');
if (commitAgeDiv) {
const wrapper = document.createElement('div');
wrapper.className = 'commit-age-wrapper';
const timeWrapper = document.createElement('div');
timeWrapper.className = 'time-wrapper';
const iconWrapper = document.createElement('div');
iconWrapper.className = 'icon-wrapper';
const timeElement = commitAgeDiv.querySelector('relative-time');
if (timeElement) {
timeWrapper.appendChild(timeElement.cloneNode(true));
timeElement.remove();
}
const uploadIcon = createUploadIcon();
const uploadUrl = getUploadUrl(row);
if (uploadUrl) {
uploadIcon.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
window.location.href = uploadUrl;
});
iconWrapper.appendChild(uploadIcon);
}
wrapper.appendChild(timeWrapper);
wrapper.appendChild(iconWrapper);
commitAgeDiv.appendChild(wrapper);
}
}
});
} catch (error) {
console.error('Error inserting upload icons:', error);
}
};
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes.length) {
insertUploadIcon();
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
insertUploadIcon();
})();