Files
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

346 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// modal-helpers.js
// Show component modal
function showComponentModal(component) {
const modalHTML = createComponentModalHTML(component);
// Remove existing modal if present
const existingModal = document.querySelector('.modal-overlay');
if (existingModal) {
existingModal.remove();
}
// Add modal to body
document.body.insertAdjacentHTML('beforeend', modalHTML);
// Render code preview
renderCodePreview(component);
// Add event listener for ESC key
const handleEscape = (e) => {
if (e.key === 'Escape') {
closeComponentModal();
document.removeEventListener('keydown', handleEscape);
}
};
document.addEventListener('keydown', handleEscape);
}
// Create component modal HTML
function createComponentModalHTML(component) {
const typeConfig = {
agent: { icon: '🤖', color: '#ff6b6b', badge: 'AGENT' },
command: { icon: '⚡', color: '#4ecdc4', badge: 'COMMAND' },
mcp: { icon: '🔌', color: '#45b7d1', badge: 'MCP' },
template: { icon: '📦', color: '#f9a825', badge: 'TEMPLATE' }
};
const config = typeConfig[component.type] || typeConfig['template'];
// Generate install command - remove .md extension from path
let componentPath = component.path || component.name;
if (componentPath.endsWith('.md')) {
componentPath = componentPath.replace(/\.md$/, '');
}
if (componentPath.endsWith('.json')) {
componentPath = componentPath.replace(/\.json$/, '');
}
const installCommand = `npx claude-code-templates@latest --${component.type}=${componentPath} --yes`;
// Generate global agent command for agents only
const globalAgentCommand = component.type === 'agent' ? `npx claude-code-templates@latest --create-agent ${componentPath}` : null;
const description = getComponentDescription(component); // Full description
// Construct GitHub URL
let githubUrl = 'https://github.com/davila7/claude-code-templates/';
if (component.type === 'template') {
githubUrl += `tree/main/cli-tool/templates/${component.folderPath}`;
} else {
githubUrl += `blob/main/cli-tool/components/${component.type}s/${component.path}`;
}
return `
<div class="modal-overlay" onclick="closeComponentModal()">
<div class="modal-content" onclick="event.stopPropagation()">
<div class="modal-header">
<div class="component-modal-title">
<span class="component-icon">${config.icon}</span>
<h3 style="color: var(--text-primary);">${formatComponentName(component.name)}</h3>
<div class="component-type-badge" style="background-color: ${config.color};">${config.badge}</div>
</div>
<button class="modal-close" onclick="closeComponentModal()">×</button>
</div>
<div class="modal-body">
<div class="component-details">
<div class="component-description">${component.type === 'mcp' ? (component.description || description) : description}</div>
<div class="installation-section">
<!-- Basic Installation -->
<div class="basic-installation-section">
<h4>📦 Basic Installation</h4>
<p class="installation-description">Install this ${component.type} locally in your project. Works with your existing Claude Code setup.</p>
<div class="command-line">
<code>${installCommand}</code>
<button class="copy-btn" data-command="${installCommand.replace(/"/g, '&quot;')}" onclick="copyToClipboard(this.dataset.command)">Copy</button>
</div>
</div>
${globalAgentCommand ? `
<!-- Global Agent (Claude Code SDK) -->
<div class="global-agent-section">
<h4>🌍 Global Agent (Claude Code SDK)</h4>
<p class="global-agent-description">Create a global AI agent accessible from anywhere with zero configuration. Perfect for automation and CI/CD workflows.</p>
<div class="command-line">
<code>${globalAgentCommand}</code>
<button class="copy-btn" data-command="${globalAgentCommand.replace(/"/g, '&quot;')}" onclick="copyToClipboard(this.dataset.command)">Copy</button>
</div>
<div class="global-agent-usage">
<div class="usage-example">
<div class="usage-title">After installation, use from anywhere:</div>
<div class="command-line usage-command">
<code>${componentPath.split('/').pop()} "your prompt here"</code>
<button class="copy-btn" data-command="${componentPath.split('/').pop()} &quot;your prompt here&quot;" onclick="copyToClipboard(this.dataset.command)">Copy</button>
</div>
</div>
<div class="global-features">
<div class="feature">✅ Works in scripts, CI/CD, npm tasks</div>
<div class="feature">✅ Auto-detects project context</div>
<div class="feature">✅ Powered by Claude Code SDK</div>
</div>
</div>
</div>` : ''}
${component.type === 'agent' ? `
<!-- Run in E2B Sandbox (Cloud Execution) -->
<div class="e2b-sandbox-section">
<h4>☁️ Run in E2B Sandbox (Cloud Execution) <span class="new-badge">NEW</span></h4>
<p class="e2b-description">Execute Claude Code with this ${component.type} in an isolated cloud environment using E2B. Perfect for testing complex projects without affecting your local system.</p>
<div class="e2b-api-setup">
<h5>🔑 Setup API Keys</h5>
<div class="env-setup-simple">
<div class="env-comment">Add to your .env file:</div>
<div class="env-example">
<code>E2B_API_KEY=your_e2b_key_here</code>
<code>ANTHROPIC_API_KEY=your_anthropic_key_here</code>
</div>
</div>
<div class="command-line">
<code>npx claude-code-templates@latest --sandbox e2b --agent=${componentPath} --prompt "your development task"</code>
<button class="copy-btn" data-command="npx claude-code-templates@latest --sandbox e2b --agent=${componentPath} --prompt &quot;your development task&quot;" onclick="copyToClipboard(this.dataset.command)">Copy</button>
</div>
</div>
<div class="e2b-features">
<div class="feature">🔒 Isolated cloud environment</div>
<div class="feature">⚡ Extended timeouts for complex operations</div>
<div class="feature">📁 Files downloaded to project root</div>
<div class="feature">🔍 Real-time execution monitoring</div>
</div>
<div class="e2b-requirements">
<div class="requirements-title">Get API Keys:</div>
<div class="api-key-links">
<a href="https://e2b.dev/dashboard" target="_blank" class="api-key-link">
<span>🔑</span> E2B API Key
</a>
<a href="https://console.anthropic.com" target="_blank" class="api-key-link">
<span>🔑</span> Anthropic API Key
</a>
</div>
</div>
</div>` : ''}
</div>
<div class="component-content">
<h4>📋 Component Details</h4>
<div class="code-editor" id="code-editor">
<div class="code-line-numbers" id="line-numbers"></div>
<div class="code-content">
<pre id="code-viewer"><code>Loading content...</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="modal-actions">
<button class="add-to-cart-btn"
data-type="${component.type}s"
data-path="${componentPath}"
onclick="handleAddToCart('${component.name.replace(/'/g, "\\'")}', '${componentPath.replace(/'/g, "\\'")}', '${component.type}s', '${component.category ? component.category.replace(/'/g, "\\'") : 'Other'}', this); closeComponentModal();">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M19,7H18V6A2,2 0 0,0 16,4H8A2,2 0 0,0 6,6V7H5A1,1 0 0,0 4,8A1,1 0 0,0 5,9H6V19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V9H19A1,1 0 0,0 20,8A1,1 0 0,0 19,7M8,6H16V7H8V6M16,19H8V9H16V19Z"/>
</svg>
Add to Stack
</button>
<a href="${githubUrl}" target="_blank" class="github-folder-link">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.30.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
View on GitHub
</a>
</div>
</div>
</div>
`;
}
// Render code preview in the modal
function renderCodePreview(component) {
const viewer = document.getElementById('code-viewer');
const lineNumbers = document.getElementById('line-numbers');
if (!viewer) return;
let content = component.content || "No content available.";
let language = 'plaintext';
if (component.path) {
const extension = component.path.split('.').pop();
switch (extension) {
case 'md':
language = 'markdown';
break;
case 'json':
language = 'json';
// Pretty print JSON
try {
content = JSON.stringify(JSON.parse(content), null, 2);
} catch (e) { /* Ignore parsing errors */ }
break;
case 'js':
language = 'javascript';
break;
case 'yml':
case 'yaml':
language = 'yaml';
break;
}
}
// Show only a preview (first 15 lines) instead of full content
const lines = content.split('\n');
const previewLines = lines.slice(0, 15);
const previewContent = previewLines.join('\n');
// Add truncation indicator if content is longer
const truncatedContent = lines.length > 15 ? previewContent + '\n...' : previewContent;
const codeElement = viewer.querySelector('code');
codeElement.innerHTML = highlightCode(truncatedContent, language);
codeElement.className = `language-${language}`;
// Generate line numbers for preview
if (lineNumbers) {
const previewLineNumbers = previewLines.map((_, index) => `<span>${index + 1}</span>`).join('');
lineNumbers.innerHTML = previewLineNumbers;
}
}
// Basic syntax highlighting
function highlightCode(content, language) {
// First escape HTML entities
let highlighted = content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
if (language === 'markdown' || language === 'yaml') {
// Highlight YAML/Markdown frontmatter keys (blue)
highlighted = highlighted.replace(/^([a-zA-Z_-]+):/gm, '<span style="color: #569cd6;">$1</span>:');
// Highlight strings in quotes (orange)
highlighted = highlighted.replace(/&quot;([^&]+?)&quot;/g, '<span style="color: #ce9178;">&quot;$1&quot;</span>');
// Highlight important keywords (light blue - like in the image)
highlighted = highlighted.replace(/\b(hackathon|strategy|AI|solution|ideation|evaluation|projects|feedback|concepts|feasibility|guidance|agent|specialist|brainstorming|winning|judge|feedback|Context|User|Examples)\b/gi, '<span style="color: #4fc1ff;">$1</span>');
// Highlight markdown headers (blue)
highlighted = highlighted.replace(/^(#+)\s*(.+)$/gm, '<span style="color: #569cd6;">$1</span> <span style="color: #dcdcaa;">$2</span>');
// Highlight code in backticks
highlighted = highlighted.replace(/`([^`]+)`/g, '<span style="color: #ce9178;">$1</span>');
// Highlight YAML separators
highlighted = highlighted.replace(/^---$/gm, '<span style="color: #808080;">---</span>');
}
return highlighted;
}
// Close component modal
function closeComponentModal() {
const modalOverlay = document.querySelector('.modal-overlay');
if (modalOverlay) {
modalOverlay.remove();
}
}
// Utility to get component description
function getComponentDescription(component, maxLength = 0) {
let description = component.description || '';
if (!description && component.content) {
const descMatch = component.content.match(/description:\s*(.+?)(?:\n|$)/);
if (descMatch) {
description = descMatch[1].trim().replace(/^["']|["']$/g, '');
} else {
const lines = component.content.split('\n');
const firstParagraph = lines.find(line => line.trim() && !line.startsWith('---') && !line.startsWith('#'));
if (firstParagraph) {
description = firstParagraph.trim();
}
}
}
if (!description) {
description = `A ${component.type} component.`;
}
if (maxLength && description.length > maxLength) {
description = description.substring(0, maxLength - 3) + '...';
}
return description;
}
// Utility to format component name
function formatComponentName(name) {
return name.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}
// Global function for component details (called from onclick)
function showComponentDetails(type, name, path, category) {
// Instead of showing modal, redirect to component page
const componentURL = createComponentURL(type, name, path);
window.location.href = componentURL;
}
// Function to create component URL
function createComponentURL(type, name, path) {
// Detect if we're in local development
const isLocal = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1' ||
window.location.hostname.includes('5500');
if (!isLocal && type && name) {
// Use SEO-friendly URL structure for production: /component/type/name
let cleanName = name;
if (cleanName.endsWith('.md')) {
cleanName = cleanName.slice(0, -3);
}
if (cleanName.endsWith('.json')) {
cleanName = cleanName.slice(0, -5);
}
return `component/${encodeURIComponent(type)}/${encodeURIComponent(cleanName)}`;
}
// Use query parameters for local development or fallback
const params = new URLSearchParams();
params.set('type', type);
if (name) params.set('name', name);
if (path) params.set('path', path);
return `component.html?${params.toString()}`;
}