Files
claude-artifact-scraper/src/exporter.js

153 lines
4.5 KiB
JavaScript

const ArtifactExporter = {
parseFileMap(fileMap) {
const lines = fileMap.split('\n').filter(line => line.trim());
const pathMapping = {};
let currentPath = '';
lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine.endsWith('/')) {
currentPath = trimmedLine;
} else if (trimmedLine && !trimmedLine.startsWith('/')) {
if (line.startsWith(' ')) {
pathMapping[trimmedLine] = currentPath + trimmedLine;
} else {
pathMapping[trimmedLine] = trimmedLine;
}
}
});
return pathMapping;
},
extractFilenamesFromFileMap(fileMap) {
const lines = fileMap.split('\n').filter(line => line.trim());
const filenames = [];
lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine.endsWith('/') || !trimmedLine || trimmedLine.startsWith('/')) {
return;
}
const filename = trimmedLine.replace(/^\s+/, '');
if (filename) {
filenames.push(filename);
}
});
return filenames.sort((a, b) => b.length - a.length);
},
extractFilenameFromArtifactName(artifactName, sortedFilenames) {
for (const filename of sortedFilenames) {
const lowerArtifactName = artifactName.toLowerCase();
const lowerFilename = filename.toLowerCase();
if (lowerArtifactName.includes(lowerFilename)) {
return filename;
}
}
return artifactName;
},
createFinalMapping(artifactCollection, pathMapping) {
const finalMap = {};
const sortedFilenames = this.extractFilenamesFromFileMap(Object.keys(pathMapping).join('\n'));
Object.entries(artifactCollection).forEach(([artifactName, content]) => {
const extractedFilename = this.extractFilenameFromArtifactName(artifactName, sortedFilenames);
const fullPath = pathMapping[extractedFilename] || extractedFilename;
if (artifactName !== extractedFilename) {
console.log(`Mapped artifact "${artifactName}" -> found filename "${extractedFilename}" -> "${fullPath}"`);
} else {
console.log(`Mapped artifact "${artifactName}" -> "${fullPath}"`);
}
finalMap[fullPath] = content;
});
return finalMap;
},
generateZipFilename() {
const now = new Date();
const timestamp = now.getFullYear() + '-' +
String(now.getMonth() + 1).padStart(2, '0') + '-' +
String(now.getDate()).padStart(2, '0');
return `${timestamp}_claude-artifacts.zip`;
},
addFilesToZip(zip, finalMap) {
Object.entries(finalMap).forEach(([fullPath, content]) => {
const pathParts = fullPath.split('/');
if (pathParts.length > 1) {
const filename = pathParts.pop();
const folderPath = pathParts.join('/');
const folder = zip.folder(folderPath);
folder.file(filename, content);
} else {
zip.file(fullPath, content);
}
});
},
downloadZip(content, filename) {
const url = URL.createObjectURL(content);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log(`✓ Downloaded: ${filename}`);
},
exportArtifacts(artifactCollection, fileMap) {
// Validation
if (!artifactCollection || Object.keys(artifactCollection).length === 0) {
console.warn("No non-filemap artifacts were found. Ask the LLM to copy them from the project to this chat first.");
return;
}
if (!fileMap) {
console.warn("No filemap was found. Ask the LLM to generate one. See the README for a sample prompt.");
return;
}
// Parse the file map into paths
const pathMapping = this.parseFileMap(fileMap);
// Create final map with full paths and contents
const finalMap = this.createFinalMapping(artifactCollection, pathMapping);
// Create zip file
const zip = new JSZip();
// Add each file to the zip
this.addFilesToZip(zip, finalMap);
// Generate filename
const zipFilename = this.generateZipFilename();
// Generate and download the zip
zip.generateAsync({type: "blob"})
.then((content) => {
this.downloadZip(content, zipFilename);
})
.catch((error) => {
console.error('Failed to generate zip:', error);
});
}
};
// No exports needed - will be in same scope after build