V1
15
background.js
Normal file
@ -0,0 +1,15 @@
|
||||
// Background script for Firefox extension
|
||||
browser.browserAction.onClicked.addListener((tab) => {
|
||||
console.log('Extension clicked');
|
||||
|
||||
// Check if we're on the correct domain and path
|
||||
if (!tab.url.startsWith('https://claude.ai/chat/')) {
|
||||
console.log('Not on claude.ai chat page');
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the content script file
|
||||
browser.tabs.executeScript(tab.id, {
|
||||
file: 'content.js'
|
||||
});
|
||||
});
|
||||
159
content.js
Normal file
@ -0,0 +1,159 @@
|
||||
// Content script for artifact processing
|
||||
(function() {
|
||||
console.log('Content script loaded');
|
||||
|
||||
// Check if we're in a project chat by looking for the project link with forward slash
|
||||
function isProjectChat() {
|
||||
const projectLink = document.querySelector('a[href*="/project/"]');
|
||||
if (!projectLink) {
|
||||
console.log('Not in a project chat - no project link found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Additional check: look for the forward slash separator in the project breadcrumb
|
||||
const breadcrumbWithSlash = projectLink.querySelector('span.opacity-50');
|
||||
if (!breadcrumbWithSlash || !breadcrumbWithSlash.textContent.includes('/')) {
|
||||
console.log('Not in a project chat - no forward slash separator found');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Confirmed we are in a project chat');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Function to trigger context menu and extract filename
|
||||
function getArtifactFilename(artifact) {
|
||||
return new Promise((resolve) => {
|
||||
// Look for context menu trigger button (usually three dots or similar)
|
||||
const contextMenuTrigger = artifact.querySelector('[data-testid*="menu"], [aria-label*="menu"], button[aria-haspopup="menu"]');
|
||||
|
||||
if (!contextMenuTrigger) {
|
||||
console.log('No context menu trigger found for artifact');
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Function to handle menu opening
|
||||
function handleMenuOpen() {
|
||||
// Wait a bit for menu to render
|
||||
setTimeout(() => {
|
||||
// Look for download or filename-related menu items
|
||||
const menuItems = document.querySelectorAll('[role="menuitem"], [data-testid*="download"], [data-testid*="save"]');
|
||||
|
||||
let filename = null;
|
||||
|
||||
menuItems.forEach(item => {
|
||||
const text = item.textContent || item.getAttribute('aria-label') || '';
|
||||
|
||||
// Look for download options that might contain filename
|
||||
if (text.toLowerCase().includes('download') || text.toLowerCase().includes('save')) {
|
||||
console.log('Found menu item:', text);
|
||||
|
||||
// Try to extract filename from the text
|
||||
// Common patterns: "Download filename.ext", "Save as filename.ext", etc.
|
||||
const filenameMatch = text.match(/(?:download|save(?:\s+as)?)\s+(.+\.\w+)/i);
|
||||
if (filenameMatch) {
|
||||
filename = filenameMatch[1].trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close the menu by clicking elsewhere or pressing escape
|
||||
document.body.click();
|
||||
|
||||
resolve(filename);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Listen for menu opening
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList') {
|
||||
const addedNodes = Array.from(mutation.addedNodes);
|
||||
const menuAdded = addedNodes.some(node =>
|
||||
node.nodeType === Node.ELEMENT_NODE &&
|
||||
(node.getAttribute('role') === 'menu' ||
|
||||
node.querySelector('[role="menu"]') ||
|
||||
node.classList?.contains('menu') ||
|
||||
node.getAttribute('data-testid')?.includes('menu'))
|
||||
);
|
||||
|
||||
if (menuAdded) {
|
||||
observer.disconnect();
|
||||
handleMenuOpen();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// Trigger the context menu
|
||||
contextMenuTrigger.click();
|
||||
|
||||
// Fallback timeout
|
||||
setTimeout(() => {
|
||||
observer.disconnect();
|
||||
if (filename === null) {
|
||||
resolve(null);
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// Find and process artifacts
|
||||
async function processArtifacts() {
|
||||
if (!isProjectChat()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for artifacts in the page
|
||||
// Try multiple selectors that might match artifacts
|
||||
const artifacts = document.querySelectorAll('[data-testid*="artifact"], .artifact, [class*="artifact"], iframe[title*="artifact"]');
|
||||
|
||||
if (artifacts.length === 0) {
|
||||
console.log('No artifacts found on this page');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${artifacts.length} artifacts`);
|
||||
|
||||
// Process each artifact one by one to avoid conflicts
|
||||
for (let i = 0; i < artifacts.length; i++) {
|
||||
const artifact = artifacts[i];
|
||||
console.log(`Processing artifact ${i + 1}`);
|
||||
|
||||
try {
|
||||
// Try to get the filename from context menu
|
||||
const filename = await getArtifactFilename(artifact);
|
||||
|
||||
if (filename) {
|
||||
console.log(`Artifact ${i + 1} filename: ${filename}`);
|
||||
} else {
|
||||
console.log(`Artifact ${i + 1} filename: Could not determine filename`);
|
||||
}
|
||||
|
||||
// Extract artifact content - this will depend on the actual structure
|
||||
const content = artifact.textContent || artifact.innerHTML;
|
||||
console.log(`Artifact ${i + 1} content preview: ${content.substring(0, 100)}...`);
|
||||
|
||||
// TODO: Add download functionality here
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error processing artifact ${i + 1}:`, error);
|
||||
}
|
||||
|
||||
// Small delay between processing artifacts
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
|
||||
// Main function to run when content script is executed
|
||||
async function main() {
|
||||
console.log('Running artifact processor');
|
||||
await processArtifacts();
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main();
|
||||
})();
|
||||
BIN
icons/icon-16.png
Normal file
|
After Width: | Height: | Size: 535 B |
BIN
icons/icon-32.png
Normal file
|
After Width: | Height: | Size: 1005 B |
BIN
icons/icon-48.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
icons/icon-96.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
23
icons/icon_16.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Claude's splat icon base -->
|
||||
<g fill="#D97757">
|
||||
<!-- Main splat shape -->
|
||||
<path d="M8 1c-1.25 0-2.4 0.6-3.1 1.55C4.1 1.9 3.25 1.75 2.45 2.1c-0.8 0.35-1.4 1.05-1.6 1.9c-0.2 0.85 0.05 1.75 0.65 2.4c-0.4 0.9-0.4 1.95 0 2.85c0.4 0.9 1.15 1.6 2.1 1.9c0.95 0.3 2 0.15 2.8-0.4C6.1 11.7 7.4 12 8.75 11.75c1.35-0.25 2.55-1.05 3.25-2.2c0.7-1.15 0.8-2.55 0.3-3.8c0.4-0.6 0.55-1.35 0.4-2.05c-0.15-0.7-0.6-1.3-1.2-1.7c-0.6-0.4-1.35-0.5-2.05-0.3C9.6 1.3 8.85 1 8 1z"/>
|
||||
<!-- Splat details -->
|
||||
<circle cx="6" cy="5" r="0.75" fill="#B85C3E"/>
|
||||
<circle cx="10" cy="6" r="0.5" fill="#B85C3E"/>
|
||||
<circle cx="7" cy="9" r="0.6" fill="#B85C3E"/>
|
||||
<ellipse cx="9" cy="4" rx="0.4" ry="0.6" fill="#B85C3E"/>
|
||||
</g>
|
||||
|
||||
<!-- Download arrow in bottom right -->
|
||||
<g transform="translate(10, 10)">
|
||||
<rect x="0" y="0" width="6" height="6" rx="1" fill="white" stroke="#333" stroke-width="0.25"/>
|
||||
<g transform="translate(3, 3)" fill="#333">
|
||||
<!-- Arrow shaft -->
|
||||
<rect x="-0.25" y="-1.5" width="0.5" height="2"/>
|
||||
<!-- Arrow head -->
|
||||
<path d="M0,-0.5 L-0.75,0.25 L-0.25,0.25 L-0.25,0.75 L0.25,0.75 L0.25,0.25 L0.75,0.25 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
23
icons/icon_32.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Claude's splat icon base -->
|
||||
<g fill="#D97757">
|
||||
<!-- Main splat shape -->
|
||||
<path d="M16 2c-2.5 0-4.8 1.2-6.2 3.1C8.2 3.8 6.5 3.5 4.9 4.2c-1.6 0.7-2.8 2.1-3.2 3.8c-0.4 1.7 0.1 3.5 1.3 4.8c-0.8 1.8-0.8 3.9 0 5.7c0.8 1.8 2.3 3.2 4.2 3.8c1.9 0.6 4 0.3 5.6-0.8C14.2 23.4 16.8 24 19.5 23.5c2.7-0.5 5.1-2.1 6.5-4.4c1.4-2.3 1.6-5.1 0.6-7.6c0.8-1.2 1.1-2.7 0.8-4.1c-0.3-1.4-1.2-2.6-2.4-3.4c-1.2-0.8-2.7-1-4.1-0.6C19.2 2.6 17.7 2 16 2z"/>
|
||||
<!-- Splat details -->
|
||||
<circle cx="12" cy="10" r="1.5" fill="#B85C3E"/>
|
||||
<circle cx="20" cy="12" r="1" fill="#B85C3E"/>
|
||||
<circle cx="14" cy="18" r="1.2" fill="#B85C3E"/>
|
||||
<ellipse cx="18" cy="8" rx="0.8" ry="1.2" fill="#B85C3E"/>
|
||||
</g>
|
||||
|
||||
<!-- Download arrow in bottom right -->
|
||||
<g transform="translate(20, 20)">
|
||||
<rect x="0" y="0" width="12" height="12" rx="2" fill="white" stroke="#333" stroke-width="0.5"/>
|
||||
<g transform="translate(6, 6)" fill="#333">
|
||||
<!-- Arrow shaft -->
|
||||
<rect x="-0.5" y="-3" width="1" height="4"/>
|
||||
<!-- Arrow head -->
|
||||
<path d="M0,-1 L-1.5,0.5 L-0.5,0.5 L-0.5,1.5 L0.5,1.5 L0.5,0.5 L1.5,0.5 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
23
icons/icon_48.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Claude's splat icon base -->
|
||||
<g fill="#D97757">
|
||||
<!-- Main splat shape -->
|
||||
<path d="M24 3c-3.75 0-7.2 1.8-9.3 4.65C12.3 5.7 9.75 5.25 7.35 6.3c-2.4 1.05-4.2 3.15-4.8 5.7c-0.6 2.55 0.15 5.25 1.95 7.2c-1.2 2.7-1.2 5.85 0 8.55c1.2 2.7 3.45 4.8 6.3 5.7c2.85 0.9 6 0.45 8.4-1.2C20.3 35.1 23.4 36 26.25 35.25c2.85-0.75 5.4-2.25 7.2-4.2c1.8-1.95 2.4-4.35 1.8-6.75c1.2-1.8 1.65-4.05 1.2-6.15c-0.45-2.1-1.8-3.9-3.6-5.1c-1.8-1.2-4.05-1.5-6.15-0.9C25.8 3.9 24.9 3 24 3z"/>
|
||||
<!-- Splat details -->
|
||||
<circle cx="18" cy="15" r="2.25" fill="#B85C3E"/>
|
||||
<circle cx="30" cy="18" r="1.5" fill="#B85C3E"/>
|
||||
<circle cx="21" cy="27" r="1.8" fill="#B85C3E"/>
|
||||
<ellipse cx="27" cy="12" rx="1.2" ry="1.8" fill="#B85C3E"/>
|
||||
</g>
|
||||
|
||||
<!-- Download arrow in bottom right -->
|
||||
<g transform="translate(30, 30)">
|
||||
<rect x="0" y="0" width="18" height="18" rx="3" fill="white" stroke="#333" stroke-width="0.75"/>
|
||||
<g transform="translate(9, 9)" fill="#333">
|
||||
<!-- Arrow shaft -->
|
||||
<rect x="-0.75" y="-4.5" width="1.5" height="6"/>
|
||||
<!-- Arrow head -->
|
||||
<path d="M0,-1.5 L-2.25,0.75 L-0.75,0.75 L-0.75,2.25 L0.75,2.25 L0.75,0.75 L2.25,0.75 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
23
icons/icon_96.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg width="96" height="96" viewBox="0 0 96 96" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Claude's splat icon base -->
|
||||
<g fill="#D97757">
|
||||
<!-- Main splat shape -->
|
||||
<path d="M48 6c-7.5 0-14.4 3.6-18.6 9.3C24.6 11.4 19.5 10.5 14.7 12.6c-4.8 2.1-8.4 6.3-9.6 11.4c-1.2 5.1 0.3 10.5 3.9 14.4c-2.4 5.4-2.4 11.7 0 17.1c2.4 5.4 6.9 9.6 12.6 11.4c5.7 1.8 12 0.9 16.8-2.4C40.6 70.2 46.8 72 53.5 70.5c6.6-1.5 12.6-4.5 16.5-8.4c3.9-3.9 5.4-9 4.2-13.8c2.4-3.6 3.3-8.1 2.4-12.3c-0.9-4.2-3.6-7.8-7.2-10.2c-3.6-2.4-8.1-3-12.3-1.8C55.8 7.8 52.2 6 48 6z"/>
|
||||
<!-- Splat details -->
|
||||
<circle cx="36" cy="30" r="4.5" fill="#B85C3E"/>
|
||||
<circle cx="60" cy="36" r="3" fill="#B85C3E"/>
|
||||
<circle cx="42" cy="54" r="3.6" fill="#B85C3E"/>
|
||||
<ellipse cx="54" cy="24" rx="2.4" ry="3.6" fill="#B85C3E"/>
|
||||
</g>
|
||||
|
||||
<!-- Download arrow in bottom right -->
|
||||
<g transform="translate(60, 60)">
|
||||
<rect x="0" y="0" width="36" height="36" rx="6" fill="white" stroke="#333" stroke-width="1.5"/>
|
||||
<g transform="translate(18, 18)" fill="#333">
|
||||
<!-- Arrow shaft -->
|
||||
<rect x="-1.5" y="-9" width="3" height="12"/>
|
||||
<!-- Arrow head -->
|
||||
<path d="M0,-3 L-4.5,1.5 L-1.5,1.5 L-1.5,4.5 L1.5,4.5 L1.5,1.5 L4.5,1.5 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
32
manifest.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Claude Download Helper",
|
||||
"version": "1.0",
|
||||
"description": "A simple Firefox extension with Claude's splat icon",
|
||||
|
||||
"permissions": [
|
||||
"activeTab"
|
||||
],
|
||||
|
||||
"background": {
|
||||
"scripts": ["background.js"],
|
||||
"persistent": false
|
||||
},
|
||||
|
||||
"browser_action": {
|
||||
"default_title": "Claude Download Helper",
|
||||
"default_icon": {
|
||||
"16": "icons/icon-16.png",
|
||||
"32": "icons/icon-32.png",
|
||||
"48": "icons/icon-48.png",
|
||||
"96": "icons/icon-96.png"
|
||||
}
|
||||
},
|
||||
|
||||
"icons": {
|
||||
"16": "icons/icon-16.png",
|
||||
"32": "icons/icon-32.png",
|
||||
"48": "icons/icon-48.png",
|
||||
"96": "icons/icon-96.png"
|
||||
}
|
||||
}
|
||||