Skip to content

GitHub Folder Concatenatorย #9

@oaustegard

Description

@oaustegard
// First, the readable version of the code
(function() {
    const TEXT_EXTENSIONS = new Set(['txt', 'md', 'py', 'js', 'ts', 'tsx', 'html', 'css', 'json', 'xml', 'csv', 'yml', 'yaml', 'ini', 'cfg']);
    const GITHUB_API = 'https://api.github.com';
    
    // Helper to create the UI overlay
    function createUI() {
        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 10000;
            max-width: 400px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        `;

        const closeBtn = document.createElement('button');
        closeBtn.textContent = 'ร—';
        closeBtn.style.cssText = `
            position: absolute;
            top: 10px;
            right: 10px;
            border: none;
            background: none;
            font-size: 20px;
            cursor: pointer;
            color: #666;
        `;
        closeBtn.onclick = () => document.body.removeChild(overlay);

        const title = document.createElement('h3');
        title.textContent = 'GitHub Folder Concatenator';
        title.style.marginTop = '0';

        const status = document.createElement('div');
        status.style.marginBottom = '10px';

        const downloadBtn = document.createElement('button');
        downloadBtn.textContent = 'Download Files';
        downloadBtn.style.cssText = `
            background: #2ea44f;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            display: none;
        `;

        overlay.append(closeBtn, title, status, downloadBtn);
        document.body.appendChild(overlay);
        return { overlay, status, downloadBtn };
    }

    // Extract current page info from GitHub URL
    function parseGitHubPage() {
        const [, owner, repo, type, branch, ...pathParts] = window.location.pathname.split('/');
        if (!owner || !repo) throw new Error('Not a GitHub repository page');
        const path = pathParts.join('/');
        return { owner, repo, branch, path };
    }

    // Get file content via GitHub API
    async function fetchFileContent(url) {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Failed to fetch file');
        const data = await response.json();
        return atob(data.content);
    }

    // Check if file should be included
    function isTextFile(filename) {
        const ext = filename.split('.').pop().toLowerCase();
        return TEXT_EXTENSIONS.has(ext);
    }

    // Process folder contents recursively
    async function processFolder(owner, repo, path, branch = 'main') {
        const apiUrl = `${GITHUB_API}/repos/${owner}/${repo}/contents/${path}?ref=${branch}`;
        const response = await fetch(apiUrl);
        if (!response.ok) throw new Error('Failed to fetch folder contents');
        
        const contents = await response.json();
        let result = [];

        for (const item of contents) {
            if (item.type === 'file' && isTextFile(item.name)) {
                const content = await fetchFileContent(item.url);
                result.push({
                    url: item.html_url,
                    content: content
                });
            } else if (item.type === 'dir') {
                const subfolderContent = await processFolder(owner, repo, item.path, branch);
                result = result.concat(subfolderContent);
            }
        }

        return result;
    }

    // Main function
    async function main() {
        const ui = createUI();
        try {
            ui.status.textContent = 'Processing folder...';
            
            const { owner, repo, path, branch } = parseGitHubPage();
            const files = await processFolder(owner, repo, path, branch);
            
            if (files.length === 0) {
                ui.status.textContent = 'No text files found in this folder.';
                return;
            }

            const concatenated = files.map(file => 
                `\n---\n${file.url}\n\n${file.content}`
            ).join('\n');

            ui.status.textContent = `Found ${files.length} text files.`;
            ui.downloadBtn.style.display = 'block';
            
            ui.downloadBtn.onclick = () => {
                const blob = new Blob([concatenated], { type: 'text/plain' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `${repo}_${path.replace(/\//g, '_')}_files.txt`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            };

        } catch (error) {
            ui.status.textContent = `Error: ${error.message}`;
            ui.status.style.color = 'red';
        }
    }

    main();
})();

// Now the minified bookmarklet version (one line):
javascript:(function(){const e=new Set(["txt","md","py","js","ts","tsx","html","css","json","xml","csv","yml","yaml","ini","cfg"]);function t(){const e=document.createElement("div");e.style.cssText="position:fixed;top:20px;right:20px;background:white;padding:20px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1);z-index:10000;max-width:400px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif";const t=document.createElement("button");t.textContent="ร—",t.style.cssText="position:absolute;top:10px;right:10px;border:none;background:none;font-size:20px;cursor:pointer;color:#666",t.onclick=()=>document.body.removeChild(e);const n=document.createElement("h3");n.textContent="GitHub Folder Concatenator",n.style.marginTop="0";const o=document.createElement("div");o.style.marginBottom="10px";const r=document.createElement("button");return r.textContent="Download Files",r.style.cssText="background:#2ea44f;color:white;border:none;padding:8px 16px;border-radius:4px;cursor:pointer;display:none",e.append(t,n,o,r),document.body.appendChild(e),{overlay:e,status:o,downloadBtn:r}}async function n(e){const t=await fetch(e);if(!t.ok)throw Error("Failed to fetch file");return atob((await t.json()).content)}function o(t){return e.has(t.split(".").pop().toLowerCase())}async function r(e,t,i,a="main"){const c=await fetch(`https://api.github.com/repos/${e}/${t}/contents/${i}?ref=${a}`);if(!c.ok)throw Error("Failed to fetch folder contents");const s=await c.json();let l=[];for(const d of s)if("file"===d.type&&o(d.name)){const e=await n(d.url);l.push({url:d.html_url,content:e})}else"dir"===d.type&&(l=l.concat(await r(e,t,d.path,a)));return l}!async function(){const e=t();try{e.status.textContent="Processing folder...";const[,t,n,o,i,...a]=window.location.pathname.split("/");if(!t||!n)throw Error("Not a GitHub repository page");const c=a.join("/"),s=await r(t,n,c,i);if(0===s.length)return void(e.status.textContent="No text files found in this folder.");const l=s.map((e=>`\n---\n${e.url}\n\n${e.content}`)).join("\n");e.status.textContent=`Found ${s.length} text files.`,e.downloadBtn.style.display="block",e.downloadBtn.onclick=()=>{const t=URL.createObjectURL(new Blob([l],{type:"text/plain"})),o=document.createElement("a");o.href=t,o.download=`${n}_${c.replace(/\//g,"_")}_files.txt`,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(t)}}catch(t){e.status.textContent=`Error: ${t.message}`,e.status.style.color="red"}})()})();

Tweak to output format

// Here's just the modified concatenation part - replace the existing concatenation in the bookmarklet:

const concatenated = `I'd like to discuss the code from this GitHub folder: ${window.location.href}

Here are the file contents:

${files.map((file, index) => 
    `<document index="${index + 1}">
<source>${file.url}</source>
<document_content>${file.content}</document_content>
</document>`
).join('\n\n')}

Please help me understand and work with this code.`;

// The rest of the bookmarklet remains the same

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions