diff --git a/background.js b/background.js index 49ccb56..b6a2bc0 100644 --- a/background.js +++ b/background.js @@ -2,7 +2,7 @@ browser.browserAction.onClicked.addListener((tab) => { console.log('Extension clicked - triggering artifact collection.'); // Check if we're on the correct domain and path - if (!tab.url.startsWith('https://claude.ai/chat/')) { + if (!tab.url.startsWith('https://claude.ai/chat') && !tab.url.startsWith('https://claude.ai/project')) { console.log('Not on claude.ai chat page'); return; } diff --git a/src/scraper.js b/src/scraper.js index 272dcf8..8a8b570 100644 --- a/src/scraper.js +++ b/src/scraper.js @@ -234,18 +234,124 @@ const ChatArtifactScraper = { }; const ProjectArtifactScraper = { + getArtifactListItems() { + const gridContainer = document.querySelector('ul.grid'); + const artifactHash = {}; + + if (!gridContainer) { + return artifactHash; + } + + gridContainer.querySelectorAll('h3').forEach((titleElement) => { + const filename = titleElement.textContent.trim(); + artifactHash[filename] = false; + }); + + return artifactHash; + }, + + getArtifact(filename, isCurrentlySelected) { + return new Promise((resolve, reject) => { + // Find the h3 element with the matching filename + const gridContainer = document.querySelector('ul.grid'); + if (!gridContainer) { + reject(new Error('Grid container not found')); + return; + } + + let targetButton = null; + gridContainer.querySelectorAll('h3').forEach((titleElement) => { + if (titleElement.textContent.trim() === filename) { + // Find the clickable button that contains this h3 + targetButton = titleElement.closest('button'); + } + }); + + if (!targetButton) { + reject(new Error(`Could not find file button for: ${filename}`)); + return; + } + + // Click the button to open the file modal + targetButton.click(); + + // Wait for modal to appear and extract content + let attempts = 0; + const maxAttempts = 30; // 3 seconds total + + const handleModal = () => { + // Look for the modal dialog and the content div within it + const modalDialog = document.querySelector('div[role="dialog"][data-state="open"]'); + + if (modalDialog) { + const contentDiv = modalDialog.querySelector('div.whitespace-pre-wrap'); + + if (contentDiv) { + const content = contentDiv.textContent || contentDiv.innerText || ''; + + // Close the modal by clicking the close button + const closeButton = modalDialog.querySelector('button.relative.can-focus svg'); + if (closeButton) { + closeButton.closest('button').click(); + } + + resolve(content); + return; + } + } + + attempts++; + if (attempts < maxAttempts) { + setTimeout(handleModal, 100); + } else { + reject(new Error(`Modal with content not found for artifact: ${filename}`)); + } + }; + + // Start checking for modal after a brief delay + setTimeout(handleModal, 100); + }); + }, async collectArtifacts() { - // TODO: Implement project page artifact collection - console.log('Project page artifact collection not yet implemented'); - return { artifacts: {}, fileMap: null }; + const artifactCollection = {}; + let fileMap = null; + + const artifactList = this.getArtifactListItems(); + + if (Object.keys(artifactList).length === 0) { + console.log('Nada'); + return { artifacts: artifactCollection, fileMap: fileMap }; + } + + console.log(`Found ${Object.keys(artifactList).length} artifacts to collect`); + + for (const [filename, isSelected] of Object.entries(artifactList)) { + try { + const content = await this.getArtifact(filename, isSelected); + + if (filename === 'files.txt') { + fileMap = content; + console.log(`✓ Found files.txt - storing in fileMap`); + } else { + artifactCollection[filename] = content; + console.log(`✓ Collected: ${filename}`); + } + } catch (error) { + console.log(`✗ Failed to collect: ${filename} - ${error.message}`); + } + + await new Promise(resolve => setTimeout(resolve, 500)); + } + + return { artifacts: artifactCollection, fileMap: fileMap }; } }; const ArtifactScraper = { - async collectArtifacts() { + console.log('Collecting') if (ProjectMeta.isProjectChat()) { console.log('Detected project chat - using ChatArtifactScraper'); return await ChatArtifactScraper.collectArtifacts();