GitHub Repo File Downloader

Allows you to download individual files directly from GitHub repository pages.

Per 09-12-2024. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         GitHub Repo File Downloader
// @description  Allows you to download individual files directly from GitHub repository pages.
// @icon         https://github.githubassets.com/favicons/favicon-dark.svg
// @version      1.0
// @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';

    function isIconModified(svg) {
        return svg.dataset.customIcon === 'true';
    }

    function modifySvgContent(svg, newContent, downloadUrl, fileName) {
        if (svg && !isIconModified(svg)) {
            const wrapper = document.createElement('div');
            wrapper.style.cursor = 'pointer';
            wrapper.style.display = 'inline-block';
            wrapper.style.transition = 'transform 0.1s ease-in-out';
            
            const newSvg = svg.cloneNode(true);
            newSvg.innerHTML = newContent;
            newSvg.dataset.customIcon = 'true';
            
            wrapper.appendChild(newSvg);
            wrapper.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                downloadFile(downloadUrl, fileName);
            });
            
            wrapper.addEventListener('mouseenter', () => {
                wrapper.style.transform = 'scale(1.1)';
            });
            wrapper.addEventListener('mouseleave', () => {
                wrapper.style.transform = 'scale(1)';
            });
            
            svg.parentNode.replaceChild(wrapper, svg);
        }
    }

    async function downloadFile(url, fileName) {
        try {
            const response = await fetch(url);
            const blob = await response.blob();
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = fileName;
            link.style.display = 'none';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        } catch (error) {
            console.error('Download failed:', error);
        }
    }

    function updateIcons() {
        const fileRows = document.querySelectorAll('.react-directory-row');
        let modifiedFileCount = 0;

        fileRows.forEach(row => {
            const svgs = row.querySelectorAll('.react-directory-filename-column svg:not([data-custom-icon="true"])');

            svgs.forEach(svg => {
                if (svg.classList.contains('color-fg-muted')) {
                    const fileLink = row.querySelector('a[href]');
                    if (!fileLink) return;
                    
                    const downloadUrl = fileLink.href
                        .replace('github.com', 'raw.githubusercontent.com')
                        .replace('/blob/', '/');
                    
                    const fileName = fileLink.textContent.trim();

                    const newFileContent = `
                        <path d="M14.5,3.4l-2.9-2.9C11.2,0.2,10.8,0,10.3,0H3.8C2.8,0,2,0.8,2,1.8v12.5c0,1,0.8,1.8,1.8,1.8h9.5c1,0,1.8-0.8,1.8-1.8V4.7
                            C15,4.2,14.8,3.8,14.5,3.4z M10.5,1.6L10.5,1.6l2.9,2.9l0,0h-2.7c-0.1,0-0.2-0.1-0.2-0.2V1.6z M13.5,14.2c0,0.1-0.1,0.2-0.2,0.2
                            H3.8c-0.1,0-0.2-0.1-0.2-0.2V1.8c0-0.1,0.1-0.2,0.2-0.2H9v2.8C9,5.2,9.8,6,10.8,6h2.8V14.2z"/>
                        <path d="M9.1,10.6V7.3c0-0.3-0.3-0.6-0.6-0.6S7.9,7,7.9,7.3v3.3L6.5,9.3C6.3,9,5.9,9,5.7,9.3c-0.2,0.2-0.2,0.6,0,0.8l2.4,2.4
                            c0.2,0.2,0.6,0.2,0.8,0h0l2.4-2.4c0.2-0.2,0.2-0.6,0-0.8c-0.2-0.2-0.6-0.2-0.8,0L9.1,10.6z"/>
                    `;
                    modifySvgContent(svg, newFileContent, downloadUrl, fileName);
                    modifiedFileCount++;
                }
            });
        });

        if (modifiedFileCount > 0) {
            console.log(`Modified ${modifiedFileCount} file icons with direct download functionality and hover effects.`);
        }
    }

    function runIconUpdate() {
        function debouncedUpdate() {
            updateIcons();
        }

        debouncedUpdate();

        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    debouncedUpdate();
                }
            });
        });

        observer.observe(document.body, { childList: true, subtree: true });

        console.log('GitHub Repo File Downloader: Script is now running.');
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', runIconUpdate);
    } else {
        runIconUpdate();
    }
})();