fix(marketplace): Update URLs to use /marketplace path and relative API endpoints
- Change API_BASE to relative '/api' for production - Move marketplace to /marketplace instead of /marketplace/frontend - Update MkDocs navigation - Fix logo path in marketplace index
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Admin Dashboard - Smart & Powerful
|
||||
const API_BASE = 'http://localhost:8100/api';
|
||||
const API_BASE = '/api';
|
||||
|
||||
class AdminDashboard {
|
||||
constructor() {
|
||||
|
||||
462
docs/md_v2/marketplace/app-detail.css
Normal file
462
docs/md_v2/marketplace/app-detail.css
Normal file
@@ -0,0 +1,462 @@
|
||||
/* App Detail Page Styles */
|
||||
|
||||
.app-detail-container {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-dark);
|
||||
}
|
||||
|
||||
/* Back Button */
|
||||
.header-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--primary-cyan);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
background: rgba(80, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* App Hero Section */
|
||||
.app-hero {
|
||||
max-width: 1800px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.app-hero-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 3rem;
|
||||
background: linear-gradient(135deg, #1a1a2e, #0f0f1e);
|
||||
border: 2px solid var(--primary-cyan);
|
||||
padding: 2rem;
|
||||
box-shadow: 0 0 30px rgba(80, 255, 255, 0.15),
|
||||
inset 0 0 20px rgba(80, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.app-hero-image {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: linear-gradient(135deg, rgba(80, 255, 255, 0.1), rgba(243, 128, 245, 0.05));
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 4rem;
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.app-badges {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.app-badge {
|
||||
padding: 0.3rem 0.6rem;
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.app-badge.featured {
|
||||
background: linear-gradient(135deg, var(--primary-cyan), var(--primary-teal));
|
||||
color: var(--bg-dark);
|
||||
box-shadow: 0 2px 10px rgba(80, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.app-badge.sponsored {
|
||||
background: linear-gradient(135deg, var(--warning), #ff8c00);
|
||||
color: var(--bg-dark);
|
||||
box-shadow: 0 2px 10px rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
.app-hero-info h1 {
|
||||
font-size: 2.5rem;
|
||||
color: var(--primary-cyan);
|
||||
margin: 0.5rem 0;
|
||||
text-shadow: 0 0 20px rgba(80, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.app-tagline {
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.app-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin: 2rem 0;
|
||||
padding: 1rem 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary-cyan);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.app-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: linear-gradient(135deg, var(--primary-cyan), var(--primary-teal));
|
||||
color: var(--bg-dark);
|
||||
border-color: var(--primary-cyan);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.action-btn.primary:hover {
|
||||
box-shadow: 0 4px 15px rgba(80, 255, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
border-color: var(--accent-pink);
|
||||
color: var(--accent-pink);
|
||||
}
|
||||
|
||||
.action-btn.secondary:hover {
|
||||
background: rgba(243, 128, 245, 0.1);
|
||||
box-shadow: 0 4px 15px rgba(243, 128, 245, 0.2);
|
||||
}
|
||||
|
||||
.action-btn.ghost {
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.action-btn.ghost:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
/* Pricing */
|
||||
.pricing-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.pricing-label {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.pricing-value {
|
||||
color: var(--warning);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Navigation Tabs */
|
||||
.app-nav {
|
||||
max-width: 1800px;
|
||||
margin: 2rem auto 0;
|
||||
padding: 0 2rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
padding: 1rem 1.5rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.nav-tab:hover {
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.nav-tab.active {
|
||||
color: var(--primary-cyan);
|
||||
border-bottom-color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
/* Content Sections */
|
||||
.app-content {
|
||||
max-width: 1800px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.docs-content {
|
||||
max-width: 1200px;
|
||||
padding: 2rem;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.docs-content h2 {
|
||||
font-size: 1.8rem;
|
||||
color: var(--primary-cyan);
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.docs-content h3 {
|
||||
font-size: 1.3rem;
|
||||
color: var(--text-primary);
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.docs-content h4 {
|
||||
font-size: 1.1rem;
|
||||
color: var(--accent-pink);
|
||||
margin: 1.5rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.docs-content p {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.docs-content code {
|
||||
background: var(--bg-tertiary);
|
||||
padding: 0.2rem 0.4rem;
|
||||
color: var(--primary-cyan);
|
||||
font-family: 'Dank Mono', Monaco, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Code Blocks */
|
||||
.code-block {
|
||||
background: var(--bg-dark);
|
||||
border: 1px solid var(--border-color);
|
||||
margin: 1rem 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.code-lang {
|
||||
color: var(--primary-cyan);
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.code-block pre {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-block code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Feature Grid */
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 1.5rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
background: rgba(80, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.feature-card h4 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Info Box */
|
||||
.info-box {
|
||||
background: linear-gradient(135deg, rgba(80, 255, 255, 0.05), rgba(243, 128, 245, 0.03));
|
||||
border: 1px solid var(--primary-cyan);
|
||||
border-left: 4px solid var(--primary-cyan);
|
||||
padding: 1.5rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.info-box h4 {
|
||||
margin-top: 0;
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
/* Support Grid */
|
||||
.support-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.support-card {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.support-card h3 {
|
||||
color: var(--primary-cyan);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Related Apps */
|
||||
.related-apps {
|
||||
max-width: 1800px;
|
||||
margin: 4rem auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.related-apps h2 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.related-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.related-app-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.related-app-card:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.app-hero-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.app-stats {
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-hero-info h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.app-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app-nav {
|
||||
overflow-x: auto;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.feature-grid,
|
||||
.support-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
324
docs/md_v2/marketplace/app-detail.js
Normal file
324
docs/md_v2/marketplace/app-detail.js
Normal file
@@ -0,0 +1,324 @@
|
||||
// App Detail Page JavaScript
|
||||
const API_BASE = '/api';
|
||||
|
||||
class AppDetailPage {
|
||||
constructor() {
|
||||
this.appSlug = this.getAppSlugFromURL();
|
||||
this.appData = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
getAppSlugFromURL() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.get('app') || '';
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.appSlug) {
|
||||
window.location.href = 'index.html';
|
||||
return;
|
||||
}
|
||||
|
||||
await this.loadAppDetails();
|
||||
this.setupEventListeners();
|
||||
await this.loadRelatedApps();
|
||||
}
|
||||
|
||||
async loadAppDetails() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/apps/${this.appSlug}`);
|
||||
if (!response.ok) throw new Error('App not found');
|
||||
|
||||
this.appData = await response.json();
|
||||
this.renderAppDetails();
|
||||
} catch (error) {
|
||||
console.error('Error loading app details:', error);
|
||||
// Fallback to loading all apps and finding the right one
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/apps`);
|
||||
const apps = await response.json();
|
||||
this.appData = apps.find(app => app.slug === this.appSlug || app.name.toLowerCase().replace(/\s+/g, '-') === this.appSlug);
|
||||
if (this.appData) {
|
||||
this.renderAppDetails();
|
||||
} else {
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading apps:', err);
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderAppDetails() {
|
||||
if (!this.appData) return;
|
||||
|
||||
// Update title
|
||||
document.title = `${this.appData.name} - Crawl4AI Marketplace`;
|
||||
|
||||
// Hero image
|
||||
const appImage = document.getElementById('app-image');
|
||||
if (this.appData.image) {
|
||||
appImage.style.backgroundImage = `url('${this.appData.image}')`;
|
||||
appImage.innerHTML = '';
|
||||
} else {
|
||||
appImage.innerHTML = `[${this.appData.category || 'APP'}]`;
|
||||
}
|
||||
|
||||
// Basic info
|
||||
document.getElementById('app-name').textContent = this.appData.name;
|
||||
document.getElementById('app-description').textContent = this.appData.description;
|
||||
document.getElementById('app-type').textContent = this.appData.type || 'Open Source';
|
||||
document.getElementById('app-category').textContent = this.appData.category;
|
||||
document.getElementById('app-pricing').textContent = this.appData.pricing || 'Free';
|
||||
|
||||
// Badges
|
||||
if (this.appData.featured) {
|
||||
document.getElementById('app-featured').style.display = 'inline-block';
|
||||
}
|
||||
if (this.appData.sponsored) {
|
||||
document.getElementById('app-sponsored').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
// Stats
|
||||
const rating = this.appData.rating || 0;
|
||||
const stars = '★'.repeat(Math.floor(rating)) + '☆'.repeat(5 - Math.floor(rating));
|
||||
document.getElementById('app-rating').textContent = stars + ` ${rating}/5`;
|
||||
document.getElementById('app-downloads').textContent = this.formatNumber(this.appData.downloads || 0);
|
||||
|
||||
// Action buttons
|
||||
const websiteBtn = document.getElementById('app-website');
|
||||
const githubBtn = document.getElementById('app-github');
|
||||
|
||||
if (this.appData.website_url) {
|
||||
websiteBtn.href = this.appData.website_url;
|
||||
} else {
|
||||
websiteBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
if (this.appData.github_url) {
|
||||
githubBtn.href = this.appData.github_url;
|
||||
} else {
|
||||
githubBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// Contact
|
||||
document.getElementById('app-contact').textContent = this.appData.contact_email || 'Not available';
|
||||
|
||||
// Integration guide
|
||||
this.renderIntegrationGuide();
|
||||
}
|
||||
|
||||
renderIntegrationGuide() {
|
||||
// Installation code
|
||||
const installCode = document.getElementById('install-code');
|
||||
if (this.appData.type === 'Open Source' && this.appData.github_url) {
|
||||
installCode.textContent = `# Clone from GitHub
|
||||
git clone ${this.appData.github_url}
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt`;
|
||||
} else if (this.appData.name.toLowerCase().includes('api')) {
|
||||
installCode.textContent = `# Install via pip
|
||||
pip install ${this.appData.slug}
|
||||
|
||||
# Or install from source
|
||||
pip install git+${this.appData.github_url || 'https://github.com/example/repo'}`;
|
||||
}
|
||||
|
||||
// Usage code - customize based on category
|
||||
const usageCode = document.getElementById('usage-code');
|
||||
if (this.appData.category === 'Browser Automation') {
|
||||
usageCode.textContent = `from crawl4ai import AsyncWebCrawler
|
||||
from ${this.appData.slug.replace(/-/g, '_')} import ${this.appData.name.replace(/\s+/g, '')}
|
||||
|
||||
async def main():
|
||||
# Initialize ${this.appData.name}
|
||||
automation = ${this.appData.name.replace(/\s+/g, '')}()
|
||||
|
||||
async with AsyncWebCrawler() as crawler:
|
||||
result = await crawler.arun(
|
||||
url="https://example.com",
|
||||
browser_config=automation.config,
|
||||
wait_for="css:body"
|
||||
)
|
||||
print(result.markdown)`;
|
||||
} else if (this.appData.category === 'Proxy Services') {
|
||||
usageCode.textContent = `from crawl4ai import AsyncWebCrawler
|
||||
import ${this.appData.slug.replace(/-/g, '_')}
|
||||
|
||||
# Configure proxy
|
||||
proxy_config = {
|
||||
"server": "${this.appData.website_url || 'https://proxy.example.com'}",
|
||||
"username": "your_username",
|
||||
"password": "your_password"
|
||||
}
|
||||
|
||||
async with AsyncWebCrawler(proxy=proxy_config) as crawler:
|
||||
result = await crawler.arun(
|
||||
url="https://example.com",
|
||||
bypass_cache=True
|
||||
)
|
||||
print(result.status_code)`;
|
||||
} else if (this.appData.category === 'LLM Integration') {
|
||||
usageCode.textContent = `from crawl4ai import AsyncWebCrawler
|
||||
from crawl4ai.extraction_strategy import LLMExtractionStrategy
|
||||
|
||||
# Configure LLM extraction
|
||||
strategy = LLMExtractionStrategy(
|
||||
provider="${this.appData.name.toLowerCase().includes('gpt') ? 'openai' : 'anthropic'}",
|
||||
api_key="your-api-key",
|
||||
model="${this.appData.name.toLowerCase().includes('gpt') ? 'gpt-4' : 'claude-3'}",
|
||||
instruction="Extract structured data"
|
||||
)
|
||||
|
||||
async with AsyncWebCrawler() as crawler:
|
||||
result = await crawler.arun(
|
||||
url="https://example.com",
|
||||
extraction_strategy=strategy
|
||||
)
|
||||
print(result.extracted_content)`;
|
||||
}
|
||||
|
||||
// Integration example
|
||||
const integrationCode = document.getElementById('integration-code');
|
||||
integrationCode.textContent = this.appData.integration_guide ||
|
||||
`# Complete ${this.appData.name} Integration Example
|
||||
|
||||
from crawl4ai import AsyncWebCrawler
|
||||
from crawl4ai.extraction_strategy import JsonCssExtractionStrategy
|
||||
import json
|
||||
|
||||
async def crawl_with_${this.appData.slug.replace(/-/g, '_')}():
|
||||
"""
|
||||
Complete example showing how to use ${this.appData.name}
|
||||
with Crawl4AI for production web scraping
|
||||
"""
|
||||
|
||||
# Define extraction schema
|
||||
schema = {
|
||||
"name": "ProductList",
|
||||
"baseSelector": "div.product",
|
||||
"fields": [
|
||||
{"name": "title", "selector": "h2", "type": "text"},
|
||||
{"name": "price", "selector": ".price", "type": "text"},
|
||||
{"name": "image", "selector": "img", "type": "attribute", "attribute": "src"},
|
||||
{"name": "link", "selector": "a", "type": "attribute", "attribute": "href"}
|
||||
]
|
||||
}
|
||||
|
||||
# Initialize crawler with ${this.appData.name}
|
||||
async with AsyncWebCrawler(
|
||||
browser_type="chromium",
|
||||
headless=True,
|
||||
verbose=True
|
||||
) as crawler:
|
||||
|
||||
# Crawl with extraction
|
||||
result = await crawler.arun(
|
||||
url="https://example.com/products",
|
||||
extraction_strategy=JsonCssExtractionStrategy(schema),
|
||||
cache_mode="bypass",
|
||||
wait_for="css:.product",
|
||||
screenshot=True
|
||||
)
|
||||
|
||||
# Process results
|
||||
if result.success:
|
||||
products = json.loads(result.extracted_content)
|
||||
print(f"Found {len(products)} products")
|
||||
|
||||
for product in products[:5]:
|
||||
print(f"- {product['title']}: {product['price']}")
|
||||
|
||||
return products
|
||||
|
||||
# Run the crawler
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
asyncio.run(crawl_with_${this.appData.slug.replace(/-/g, '_')}())`;
|
||||
}
|
||||
|
||||
formatNumber(num) {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Tab switching
|
||||
const tabs = document.querySelectorAll('.nav-tab');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
// Update active tab
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
// Show corresponding content
|
||||
const tabName = tab.dataset.tab;
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
document.getElementById(`${tabName}-tab`).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Copy integration code
|
||||
document.getElementById('copy-integration').addEventListener('click', () => {
|
||||
const code = document.getElementById('integration-code').textContent;
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
const btn = document.getElementById('copy-integration');
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = '<span>✓</span> Copied!';
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = originalText;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
// Copy code buttons
|
||||
document.querySelectorAll('.copy-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const codeBlock = e.target.closest('.code-block');
|
||||
const code = codeBlock.querySelector('code').textContent;
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(() => {
|
||||
btn.textContent = 'Copy';
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async loadRelatedApps() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/apps?category=${encodeURIComponent(this.appData.category)}&limit=4`);
|
||||
const apps = await response.json();
|
||||
|
||||
const relatedApps = apps.filter(app => app.slug !== this.appSlug).slice(0, 3);
|
||||
const grid = document.getElementById('related-apps-grid');
|
||||
|
||||
grid.innerHTML = relatedApps.map(app => `
|
||||
<div class="related-app-card" onclick="window.location.href='app-detail.html?app=${app.slug || app.name.toLowerCase().replace(/\s+/g, '-')}'">
|
||||
<h4>${app.name}</h4>
|
||||
<p>${app.description.substring(0, 100)}...</p>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: 0.5rem; font-size: 0.75rem;">
|
||||
<span style="color: var(--primary-cyan)">${app.type}</span>
|
||||
<span style="color: var(--warning)">★ ${app.rating}/5</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch (error) {
|
||||
console.error('Error loading related apps:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new AppDetailPage();
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
// App Detail Page JavaScript
|
||||
const API_BASE = 'http://localhost:8100/api';
|
||||
const API_BASE = '/api';
|
||||
|
||||
class AppDetailPage {
|
||||
constructor() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Marketplace JS - Magazine Layout
|
||||
const API_BASE = 'http://localhost:8100/api';
|
||||
const API_BASE = '/api';
|
||||
const CACHE_TTL = 3600000; // 1 hour in ms
|
||||
|
||||
class MarketplaceCache {
|
||||
|
||||
147
docs/md_v2/marketplace/index.html
Normal file
147
docs/md_v2/marketplace/index.html
Normal file
@@ -0,0 +1,147 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Marketplace - Crawl4AI</title>
|
||||
<link rel="stylesheet" href="marketplace.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="marketplace-container">
|
||||
<!-- Header -->
|
||||
<header class="marketplace-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="logo-title">
|
||||
<img src="../assets/images/logo.png" alt="Crawl4AI" class="header-logo">
|
||||
<h1>
|
||||
<span class="ascii-border">[</span>
|
||||
Marketplace
|
||||
<span class="ascii-border">]</span>
|
||||
</h1>
|
||||
</div>
|
||||
<p class="tagline">Tools, Integrations & Resources for Web Crawling</p>
|
||||
</div>
|
||||
<div class="header-stats" id="stats">
|
||||
<span class="stat-item">Apps: <span id="total-apps">--</span></span>
|
||||
<span class="stat-item">Articles: <span id="total-articles">--</span></span>
|
||||
<span class="stat-item">Downloads: <span id="total-downloads">--</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Search and Category Bar -->
|
||||
<div class="search-filter-bar">
|
||||
<div class="search-box">
|
||||
<span class="search-icon">></span>
|
||||
<input type="text" id="search-input" placeholder="Search apps, articles, tools..." />
|
||||
<kbd>/</kbd>
|
||||
</div>
|
||||
<div class="category-filter" id="category-filter">
|
||||
<button class="filter-btn active" data-category="all">All</button>
|
||||
<!-- Categories will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Magazine Grid Layout -->
|
||||
<main class="magazine-layout">
|
||||
<!-- Hero Featured Section -->
|
||||
<section class="hero-featured">
|
||||
<div id="featured-hero" class="featured-hero-card">
|
||||
<!-- Large featured card with big image -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Secondary Featured -->
|
||||
<section class="secondary-featured">
|
||||
<div id="featured-secondary" class="featured-secondary-cards">
|
||||
<!-- 2-3 medium featured cards with images -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Sponsored Section -->
|
||||
<section class="sponsored-section">
|
||||
<div class="section-label">SPONSORED</div>
|
||||
<div id="sponsored-content" class="sponsored-cards">
|
||||
<!-- Sponsored content cards -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Main Content Grid -->
|
||||
<section class="main-content">
|
||||
<!-- Apps Column -->
|
||||
<div class="apps-column">
|
||||
<div class="column-header">
|
||||
<h2><span class="ascii-icon">></span> Latest Apps</h2>
|
||||
<select id="type-filter" class="mini-filter">
|
||||
<option value="">All</option>
|
||||
<option value="Open Source">Open Source</option>
|
||||
<option value="Paid">Paid</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="apps-grid" class="apps-compact-grid">
|
||||
<!-- Compact app cards -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Articles Column -->
|
||||
<div class="articles-column">
|
||||
<div class="column-header">
|
||||
<h2><span class="ascii-icon">></span> Latest Articles</h2>
|
||||
</div>
|
||||
<div id="articles-list" class="articles-compact-list">
|
||||
<!-- Article items -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trending/Tools Column -->
|
||||
<div class="trending-column">
|
||||
<div class="column-header">
|
||||
<h2><span class="ascii-icon">#</span> Trending</h2>
|
||||
</div>
|
||||
<div id="trending-list" class="trending-items">
|
||||
<!-- Trending items -->
|
||||
</div>
|
||||
|
||||
<div class="submit-box">
|
||||
<h3><span class="ascii-icon">+</span> Submit Your Tool</h3>
|
||||
<p>Share your integration</p>
|
||||
<a href="mailto:marketplace@crawl4ai.com" class="submit-btn">Submit →</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- More Apps Grid -->
|
||||
<section class="more-apps">
|
||||
<div class="section-header">
|
||||
<h2><span class="ascii-icon">></span> More Apps</h2>
|
||||
<button id="load-more" class="load-more-btn">Load More ↓</button>
|
||||
</div>
|
||||
<div id="more-apps-grid" class="more-apps-grid">
|
||||
<!-- Additional app cards -->
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="marketplace-footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h3>About Marketplace</h3>
|
||||
<p>Discover tools and integrations built by the Crawl4AI community.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h3>Become a Sponsor</h3>
|
||||
<p>Reach developers building with Crawl4AI</p>
|
||||
<a href="mailto:sponsors@crawl4ai.com" class="sponsor-btn">Learn More →</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>[ Crawl4AI Marketplace · Updated <span id="last-update">--</span> ]</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="marketplace.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
957
docs/md_v2/marketplace/marketplace.css
Normal file
957
docs/md_v2/marketplace/marketplace.css
Normal file
@@ -0,0 +1,957 @@
|
||||
/* Marketplace CSS - Magazine Style Terminal Theme */
|
||||
@import url('../../assets/styles.css');
|
||||
|
||||
:root {
|
||||
--primary-cyan: #50ffff;
|
||||
--primary-teal: #09b5a5;
|
||||
--accent-pink: #f380f5;
|
||||
--bg-dark: #070708;
|
||||
--bg-secondary: #1a1a1a;
|
||||
--bg-tertiary: #3f3f44;
|
||||
--text-primary: #e8e9ed;
|
||||
--text-secondary: #d5cec0;
|
||||
--text-tertiary: #a3abba;
|
||||
--border-color: #3f3f44;
|
||||
--success: #50ff50;
|
||||
--error: #ff3c74;
|
||||
--warning: #f59e0b;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Dank Mono', Monaco, monospace;
|
||||
background: var(--bg-dark);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Global link styles */
|
||||
a {
|
||||
color: var(--primary-cyan);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-pink);
|
||||
}
|
||||
|
||||
.marketplace-container {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.marketplace-header {
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
height: 40px;
|
||||
width: auto;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.marketplace-header h1 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary-cyan);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ascii-border {
|
||||
color: var(--border-color);
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-tertiary);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.header-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.stat-item span {
|
||||
color: var(--primary-cyan);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Search and Filter Bar */
|
||||
.search-filter-bar {
|
||||
max-width: 1800px;
|
||||
margin: 1.5rem auto;
|
||||
padding: 0 2rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.75rem 1rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.search-box:focus-within {
|
||||
border-color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
color: var(--text-tertiary);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
#search-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-box kbd {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
padding: 0.5rem 1rem;
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background: var(--primary-cyan);
|
||||
color: var(--bg-dark);
|
||||
border-color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
/* Magazine Layout */
|
||||
.magazine-layout {
|
||||
max-width: 1800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem 4rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Hero Featured Section */
|
||||
.hero-featured {
|
||||
grid-column: 1 / -1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-featured::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: -20px;
|
||||
right: -20px;
|
||||
bottom: -20px;
|
||||
background: radial-gradient(ellipse at center, rgba(80, 255, 255, 0.05), transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.featured-hero-card {
|
||||
background: linear-gradient(135deg, #1a1a2e, #0f0f1e);
|
||||
border: 2px solid var(--primary-cyan);
|
||||
box-shadow: 0 0 30px rgba(80, 255, 255, 0.15),
|
||||
inset 0 0 20px rgba(80, 255, 255, 0.05);
|
||||
height: 380px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.featured-hero-card:hover {
|
||||
border-color: var(--accent-pink);
|
||||
box-shadow: 0 0 40px rgba(243, 128, 245, 0.2),
|
||||
inset 0 0 30px rgba(243, 128, 245, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
background: linear-gradient(135deg, rgba(80, 255, 255, 0.1), rgba(243, 128, 245, 0.05));
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 3rem;
|
||||
color: var(--primary-cyan);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
filter: brightness(1.1) contrast(1.1);
|
||||
}
|
||||
|
||||
.hero-image::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60%;
|
||||
background: linear-gradient(to top, rgba(10, 10, 20, 0.95), transparent);
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-block;
|
||||
padding: 0.3rem 0.6rem;
|
||||
background: linear-gradient(135deg, var(--primary-cyan), var(--primary-teal));
|
||||
color: var(--bg-dark);
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 10px rgba(80, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 1.6rem;
|
||||
color: var(--primary-cyan);
|
||||
margin: 0.5rem 0;
|
||||
text-shadow: 0 0 20px rgba(80, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.hero-description {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.hero-meta {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.hero-meta span {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.hero-meta span:first-child {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
/* Secondary Featured */
|
||||
.secondary-featured {
|
||||
grid-column: 1 / -1;
|
||||
height: 380px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.featured-secondary-cards {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.secondary-card {
|
||||
background: linear-gradient(135deg, rgba(80, 255, 255, 0.03), rgba(243, 128, 245, 0.02));
|
||||
border: 1px solid rgba(80, 255, 255, 0.3);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
height: calc((380px - 1.5rem) / 3);
|
||||
flex: 1;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.secondary-card:hover {
|
||||
border-color: var(--accent-pink);
|
||||
background: linear-gradient(135deg, rgba(243, 128, 245, 0.05), rgba(80, 255, 255, 0.03));
|
||||
box-shadow: 0 4px 15px rgba(243, 128, 245, 0.2);
|
||||
transform: translateX(-3px);
|
||||
}
|
||||
|
||||
.secondary-image {
|
||||
width: 120px;
|
||||
background: linear-gradient(135deg, var(--bg-tertiary), var(--bg-secondary));
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary-cyan);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.secondary-content {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.secondary-title {
|
||||
font-size: 1rem;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.secondary-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.secondary-meta {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.secondary-meta span:last-child {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
/* Sponsored Section */
|
||||
.sponsored-section {
|
||||
grid-column: 1 / -1;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--warning);
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
position: absolute;
|
||||
top: -0.5rem;
|
||||
left: 1rem;
|
||||
background: var(--bg-secondary);
|
||||
padding: 0 0.5rem;
|
||||
color: var(--warning);
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.sponsored-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sponsor-card {
|
||||
padding: 1rem;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.sponsor-card h4 {
|
||||
color: var(--accent-pink);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.sponsor-card p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.sponsor-card a {
|
||||
color: var(--primary-cyan);
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sponsor-card a:hover {
|
||||
color: var(--accent-pink);
|
||||
}
|
||||
|
||||
/* Main Content Grid */
|
||||
.main-content {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Column Headers */
|
||||
.column-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.column-header h2 {
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mini-filter {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.ascii-icon {
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
/* Apps Column */
|
||||
.apps-compact-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.app-compact {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 3px solid var(--border-color);
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.app-compact:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
border-left-color: var(--accent-pink);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.app-compact-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.app-compact-header span:first-child {
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.app-compact-header span:last-child {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.app-compact-title {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.app-compact-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Articles Column */
|
||||
.articles-compact-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.article-compact {
|
||||
border-left: 2px solid var(--border-color);
|
||||
padding-left: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.article-compact:hover {
|
||||
border-left-color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.article-meta span:first-child {
|
||||
color: var(--accent-pink);
|
||||
}
|
||||
|
||||
.article-title {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.article-author {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Trending Column */
|
||||
.trending-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.trending-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.trending-item:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.trending-rank {
|
||||
font-size: 1.2rem;
|
||||
color: var(--primary-cyan);
|
||||
width: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.trending-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.trending-name {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.trending-stats {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Submit Box */
|
||||
.submit-box {
|
||||
margin-top: 1.5rem;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--primary-cyan);
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit-box h3 {
|
||||
font-size: 1rem;
|
||||
color: var(--primary-cyan);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.submit-box p {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--primary-cyan);
|
||||
color: var(--primary-cyan);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background: var(--primary-cyan);
|
||||
color: var(--bg-dark);
|
||||
}
|
||||
|
||||
/* More Apps Section */
|
||||
.more-apps {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.more-apps-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.load-more-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
padding: 0.5rem 1.5rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.load-more-btn:hover {
|
||||
border-color: var(--primary-cyan);
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.marketplace-footer {
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-top: 4rem;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
max-width: 1800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.footer-section h3 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
.footer-section p {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.sponsor-btn {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--primary-cyan);
|
||||
color: var(--primary-cyan);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.sponsor-btn:hover {
|
||||
background: var(--primary-cyan);
|
||||
color: var(--bg-dark);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
max-width: 1800px;
|
||||
margin: 2rem auto 0;
|
||||
padding: 1rem 2rem 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--primary-cyan);
|
||||
max-width: 800px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
padding: 0.25rem 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
border-color: var(--error);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.app-detail {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.app-detail h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--primary-cyan);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Responsive - Tablet */
|
||||
@media (min-width: 768px) {
|
||||
.magazine-layout {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.hero-featured {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.secondary-featured {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.sponsored-section {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive - Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.magazine-layout {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.hero-featured {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.secondary-featured {
|
||||
grid-column: 3 / 4;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.featured-secondary-cards {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sponsored-section {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive - Wide Desktop */
|
||||
@media (min-width: 1400px) {
|
||||
.magazine-layout {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.hero-featured {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
.secondary-featured {
|
||||
grid-column: 3 / 5;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.featured-secondary-cards {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.apps-column {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.more-apps-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive - Ultra Wide Desktop (for coders with wide monitors) */
|
||||
@media (min-width: 1800px) {
|
||||
.magazine-layout {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
|
||||
.hero-featured {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
.secondary-featured {
|
||||
grid-column: 3 / 6;
|
||||
}
|
||||
|
||||
.featured-secondary-cards {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.sponsored-section {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.sponsored-cards {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
|
||||
.apps-column {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.articles-column {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.more-apps-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive - Mobile */
|
||||
@media (max-width: 767px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.search-filter-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.magazine-layout {
|
||||
padding: 0 1rem 2rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.secondary-card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.secondary-image {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
395
docs/md_v2/marketplace/marketplace.js
Normal file
395
docs/md_v2/marketplace/marketplace.js
Normal file
@@ -0,0 +1,395 @@
|
||||
// Marketplace JS - Magazine Layout
|
||||
const API_BASE = '/api';
|
||||
const CACHE_TTL = 3600000; // 1 hour in ms
|
||||
|
||||
class MarketplaceCache {
|
||||
constructor() {
|
||||
this.prefix = 'c4ai_market_';
|
||||
}
|
||||
|
||||
get(key) {
|
||||
const item = localStorage.getItem(this.prefix + key);
|
||||
if (!item) return null;
|
||||
|
||||
const data = JSON.parse(item);
|
||||
if (Date.now() > data.expires) {
|
||||
localStorage.removeItem(this.prefix + key);
|
||||
return null;
|
||||
}
|
||||
return data.value;
|
||||
}
|
||||
|
||||
set(key, value, ttl = CACHE_TTL) {
|
||||
const data = {
|
||||
value: value,
|
||||
expires: Date.now() + ttl
|
||||
};
|
||||
localStorage.setItem(this.prefix + key, JSON.stringify(data));
|
||||
}
|
||||
|
||||
clear() {
|
||||
Object.keys(localStorage)
|
||||
.filter(k => k.startsWith(this.prefix))
|
||||
.forEach(k => localStorage.removeItem(k));
|
||||
}
|
||||
}
|
||||
|
||||
class MarketplaceAPI {
|
||||
constructor() {
|
||||
this.cache = new MarketplaceCache();
|
||||
this.searchTimeout = null;
|
||||
}
|
||||
|
||||
async fetch(endpoint, useCache = true) {
|
||||
const cacheKey = endpoint.replace(/[^\w]/g, '_');
|
||||
|
||||
if (useCache) {
|
||||
const cached = this.cache.get(cacheKey);
|
||||
if (cached) return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}${endpoint}`);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
const data = await response.json();
|
||||
this.cache.set(cacheKey, data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getStats() {
|
||||
return this.fetch('/stats');
|
||||
}
|
||||
|
||||
async getCategories() {
|
||||
return this.fetch('/categories');
|
||||
}
|
||||
|
||||
async getApps(params = {}) {
|
||||
const query = new URLSearchParams(params).toString();
|
||||
return this.fetch(`/apps${query ? '?' + query : ''}`);
|
||||
}
|
||||
|
||||
async getArticles(params = {}) {
|
||||
const query = new URLSearchParams(params).toString();
|
||||
return this.fetch(`/articles${query ? '?' + query : ''}`);
|
||||
}
|
||||
|
||||
async getSponsors() {
|
||||
return this.fetch('/sponsors');
|
||||
}
|
||||
|
||||
async search(query) {
|
||||
if (query.length < 2) return {};
|
||||
return this.fetch(`/search?q=${encodeURIComponent(query)}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
class MarketplaceUI {
|
||||
constructor() {
|
||||
this.api = new MarketplaceAPI();
|
||||
this.currentCategory = 'all';
|
||||
this.currentType = '';
|
||||
this.searchTimeout = null;
|
||||
this.loadedApps = 10;
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.loadStats();
|
||||
await this.loadCategories();
|
||||
await this.loadFeaturedContent();
|
||||
await this.loadSponsors();
|
||||
await this.loadMainContent();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
async loadStats() {
|
||||
const stats = await this.api.getStats();
|
||||
if (stats) {
|
||||
document.getElementById('total-apps').textContent = stats.total_apps || '0';
|
||||
document.getElementById('total-articles').textContent = stats.total_articles || '0';
|
||||
document.getElementById('total-downloads').textContent = stats.total_downloads || '0';
|
||||
document.getElementById('last-update').textContent = new Date().toLocaleDateString();
|
||||
}
|
||||
}
|
||||
|
||||
async loadCategories() {
|
||||
const categories = await this.api.getCategories();
|
||||
if (!categories) return;
|
||||
|
||||
const filter = document.getElementById('category-filter');
|
||||
categories.forEach(cat => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'filter-btn';
|
||||
btn.dataset.category = cat.slug;
|
||||
btn.textContent = cat.name;
|
||||
btn.onclick = () => this.filterByCategory(cat.slug);
|
||||
filter.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
async loadFeaturedContent() {
|
||||
// Load hero featured
|
||||
const featured = await this.api.getApps({ featured: true, limit: 4 });
|
||||
if (!featured || !featured.length) return;
|
||||
|
||||
// Hero card (first featured)
|
||||
const hero = featured[0];
|
||||
const heroCard = document.getElementById('featured-hero');
|
||||
if (hero) {
|
||||
const imageUrl = hero.image || '';
|
||||
heroCard.innerHTML = `
|
||||
<div class="hero-image" ${imageUrl ? `style="background-image: url('${imageUrl}')"` : ''}>
|
||||
${!imageUrl ? `[${hero.category || 'APP'}]` : ''}
|
||||
</div>
|
||||
<div class="hero-content">
|
||||
<span class="hero-badge">${hero.type || 'PAID'}</span>
|
||||
<h2 class="hero-title">${hero.name}</h2>
|
||||
<p class="hero-description">${hero.description}</p>
|
||||
<div class="hero-meta">
|
||||
<span>★ ${hero.rating || 0}/5</span>
|
||||
<span>${hero.downloads || 0} downloads</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
heroCard.onclick = () => this.showAppDetail(hero);
|
||||
}
|
||||
|
||||
// Secondary featured cards
|
||||
const secondary = document.getElementById('featured-secondary');
|
||||
secondary.innerHTML = '';
|
||||
if (featured.length > 1) {
|
||||
featured.slice(1, 4).forEach(app => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'secondary-card';
|
||||
const imageUrl = app.image || '';
|
||||
card.innerHTML = `
|
||||
<div class="secondary-image" ${imageUrl ? `style="background-image: url('${imageUrl}')"` : ''}>
|
||||
${!imageUrl ? `[${app.category || 'APP'}]` : ''}
|
||||
</div>
|
||||
<div class="secondary-content">
|
||||
<h3 class="secondary-title">${app.name}</h3>
|
||||
<p class="secondary-desc">${(app.description || '').substring(0, 100)}...</p>
|
||||
<div class="secondary-meta">
|
||||
<span>${app.type || 'Open Source'}</span> · <span>★ ${app.rating || 0}/5</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
card.onclick = () => this.showAppDetail(app);
|
||||
secondary.appendChild(card);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async loadSponsors() {
|
||||
const sponsors = await this.api.getSponsors();
|
||||
if (!sponsors || !sponsors.length) {
|
||||
// Show placeholder if no sponsors
|
||||
const container = document.getElementById('sponsored-content');
|
||||
container.innerHTML = `
|
||||
<div class="sponsor-card">
|
||||
<h4>Become a Sponsor</h4>
|
||||
<p>Reach thousands of developers using Crawl4AI</p>
|
||||
<a href="mailto:sponsors@crawl4ai.com">Contact Us →</a>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const container = document.getElementById('sponsored-content');
|
||||
container.innerHTML = sponsors.slice(0, 5).map(sponsor => `
|
||||
<div class="sponsor-card">
|
||||
<h4>${sponsor.company_name}</h4>
|
||||
<p>${sponsor.tier} Sponsor - Premium Solutions</p>
|
||||
<a href="${sponsor.landing_url}" target="_blank">Learn More →</a>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async loadMainContent() {
|
||||
// Load apps column
|
||||
const apps = await this.api.getApps({ limit: 8 });
|
||||
if (apps && apps.length) {
|
||||
const appsGrid = document.getElementById('apps-grid');
|
||||
appsGrid.innerHTML = apps.map(app => `
|
||||
<div class="app-compact" onclick="marketplace.showAppDetail(${JSON.stringify(app).replace(/"/g, '"')})">
|
||||
<div class="app-compact-header">
|
||||
<span>${app.category}</span>
|
||||
<span>★ ${app.rating}/5</span>
|
||||
</div>
|
||||
<div class="app-compact-title">${app.name}</div>
|
||||
<div class="app-compact-desc">${app.description}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Load articles column
|
||||
const articles = await this.api.getArticles({ limit: 6 });
|
||||
if (articles && articles.length) {
|
||||
const articlesList = document.getElementById('articles-list');
|
||||
articlesList.innerHTML = articles.map(article => `
|
||||
<div class="article-compact" onclick="marketplace.showArticle('${article.id}')">
|
||||
<div class="article-meta">
|
||||
<span>${article.category}</span> · <span>${new Date(article.published_at).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div class="article-title">${article.title}</div>
|
||||
<div class="article-author">by ${article.author}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Load trending
|
||||
if (apps && apps.length) {
|
||||
const trending = apps.slice(0, 5);
|
||||
const trendingList = document.getElementById('trending-list');
|
||||
trendingList.innerHTML = trending.map((app, i) => `
|
||||
<div class="trending-item" onclick="marketplace.showAppDetail(${JSON.stringify(app).replace(/"/g, '"')})">
|
||||
<div class="trending-rank">${i + 1}</div>
|
||||
<div class="trending-info">
|
||||
<div class="trending-name">${app.name}</div>
|
||||
<div class="trending-stats">${app.downloads} downloads</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Load more apps grid
|
||||
const moreApps = await this.api.getApps({ offset: 8, limit: 12 });
|
||||
if (moreApps && moreApps.length) {
|
||||
const moreGrid = document.getElementById('more-apps-grid');
|
||||
moreGrid.innerHTML = moreApps.map(app => `
|
||||
<div class="app-compact" onclick="marketplace.showAppDetail(${JSON.stringify(app).replace(/"/g, '"')})">
|
||||
<div class="app-compact-header">
|
||||
<span>${app.category}</span>
|
||||
<span>${app.type}</span>
|
||||
</div>
|
||||
<div class="app-compact-title">${app.name}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Search
|
||||
const searchInput = document.getElementById('search-input');
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
clearTimeout(this.searchTimeout);
|
||||
this.searchTimeout = setTimeout(() => this.search(e.target.value), 300);
|
||||
});
|
||||
|
||||
// Keyboard shortcut
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === '/' && !searchInput.contains(document.activeElement)) {
|
||||
e.preventDefault();
|
||||
searchInput.focus();
|
||||
}
|
||||
if (e.key === 'Escape' && searchInput.contains(document.activeElement)) {
|
||||
searchInput.blur();
|
||||
searchInput.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Type filter
|
||||
const typeFilter = document.getElementById('type-filter');
|
||||
typeFilter.addEventListener('change', (e) => {
|
||||
this.currentType = e.target.value;
|
||||
this.loadMainContent();
|
||||
});
|
||||
|
||||
// Load more
|
||||
const loadMore = document.getElementById('load-more');
|
||||
loadMore.addEventListener('click', () => this.loadMoreApps());
|
||||
}
|
||||
|
||||
async filterByCategory(category) {
|
||||
// Update active state
|
||||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.category === category);
|
||||
});
|
||||
|
||||
this.currentCategory = category;
|
||||
await this.loadMainContent();
|
||||
}
|
||||
|
||||
async search(query) {
|
||||
if (!query) {
|
||||
await this.loadMainContent();
|
||||
return;
|
||||
}
|
||||
|
||||
const results = await this.api.search(query);
|
||||
if (!results) return;
|
||||
|
||||
// Update apps grid with search results
|
||||
if (results.apps && results.apps.length) {
|
||||
const appsGrid = document.getElementById('apps-grid');
|
||||
appsGrid.innerHTML = results.apps.map(app => `
|
||||
<div class="app-compact" onclick="marketplace.showAppDetail(${JSON.stringify(app).replace(/"/g, '"')})">
|
||||
<div class="app-compact-header">
|
||||
<span>${app.category}</span>
|
||||
<span>★ ${app.rating}/5</span>
|
||||
</div>
|
||||
<div class="app-compact-title">${app.name}</div>
|
||||
<div class="app-compact-desc">${app.description}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Update articles with search results
|
||||
if (results.articles && results.articles.length) {
|
||||
const articlesList = document.getElementById('articles-list');
|
||||
articlesList.innerHTML = results.articles.map(article => `
|
||||
<div class="article-compact" onclick="marketplace.showArticle('${article.id}')">
|
||||
<div class="article-meta">
|
||||
<span>${article.category}</span> · <span>${new Date(article.published_at).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div class="article-title">${article.title}</div>
|
||||
<div class="article-author">by ${article.author}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
async loadMoreApps() {
|
||||
this.loadedApps += 12;
|
||||
const moreApps = await this.api.getApps({ offset: this.loadedApps, limit: 12 });
|
||||
if (moreApps && moreApps.length) {
|
||||
const moreGrid = document.getElementById('more-apps-grid');
|
||||
moreApps.forEach(app => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'app-compact';
|
||||
card.innerHTML = `
|
||||
<div class="app-compact-header">
|
||||
<span>${app.category}</span>
|
||||
<span>${app.type}</span>
|
||||
</div>
|
||||
<div class="app-compact-title">${app.name}</div>
|
||||
`;
|
||||
card.onclick = () => this.showAppDetail(app);
|
||||
moreGrid.appendChild(card);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showAppDetail(app) {
|
||||
// Navigate to detail page instead of showing modal
|
||||
const slug = app.slug || app.name.toLowerCase().replace(/\s+/g, '-');
|
||||
window.location.href = `app-detail.html?app=${slug}`;
|
||||
}
|
||||
|
||||
showArticle(articleId) {
|
||||
// Could create article detail page similarly
|
||||
console.log('Show article:', articleId);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize marketplace
|
||||
let marketplace;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
marketplace = new MarketplaceUI();
|
||||
});
|
||||
Reference in New Issue
Block a user