Created
April 4, 2026 02:12
-
-
Save seafarer/26db87db8bb8997f3e46fa7e3edcc869 to your computer and use it in GitHub Desktop.
Use this to download a Lovable project. All subfolders must be open. Will not download binary files. Paste into dev tools and go.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (async () => { | |
| const sleep = ms => new Promise(r => setTimeout(r, ms)); | |
| if (!window.JSZip) { | |
| await new Promise((resolve, reject) => { | |
| const s = document.createElement('script'); | |
| s.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js'; | |
| s.onload = resolve; | |
| s.onerror = reject; | |
| document.head.appendChild(s); | |
| }); | |
| } | |
| const panel = document.querySelector('[class*="min-w-64"]'); | |
| const zip = new JSZip(); | |
| const isFile = text => /\.\w{1,10}$/.test(text.trim()); | |
| const getDoc = () => { | |
| const cm = document.querySelector('.cm-content'); | |
| return cm?.cmTile?.view?.state?.doc?.toString() || ''; | |
| }; | |
| const items = [...panel.querySelectorAll('div[class*="cursor-pointer"]')]; | |
| const dirStack = []; | |
| const files = []; | |
| for (const el of items) { | |
| const text = el.textContent.trim(); | |
| const mlDiv = el.querySelector('div[class*="mr-1"]'); | |
| const ml = mlDiv ? parseInt(mlDiv.style.marginLeft) || 0 : 0; | |
| const depth = ml / 12; | |
| while (dirStack.length > depth) dirStack.pop(); | |
| if (isFile(text)) { | |
| files.push({ el, name: text, path: [...dirStack, text].join('/') }); | |
| } else { | |
| dirStack.push(text); | |
| } | |
| } | |
| console.log(`Found ${files.length} files:`); | |
| files.forEach(f => console.log(` ${f.path}`)); | |
| let previousContent = ''; | |
| let success = 0; | |
| for (const file of files) { | |
| console.log(`Clicking: ${file.path}`); | |
| file.el.click(); | |
| let content = ''; | |
| let stableCount = 0; | |
| let lastContent = ''; | |
| for (let i = 0; i < 50; i++) { | |
| await sleep(200); | |
| content = getDoc(); | |
| if (content === previousContent && i < 40) continue; | |
| if (content === lastContent && content !== previousContent) { | |
| stableCount++; | |
| if (stableCount >= 3) break; | |
| } else { | |
| stableCount = 0; | |
| lastContent = content; | |
| } | |
| } | |
| if (content.length > 0) { | |
| zip.file(file.path, content); | |
| previousContent = content; | |
| success++; | |
| console.log(` ✅ ${file.path} (${content.length} chars)`); | |
| } else { | |
| console.log(` ❌ ${file.path} skipped`); | |
| } | |
| } | |
| console.log(`\nDone: ${success}/${files.length} files captured`); | |
| const blob = await zip.generateAsync({ type: 'blob' }); | |
| const a = document.createElement('a'); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = 'lovable-project.zip'; | |
| a.click(); | |
| console.log('🎉 Download started!'); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment