-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
// 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 sameoaustegard
Metadata
Metadata
Assignees
Labels
No labels