From 13e116610daa493fb32d5dffa5f8c5c4a87a7c7d Mon Sep 17 00:00:00 2001 From: ntohidi Date: Thu, 23 Oct 2025 16:12:30 +0200 Subject: [PATCH] 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() {