Merge pull request #1560 from unclecode/fix/marketplace
Fix/marketplace
This commit is contained in:
@@ -529,8 +529,19 @@ class AdminDashboard {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group full-width">
|
<div class="form-group full-width">
|
||||||
<label>Integration Guide</label>
|
<label>Long Description (Markdown - Overview tab)</label>
|
||||||
<textarea id="form-integration" rows="10">${app?.integration_guide || ''}</textarea>
|
<textarea id="form-long-description" rows="10" placeholder="Enter detailed description with markdown formatting...">${app?.long_description || ''}</textarea>
|
||||||
|
<small>Markdown support: **bold**, *italic*, [links](url), # headers, code blocks, lists</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Integration Guide (Markdown - Integration tab)</label>
|
||||||
|
<textarea id="form-integration" rows="20" placeholder="Enter integration guide with installation, examples, and code snippets using markdown...">${app?.integration_guide || ''}</textarea>
|
||||||
|
<small>Single markdown field with installation, examples, and complete guide. Code blocks get auto copy buttons.</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Documentation (Markdown - Documentation tab)</label>
|
||||||
|
<textarea id="form-documentation" rows="20" placeholder="Enter documentation with API reference, examples, and best practices using markdown...">${app?.documentation || ''}</textarea>
|
||||||
|
<small>Full documentation with API reference, examples, best practices, etc.</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -712,7 +723,9 @@ class AdminDashboard {
|
|||||||
data.contact_email = document.getElementById('form-email').value;
|
data.contact_email = document.getElementById('form-email').value;
|
||||||
data.featured = document.getElementById('form-featured').checked ? 1 : 0;
|
data.featured = document.getElementById('form-featured').checked ? 1 : 0;
|
||||||
data.sponsored = document.getElementById('form-sponsored').checked ? 1 : 0;
|
data.sponsored = document.getElementById('form-sponsored').checked ? 1 : 0;
|
||||||
|
data.long_description = document.getElementById('form-long-description').value;
|
||||||
data.integration_guide = document.getElementById('form-integration').value;
|
data.integration_guide = document.getElementById('form-integration').value;
|
||||||
|
data.documentation = document.getElementById('form-documentation').value;
|
||||||
} else if (type === 'articles') {
|
} else if (type === 'articles') {
|
||||||
data.title = document.getElementById('form-title').value;
|
data.title = document.getElementById('form-title').value;
|
||||||
data.slug = this.generateSlug(data.title);
|
data.slug = this.generateSlug(data.title);
|
||||||
|
|||||||
@@ -510,6 +510,31 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Markdown rendered code blocks */
|
||||||
|
.integration-content pre,
|
||||||
|
.docs-content pre {
|
||||||
|
background: var(--bg-dark);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 1rem;
|
||||||
|
padding-top: 2.5rem; /* Space for copy button */
|
||||||
|
overflow-x: auto;
|
||||||
|
position: relative;
|
||||||
|
max-height: none; /* Remove any height restrictions */
|
||||||
|
height: auto; /* Allow content to expand */
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-content pre code,
|
||||||
|
.docs-content pre code {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre; /* Preserve whitespace and line breaks */
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
/* Feature Grid */
|
/* Feature Grid */
|
||||||
.feature-grid {
|
.feature-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -80,20 +80,7 @@
|
|||||||
<section id="overview-tab" class="tab-content active">
|
<section id="overview-tab" class="tab-content active">
|
||||||
<div class="overview-columns">
|
<div class="overview-columns">
|
||||||
<div class="overview-main">
|
<div class="overview-main">
|
||||||
<h2>Overview</h2>
|
|
||||||
<div id="app-overview">Overview content goes here.</div>
|
<div id="app-overview">Overview content goes here.</div>
|
||||||
|
|
||||||
<h3>Key Features</h3>
|
|
||||||
<ul id="app-features" class="features-list">
|
|
||||||
<li>Feature 1</li>
|
|
||||||
<li>Feature 2</li>
|
|
||||||
<li>Feature 3</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Use Cases</h3>
|
|
||||||
<div id="app-use-cases" class="use-cases">
|
|
||||||
<p>Describe how this app can help your workflow.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
@@ -142,33 +129,14 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="integration-tab" class="tab-content">
|
<section id="integration-tab" class="tab-content">
|
||||||
<div class="integration-content">
|
<div class="integration-content" id="app-integration">
|
||||||
<h2>Integration Guide</h2>
|
<!-- Integration guide markdown content will be rendered here -->
|
||||||
|
|
||||||
<h3>Installation</h3>
|
|
||||||
<div class="code-block">
|
|
||||||
<pre><code id="install-code"># Installation instructions will appear here</code></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Basic Usage</h3>
|
|
||||||
<div class="code-block">
|
|
||||||
<pre><code id="usage-code"># Usage example will appear here</code></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Complete Integration Example</h3>
|
|
||||||
<div class="code-block">
|
|
||||||
<button class="copy-btn" id="copy-integration">Copy</button>
|
|
||||||
<pre><code id="integration-code"># Complete integration guide will appear here</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="docs-tab" class="tab-content">
|
<section id="docs-tab" class="tab-content">
|
||||||
<div class="docs-content">
|
<div class="docs-content" id="app-docs">
|
||||||
<h2>Documentation</h2>
|
<!-- Documentation markdown content will be rendered here -->
|
||||||
<div id="app-docs" class="doc-sections">
|
|
||||||
<p>Documentation coming soon.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -123,144 +123,132 @@ class AppDetailPage {
|
|||||||
document.getElementById('sidebar-pricing').textContent = this.appData.pricing || 'Free';
|
document.getElementById('sidebar-pricing').textContent = this.appData.pricing || 'Free';
|
||||||
document.getElementById('sidebar-contact').textContent = this.appData.contact_email || 'contact@example.com';
|
document.getElementById('sidebar-contact').textContent = this.appData.contact_email || 'contact@example.com';
|
||||||
|
|
||||||
// Integration guide
|
// Render tab contents from database fields
|
||||||
this.renderIntegrationGuide();
|
this.renderTabContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderIntegrationGuide() {
|
renderTabContents() {
|
||||||
// Installation code
|
// Overview tab - use long_description from database
|
||||||
const installCode = document.getElementById('install-code');
|
const overviewDiv = document.getElementById('app-overview');
|
||||||
if (installCode) {
|
if (overviewDiv) {
|
||||||
if (this.appData.type === 'Open Source' && this.appData.github_url) {
|
if (this.appData.long_description) {
|
||||||
installCode.textContent = `# Clone from GitHub
|
overviewDiv.innerHTML = this.renderMarkdown(this.appData.long_description);
|
||||||
git clone ${this.appData.github_url}
|
} else {
|
||||||
|
overviewDiv.innerHTML = `<p>${this.appData.description || 'No overview available.'}</p>`;
|
||||||
# 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
|
// Integration tab - use integration_guide field from database
|
||||||
const usageCode = document.getElementById('usage-code');
|
const integrationDiv = document.getElementById('app-integration');
|
||||||
if (usageCode) {
|
if (integrationDiv) {
|
||||||
if (this.appData.category === 'Browser Automation') {
|
if (this.appData.integration_guide) {
|
||||||
usageCode.textContent = `from crawl4ai import AsyncWebCrawler
|
integrationDiv.innerHTML = this.renderMarkdown(this.appData.integration_guide);
|
||||||
from ${this.appData.slug.replace(/-/g, '_')} import ${this.appData.name.replace(/\s+/g, '')}
|
// Add copy buttons to all code blocks
|
||||||
|
this.addCopyButtonsToCodeBlocks(integrationDiv);
|
||||||
async def main():
|
} else {
|
||||||
# Initialize ${this.appData.name}
|
integrationDiv.innerHTML = '<p>Integration guide not yet available. Please check the official website for details.</p>';
|
||||||
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
|
// Documentation tab - use documentation field from database
|
||||||
const integrationCode = document.getElementById('integration-code');
|
const docsDiv = document.getElementById('app-docs');
|
||||||
if (integrationCode) {
|
if (docsDiv) {
|
||||||
integrationCode.textContent = this.appData.integration_guide ||
|
if (this.appData.documentation) {
|
||||||
`# Complete ${this.appData.name} Integration Example
|
docsDiv.innerHTML = this.renderMarkdown(this.appData.documentation);
|
||||||
|
// Add copy buttons to all code blocks
|
||||||
from crawl4ai import AsyncWebCrawler
|
this.addCopyButtonsToCodeBlocks(docsDiv);
|
||||||
from crawl4ai.extraction_strategy import JsonCssExtractionStrategy
|
} else {
|
||||||
import json
|
docsDiv.innerHTML = '<p>Documentation coming soon.</p>';
|
||||||
|
}
|
||||||
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}
|
addCopyButtonsToCodeBlocks(container) {
|
||||||
async with AsyncWebCrawler(
|
// Find all code blocks and add copy buttons
|
||||||
browser_type="chromium",
|
const codeBlocks = container.querySelectorAll('pre code');
|
||||||
headless=True,
|
codeBlocks.forEach(codeBlock => {
|
||||||
verbose=True
|
const pre = codeBlock.parentElement;
|
||||||
) as crawler:
|
|
||||||
|
|
||||||
# Crawl with extraction
|
// Skip if already has a copy button
|
||||||
result = await crawler.arun(
|
if (pre.querySelector('.copy-btn')) return;
|
||||||
url="https://example.com/products",
|
|
||||||
extraction_strategy=JsonCssExtractionStrategy(schema),
|
|
||||||
cache_mode="bypass",
|
|
||||||
wait_for="css:.product",
|
|
||||||
screenshot=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Process results
|
// Create copy button
|
||||||
if result.success:
|
const copyBtn = document.createElement('button');
|
||||||
products = json.loads(result.extracted_content)
|
copyBtn.className = 'copy-btn';
|
||||||
print(f"Found {len(products)} products")
|
copyBtn.textContent = 'Copy';
|
||||||
|
copyBtn.onclick = () => {
|
||||||
|
navigator.clipboard.writeText(codeBlock.textContent).then(() => {
|
||||||
|
copyBtn.textContent = '✓ Copied!';
|
||||||
|
setTimeout(() => {
|
||||||
|
copyBtn.textContent = 'Copy';
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
for product in products[:5]:
|
// Add button to pre element
|
||||||
print(f"- {product['title']}: {product['price']}")
|
pre.style.position = 'relative';
|
||||||
|
pre.insertBefore(copyBtn, codeBlock);
|
||||||
return products
|
});
|
||||||
|
|
||||||
# Run the crawler
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import asyncio
|
|
||||||
asyncio.run(crawl_with_${this.appData.slug.replace(/-/g, '_')}())`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderMarkdown(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
|
||||||
|
// Store code blocks temporarily to protect them from processing
|
||||||
|
const codeBlocks = [];
|
||||||
|
let processed = text.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
|
||||||
|
const placeholder = `___CODE_BLOCK_${codeBlocks.length}___`;
|
||||||
|
codeBlocks.push(`<pre><code class="language-${lang || ''}">${this.escapeHtml(code)}</code></pre>`);
|
||||||
|
return placeholder;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store inline code temporarily
|
||||||
|
const inlineCodes = [];
|
||||||
|
processed = processed.replace(/`([^`]+)`/g, (match, code) => {
|
||||||
|
const placeholder = `___INLINE_CODE_${inlineCodes.length}___`;
|
||||||
|
inlineCodes.push(`<code>${this.escapeHtml(code)}</code>`);
|
||||||
|
return placeholder;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now process the rest of the markdown
|
||||||
|
processed = processed
|
||||||
|
// Headers
|
||||||
|
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
||||||
|
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
||||||
|
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
||||||
|
// Bold
|
||||||
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
// Italic
|
||||||
|
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||||
|
// Links
|
||||||
|
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
|
||||||
|
// Line breaks
|
||||||
|
.replace(/\n\n/g, '</p><p>')
|
||||||
|
.replace(/\n/g, '<br>')
|
||||||
|
// Lists
|
||||||
|
.replace(/^\* (.*)$/gim, '<li>$1</li>')
|
||||||
|
.replace(/^- (.*)$/gim, '<li>$1</li>')
|
||||||
|
// Wrap in paragraphs
|
||||||
|
.replace(/^(?!<[h|p|pre|ul|ol|li])/gim, '<p>')
|
||||||
|
.replace(/(?<![>])$/gim, '</p>');
|
||||||
|
|
||||||
|
// Restore inline code
|
||||||
|
inlineCodes.forEach((code, i) => {
|
||||||
|
processed = processed.replace(`___INLINE_CODE_${i}___`, code);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restore code blocks
|
||||||
|
codeBlocks.forEach((block, i) => {
|
||||||
|
processed = processed.replace(`___CODE_BLOCK_${i}___`, block);
|
||||||
|
});
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatNumber(num) {
|
formatNumber(num) {
|
||||||
@@ -289,33 +277,6 @@ if __name__ == "__main__":
|
|||||||
document.getElementById(`${tabName}-tab`).classList.add('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() {
|
async loadRelatedApps() {
|
||||||
|
|||||||
Reference in New Issue
Block a user