Enhance HTMLContentRenderer and ToggleTableRow for improved content handling and metadata display

- Updated HTMLContentRenderer to directly utilize HTML from content objects.
- Modified ToggleTableRow to extract and display content metadata, including primary and secondary keywords, tags, categories, and meta descriptions.
- Refactored badge rendering logic for better organization and clarity in the UI.
- Improved content fallback mechanisms in the Content page for better user experience.
This commit is contained in:
IGNY8 VPS (Salman)
2025-11-10 14:10:01 +00:00
parent 8b6e18649c
commit 926ac150fd
3 changed files with 187 additions and 9 deletions

View File

@@ -18,6 +18,14 @@ interface HTMLContentRendererProps {
function formatContentOutline(content: any): string { function formatContentOutline(content: any): string {
if (!content) return ''; if (!content) return '';
// If object contains a `content` field with HTML, use that directly
if (typeof content === 'object' && content !== null && 'content' in content) {
const mainContent = (content as any).content;
if (typeof mainContent === 'string' && mainContent.trim().length > 0) {
return sanitizeHTML(mainContent);
}
}
let html = '<div class="content-outline">'; let html = '<div class="content-outline">';
// Handle introduction section - can be object or string // Handle introduction section - can be object or string

View File

@@ -6,6 +6,7 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { ChevronDownIcon, HorizontaLDots } from '../../icons'; import { ChevronDownIcon, HorizontaLDots } from '../../icons';
import HTMLContentRenderer from './HTMLContentRenderer'; import HTMLContentRenderer from './HTMLContentRenderer';
import Badge from '../ui/badge/Badge';
interface ToggleTableRowProps { interface ToggleTableRowProps {
/** The row data */ /** The row data */
@@ -44,6 +45,13 @@ const ToggleTableRow: React.FC<ToggleTableRowProps> = ({
// Get content - handle fallback to description if primary contentKey is empty // Get content - handle fallback to description if primary contentKey is empty
let content = row[contentKey]; let content = row[contentKey];
let contentMetadata: Record<string, any> | null = null;
if (content && typeof content === 'object' && content !== null && 'content' in content) {
contentMetadata = { ...content };
content = content.content;
}
if (!content || (typeof content === 'string' && content.trim().length === 0)) { if (!content || (typeof content === 'string' && content.trim().length === 0)) {
// Try fallback to description if primary content is empty // Try fallback to description if primary content is empty
content = row.description || row.content_outline || null; content = row.description || row.content_outline || null;
@@ -104,9 +112,157 @@ const ToggleTableRow: React.FC<ToggleTableRowProps> = ({
className="overflow-hidden" className="overflow-hidden"
> >
<div className="py-4 px-2"> <div className="py-4 px-2">
<div className="mb-2 text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 tracking-wide"> <div className="flex flex-col gap-3">
{contentLabel} <div className="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 tracking-wide">
{contentLabel}
</div>
{/* Metadata badges */}
<ToggleMetadata row={row} contentMetadata={contentMetadata} />
{/* Rendered content */}
<div className="html-content-wrapper">
<HTMLContentRenderer
content={content}
className="text-sm text-gray-700 dark:text-gray-300 leading-relaxed"
/>
</div>
</div> </div>
</div>
</div>
</td>
</tr>
);
};
interface ToggleMetadataProps {
row: any;
contentMetadata: Record<string, any> | null;
}
const ToggleMetadata: React.FC<ToggleMetadataProps> = ({ row, contentMetadata }) => {
const primaryKeyword =
row.content_primary_keyword ||
row.primary_keyword ||
contentMetadata?.primary_keyword ||
contentMetadata?.metadata?.primary_keyword ||
null;
const secondaryKeywords =
row.content_secondary_keywords ||
row.secondary_keywords ||
contentMetadata?.secondary_keywords ||
contentMetadata?.metadata?.secondary_keywords ||
[];
const tags =
row.content_tags ||
row.tags ||
contentMetadata?.tags ||
contentMetadata?.metadata?.tags ||
[];
const categories =
row.content_categories ||
row.categories ||
contentMetadata?.categories ||
contentMetadata?.metadata?.categories ||
[];
const metaDescription =
row.meta_description ||
row.content_meta_description ||
contentMetadata?.meta_description ||
contentMetadata?.metadata?.meta_description ||
null;
const hasMetadata =
primaryKeyword ||
(secondaryKeywords && secondaryKeywords.length > 0) ||
(tags && tags.length > 0) ||
(categories && categories.length > 0) ||
metaDescription;
if (!hasMetadata) {
return null;
}
const renderBadgeList = (items: any, color: 'info' | 'light' = 'light') => {
if (!items) return null;
const list = Array.isArray(items) ? items : [items];
if (list.length === 0) return null;
return (
<div className="flex flex-wrap gap-1">
{list.map((item, index) => (
<Badge
key={`${item}-${index}`}
color={color}
size="sm"
variant="light"
>
{item}
</Badge>
))}
</div>
);
};
return (
<div className="space-y-2">
{primaryKeyword && (
<div className="flex items-start gap-2 text-sm text-gray-700 dark:text-gray-300">
<span className="font-medium text-gray-600 dark:text-gray-400">Primary Keyword:</span>
<Badge color="info" size="sm" variant="light">
{primaryKeyword}
</Badge>
</div>
)}
{(() => {
const badges = renderBadgeList(secondaryKeywords);
if (!badges) return null;
return (
<div className="flex items-start gap-2 text-sm text-gray-700 dark:text-gray-300">
<span className="font-medium text-gray-600 dark:text-gray-400">Secondary Keywords:</span>
{badges}
</div>
);
})()}
{(() => {
const badges = renderBadgeList(tags);
if (!badges) return null;
return (
<div className="flex items-start gap-2 text-sm text-gray-700 dark:text-gray-300">
<span className="font-medium text-gray-600 dark:text-gray-400">Tags:</span>
{badges}
</div>
);
})()}
{(() => {
const badges = renderBadgeList(categories);
if (!badges) return null;
return (
<div className="flex items-start gap-2 text-sm text-gray-700 dark:text-gray-300">
<span className="font-medium text-gray-600 dark:text-gray-400">Categories:</span>
{badges}
</div>
);
})()}
{metaDescription && (
<div className="rounded-lg bg-gray-50 dark:bg-gray-800/50 p-3 text-xs text-gray-600 dark:text-gray-400">
<span className="font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400 block mb-1">
Meta Description
</span>
{metaDescription}
</div>
)}
</div>
);
};
<div className="html-content-wrapper"> <div className="html-content-wrapper">
<HTMLContentRenderer <HTMLContentRenderer
content={content} content={content}

View File

@@ -43,7 +43,14 @@ export default function Content() {
minute: 'numeric', minute: 'numeric',
}); });
const renderBadgeList = (items?: string[], emptyLabel = '-') => { const getList = (primary?: string[], fallback?: any): string[] => {
if (primary && primary.length > 0) return primary;
if (!fallback) return [];
if (Array.isArray(fallback)) return fallback;
return [];
};
const renderBadgeList = (items: string[], emptyLabel = '-') => {
if (!items || items.length === 0) { if (!items || items.length === 0) {
return <span className="text-gray-400 dark:text-gray-500">{emptyLabel}</span>; return <span className="text-gray-400 dark:text-gray-500">{emptyLabel}</span>;
} }
@@ -112,11 +119,18 @@ export default function Content() {
<tbody className="divide-y divide-gray-200 dark:divide-white/[0.05]"> <tbody className="divide-y divide-gray-200 dark:divide-white/[0.05]">
{content.map((item) => { {content.map((item) => {
const isExpanded = expandedId === item.id; const isExpanded = expandedId === item.id;
return ( const secondaryKeywords = getList(
item.secondary_keywords,
item.metadata?.secondary_keywords
);
const tags = getList(item.tags, item.metadata?.tags);
const categories = getList(item.categories, item.metadata?.categories);
return (
<tr key={item.id} className="bg-white dark:bg-gray-900"> <tr key={item.id} className="bg-white dark:bg-gray-900">
<td className="px-5 py-4 align-top"> <td className="px-5 py-4 align-top">
<div className="font-medium text-gray-900 dark:text-white"> <div className="font-medium text-gray-900 dark:text-white">
{item.meta_title || item.title || `Task #${item.task}`} {item.meta_title || item.title || item.task_title || `Task #${item.task}`}
</div> </div>
{item.meta_description && ( {item.meta_description && (
<div className="mt-1 text-sm text-gray-500 dark:text-gray-400 line-clamp-2"> <div className="mt-1 text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
@@ -134,13 +148,13 @@ export default function Content() {
)} )}
</td> </td>
<td className="px-5 py-4 align-top"> <td className="px-5 py-4 align-top">
{renderBadgeList(item.secondary_keywords)} {renderBadgeList(secondaryKeywords)}
</td> </td>
<td className="px-5 py-4 align-top"> <td className="px-5 py-4 align-top">
{renderBadgeList(item.tags)} {renderBadgeList(tags)}
</td> </td>
<td className="px-5 py-4 align-top"> <td className="px-5 py-4 align-top">
{renderBadgeList(item.categories)} {renderBadgeList(categories)}
</td> </td>
<td className="px-5 py-4 align-top text-gray-700 dark:text-gray-300"> <td className="px-5 py-4 align-top text-gray-700 dark:text-gray-300">
{item.word_count?.toLocaleString?.() ?? '-'} {item.word_count?.toLocaleString?.() ?? '-'}