From 97c92c4f62dc3ece3912776229e6909e8a1f4af7 Mon Sep 17 00:00:00 2001 From: ntohidi Date: Tue, 21 Oct 2025 15:39:04 +0200 Subject: [PATCH 1/2] 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) { From 13e116610daa493fb32d5dffa5f8c5c4a87a7c7d Mon Sep 17 00:00:00 2001 From: ntohidi Date: Thu, 23 Oct 2025 16:12:30 +0200 Subject: [PATCH 2/2] fix(marketplace): improve app detail page content rendering and UX Fixed multiple issues with app detail page content display and formatting --- docs/md_v2/marketplace/admin/admin.js | 31 ++---- docs/md_v2/marketplace/app-detail.css | 25 +++++ docs/md_v2/marketplace/app-detail.html | 40 +------ docs/md_v2/marketplace/app-detail.js | 142 +++++++++++++------------ 4 files changed, 112 insertions(+), 126 deletions(-) diff --git a/docs/md_v2/marketplace/admin/admin.js b/docs/md_v2/marketplace/admin/admin.js index ccc6c467..7bdc3fc5 100644 --- a/docs/md_v2/marketplace/admin/admin.js +++ b/docs/md_v2/marketplace/admin/admin.js @@ -529,29 +529,19 @@ class AdminDashboard {
    - - - Supports markdown: **bold**, *italic*, [links](url), # headers, etc. + + + Markdown support: **bold**, *italic*, [links](url), # headers, code blocks, lists
    - - + + + Single markdown field with installation, examples, and complete guide. Code blocks get auto copy buttons.
    - - -
    -
    - - -
    -
    - - -
    -
    - - + + + Full documentation with API reference, examples, best practices, etc.
    `; @@ -734,11 +724,8 @@ class AdminDashboard { 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.css b/docs/md_v2/marketplace/app-detail.css index 590bea03..0e5d0002 100644 --- a/docs/md_v2/marketplace/app-detail.css +++ b/docs/md_v2/marketplace/app-detail.css @@ -510,6 +510,31 @@ 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 { display: grid; diff --git a/docs/md_v2/marketplace/app-detail.html b/docs/md_v2/marketplace/app-detail.html index ef1138a8..fbc8c13d 100644 --- a/docs/md_v2/marketplace/app-detail.html +++ b/docs/md_v2/marketplace/app-detail.html @@ -80,20 +80,7 @@
    -

    Overview

    Overview content goes here.
    - -

    Key Features

    -
      -
    • Feature 1
    • -
    • Feature 2
    • -
    • Feature 3
    • -
    - -

    Use Cases

    -
    -

    Describe how this app can help your workflow.

    -
    -
    -

    Integration Guide

    - -

    Installation

    -
    -
    # Installation instructions will appear here
    -
    - -

    Basic Usage

    -
    -
    # Usage example will appear here
    -
    - -

    Complete Integration Example

    -
    - -
    # Complete integration guide will appear here
    -
    +
    +
    -
    -

    Documentation

    -
    -

    Documentation coming soon.

    -
    +
    +
    diff --git a/docs/md_v2/marketplace/app-detail.js b/docs/md_v2/marketplace/app-detail.js index 404f484e..09c519b4 100644 --- a/docs/md_v2/marketplace/app-detail.js +++ b/docs/md_v2/marketplace/app-detail.js @@ -138,61 +138,80 @@ class AppDetailPage { } } + // Integration tab - use integration_guide field from database + const integrationDiv = document.getElementById('app-integration'); + if (integrationDiv) { + if (this.appData.integration_guide) { + integrationDiv.innerHTML = this.renderMarkdown(this.appData.integration_guide); + // Add copy buttons to all code blocks + this.addCopyButtonsToCodeBlocks(integrationDiv); + } else { + integrationDiv.innerHTML = '

    Integration guide not yet available. Please check the official website for details.

    '; + } + } + // 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); + // Add copy buttons to all code blocks + this.addCopyButtonsToCodeBlocks(docsDiv); } 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.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`; - } - } + addCopyButtonsToCodeBlocks(container) { + // Find all code blocks and add copy buttons + const codeBlocks = container.querySelectorAll('pre code'); + codeBlocks.forEach(codeBlock => { + const pre = codeBlock.parentElement; - // Usage code - use examples field from database - const usageCode = document.getElementById('usage-code'); - 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; - } - } + // Skip if already has a copy button + if (pre.querySelector('.copy-btn')) return; - // Complete integration - use integration_guide field from database - const integrationCode = document.getElementById('integration-code'); - if (integrationCode) { - 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`; - } - } + // Create copy button + const copyBtn = document.createElement('button'); + copyBtn.className = 'copy-btn'; + copyBtn.textContent = 'Copy'; + copyBtn.onclick = () => { + navigator.clipboard.writeText(codeBlock.textContent).then(() => { + copyBtn.textContent = '✓ Copied!'; + setTimeout(() => { + copyBtn.textContent = 'Copy'; + }, 2000); + }); + }; + + // Add button to pre element + pre.style.position = 'relative'; + pre.insertBefore(copyBtn, codeBlock); + }); } renderMarkdown(text) { if (!text) return ''; - // Simple markdown rendering (convert to HTML) - return text + // 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(`
    ${this.escapeHtml(code)}
    `); + return placeholder; + }); + + // Store inline code temporarily + const inlineCodes = []; + processed = processed.replace(/`([^`]+)`/g, (match, code) => { + const placeholder = `___INLINE_CODE_${inlineCodes.length}___`; + inlineCodes.push(`${this.escapeHtml(code)}`); + return placeholder; + }); + + // Now process the rest of the markdown + processed = processed // Headers .replace(/^### (.*$)/gim, '

    $1

    ') .replace(/^## (.*$)/gim, '

    $1

    ') @@ -203,10 +222,6 @@ class AppDetailPage { .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, '
    ') @@ -216,6 +231,24 @@ class AppDetailPage { // Wrap in paragraphs .replace(/^(?!<[h|p|pre|ul|ol|li])/gim, '

    ') .replace(/(?])$/gim, '

    '); + + // 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) { @@ -244,33 +277,6 @@ class AppDetailPage { 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 = ' 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() {