From 97c92c4f62dc3ece3912776229e6909e8a1f4af7 Mon Sep 17 00:00:00 2001
From: ntohidi
Date: Tue, 21 Oct 2025 15:39:04 +0200
Subject: [PATCH] fix(marketplace): replace hardcoded app detail content with
database-driven fields.
The app detail page was displaying hardcoded/templated content instead of
using actual data from the database. This prevented admins from controlling
the content shown in Overview, Integration, and Documentation tabs.
---
docs/md_v2/marketplace/admin/admin.js | 30 +++-
docs/md_v2/marketplace/app-detail.js | 199 ++++++++++----------------
2 files changed, 105 insertions(+), 124 deletions(-)
diff --git a/docs/md_v2/marketplace/admin/admin.js b/docs/md_v2/marketplace/admin/admin.js
index d43dd822..ccc6c467 100644
--- a/docs/md_v2/marketplace/admin/admin.js
+++ b/docs/md_v2/marketplace/admin/admin.js
@@ -529,8 +529,29 @@ class AdminDashboard {
-
-
+
+
+ Supports markdown: **bold**, *italic*, [links](url), # headers, etc.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`;
@@ -712,7 +733,12 @@ class AdminDashboard {
data.contact_email = document.getElementById('form-email').value;
data.featured = document.getElementById('form-featured').checked ? 1 : 0;
data.sponsored = document.getElementById('form-sponsored').checked ? 1 : 0;
+ data.long_description = document.getElementById('form-long-description').value;
+ data.installation_command = document.getElementById('form-installation').value;
+ data.examples = document.getElementById('form-examples').value;
data.integration_guide = document.getElementById('form-integration').value;
+ data.documentation = document.getElementById('form-documentation').value;
+ data.requirements = document.getElementById('form-requirements').value;
} else if (type === 'articles') {
data.title = document.getElementById('form-title').value;
data.slug = this.generateSlug(data.title);
diff --git a/docs/md_v2/marketplace/app-detail.js b/docs/md_v2/marketplace/app-detail.js
index f470bf51..404f484e 100644
--- a/docs/md_v2/marketplace/app-detail.js
+++ b/docs/md_v2/marketplace/app-detail.js
@@ -123,144 +123,99 @@ class AppDetailPage {
document.getElementById('sidebar-pricing').textContent = this.appData.pricing || 'Free';
document.getElementById('sidebar-contact').textContent = this.appData.contact_email || 'contact@example.com';
- // Integration guide
- this.renderIntegrationGuide();
+ // Render tab contents from database fields
+ this.renderTabContents();
}
- renderIntegrationGuide() {
- // Installation code
+ renderTabContents() {
+ // Overview tab - use long_description from database
+ const overviewDiv = document.getElementById('app-overview');
+ if (overviewDiv) {
+ if (this.appData.long_description) {
+ overviewDiv.innerHTML = this.renderMarkdown(this.appData.long_description);
+ } else {
+ overviewDiv.innerHTML = `${this.appData.description || 'No overview available.'}
`;
+ }
+ }
+
+ // Documentation tab - use documentation field from database
+ const docsDiv = document.getElementById('app-docs');
+ if (docsDiv) {
+ if (this.appData.documentation) {
+ docsDiv.innerHTML = this.renderMarkdown(this.appData.documentation);
+ } else {
+ docsDiv.innerHTML = 'Documentation coming soon.
';
+ }
+ }
+
+ // Integration tab - use integration_guide, installation_command, examples from database
+ this.renderIntegrationTab();
+ }
+
+ renderIntegrationTab() {
+ // Installation code - use installation_command from database
const installCode = document.getElementById('install-code');
if (installCode) {
- 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'}`;
+ if (this.appData.installation_command) {
+ installCode.textContent = this.appData.installation_command;
+ } else {
+ // Fallback to generic installation
+ installCode.textContent = `# Installation instructions not yet available\n# Please check ${this.appData.website_url || 'the official website'} for details`;
}
}
- // Usage code - customize based on category
+ // Usage code - use examples field from database
const usageCode = document.getElementById('usage-code');
- if (usageCode) {
- 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)`;
+ if (usageCode && this.appData.examples) {
+ // Extract first code block from examples if it contains multiple
+ const codeMatch = this.appData.examples.match(/```[\s\S]*?```/);
+ if (codeMatch) {
+ usageCode.textContent = codeMatch[0].replace(/```(\w+)?\n?/g, '').trim();
+ } else {
+ usageCode.textContent = this.appData.examples;
}
}
- // Integration example
+ // Complete integration - use integration_guide field from database
const integrationCode = document.getElementById('integration-code');
if (integrationCode) {
- 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"}
- ]
+ if (this.appData.integration_guide) {
+ integrationCode.textContent = this.appData.integration_guide;
+ } else {
+ // Fallback message
+ integrationCode.textContent = `# Integration guide not yet available for ${this.appData.name}\n\n# Please visit the admin panel to add integration instructions\n# Or check ${this.appData.website_url || 'the official website'} for integration details`;
+ }
+ }
}
- # Initialize crawler with ${this.appData.name}
- async with AsyncWebCrawler(
- browser_type="chromium",
- headless=True,
- verbose=True
- ) as crawler:
+ renderMarkdown(text) {
+ if (!text) return '';
- # 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, '_')}())`;
- }
+ // Simple markdown rendering (convert to HTML)
+ return text
+ // Headers
+ .replace(/^### (.*$)/gim, '$1
')
+ .replace(/^## (.*$)/gim, '$1
')
+ .replace(/^# (.*$)/gim, '$1
')
+ // Bold
+ .replace(/\*\*(.*?)\*\*/g, '$1')
+ // Italic
+ .replace(/\*(.*?)\*/g, '$1')
+ // Links
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
+ // Code blocks
+ .replace(/```(\w+)?\n([\s\S]*?)```/g, '$2
')
+ // Inline code
+ .replace(/`([^`]+)`/g, '$1')
+ // Line breaks
+ .replace(/\n\n/g, '
')
+ .replace(/\n/g, '
')
+ // Lists
+ .replace(/^\* (.*)$/gim, '
$1')
+ .replace(/^- (.*)$/gim, '$1')
+ // Wrap in paragraphs
+ .replace(/^(?!<[h|p|pre|ul|ol|li])/gim, '')
+ .replace(/(?])$/gim, '
');
}
formatNumber(num) {