mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat: remove truncated description logic - cards are now a fixed height and the description section can be scrolled
This commit is contained in:
@@ -33,8 +33,6 @@ function parseLanguage(languageStr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_DESCRIPTION_LENGTH = 1000;
|
|
||||||
|
|
||||||
const ProfileCard = ({
|
const ProfileCard = ({
|
||||||
profile,
|
profile,
|
||||||
onEdit,
|
onEdit,
|
||||||
@@ -74,18 +72,12 @@ const ProfileCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const truncateDescription = text => {
|
|
||||||
if (!text) return '';
|
|
||||||
if (text.length <= MAX_DESCRIPTION_LENGTH) return text;
|
|
||||||
return text.substring(0, MAX_DESCRIPTION_LENGTH) + '...';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get quality preferences as an array
|
// Get quality preferences as an array
|
||||||
const qualityPreferences = content.qualities?.map(q => q.name) || [];
|
const qualityPreferences = content.qualities?.map(q => q.name) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-full bg-gradient-to-br from-gray-800/95 to-gray-900 border ${
|
className={`w-full h-[24rem] bg-gradient-to-br from-gray-800/95 to-gray-900 border ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-blue-500'
|
? 'border-blue-500'
|
||||||
: willBeSelected
|
: willBeSelected
|
||||||
@@ -97,137 +89,144 @@ const ProfileCard = ({
|
|||||||
? 'hover:border-blue-400'
|
? 'hover:border-blue-400'
|
||||||
: 'hover:border-gray-400'
|
: 'hover:border-gray-400'
|
||||||
: 'hover:border-blue-400'
|
: 'hover:border-blue-400'
|
||||||
} transition-all cursor-pointer overflow-hidden`}
|
} transition-all cursor-pointer overflow-hidden flex flex-col`}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onMouseDown={handleMouseDown}>
|
onMouseDown={handleMouseDown}>
|
||||||
<div className='p-6 relative'>
|
<div className='p-6 flex flex-col h-full'>
|
||||||
{/* Header Section */}
|
{/* Header Section - Fixed Height */}
|
||||||
<div className='flex justify-between items-start'>
|
<div className='flex-none'>
|
||||||
<div className='flex items-center gap-3 flex-wrap'>
|
<div className='flex justify-between items-start'>
|
||||||
<h3 className='text-xl font-bold text-gray-100'>
|
<div className='flex items-center gap-3 flex-wrap'>
|
||||||
{unsanitize(content.name)}
|
<h3 className='text-xl font-bold text-gray-100'>
|
||||||
</h3>
|
{unsanitize(content.name)}
|
||||||
{content.tags && content.tags.length > 0 && (
|
</h3>
|
||||||
<div className='flex flex-wrap gap-2'>
|
{content.tags && content.tags.length > 0 && (
|
||||||
{content.tags.map(tag => (
|
<div className='flex flex-wrap gap-2'>
|
||||||
<span
|
{content.tags.map(tag => (
|
||||||
key={`${profile.file_name}-${tag}`}
|
<span
|
||||||
className='bg-blue-600/20 text-blue-400 px-2 py-1 rounded-full text-xs font-semibold shadow-sm'>
|
key={`${profile.file_name}-${tag}`}
|
||||||
{unsanitize(tag)}
|
className='bg-blue-600/20 text-blue-400 px-2 py-1 rounded-full text-xs font-semibold shadow-sm'>
|
||||||
</span>
|
{unsanitize(tag)}
|
||||||
))}
|
</span>
|
||||||
</div>
|
))}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center'>
|
|
||||||
<div className='w-8 h-8 flex items-center justify-center'>
|
|
||||||
{isSelectionMode ? (
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
isSelected
|
|
||||||
? 'Selected'
|
|
||||||
: willBeSelected
|
|
||||||
? 'Will be selected'
|
|
||||||
: 'Select'
|
|
||||||
}>
|
|
||||||
<div
|
|
||||||
className={`w-6 h-6 rounded-full flex items-center justify-center ${
|
|
||||||
isSelected
|
|
||||||
? 'bg-blue-500'
|
|
||||||
: willBeSelected
|
|
||||||
? 'bg-blue-200/20'
|
|
||||||
: 'bg-gray-200/20'
|
|
||||||
} transition-colors hover:bg-blue-600`}>
|
|
||||||
{isSelected && (
|
|
||||||
<Check
|
|
||||||
size={14}
|
|
||||||
className='text-white'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{willBeSelected && !isSelected && (
|
|
||||||
<div className='w-1.5 h-1.5 rounded-full bg-blue-400' />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
onClick={handleCloneClick}
|
|
||||||
className='text-gray-400 hover:text-white transition-colors'>
|
|
||||||
<Copy className='w-5 h-5' />
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<div className='w-8 h-8 flex items-center justify-center'>
|
||||||
|
{isSelectionMode ? (
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
isSelected
|
||||||
|
? 'Selected'
|
||||||
|
: willBeSelected
|
||||||
|
? 'Will be selected'
|
||||||
|
: 'Select'
|
||||||
|
}>
|
||||||
|
<div
|
||||||
|
className={`w-6 h-6 rounded-full flex items-center justify-center ${
|
||||||
|
isSelected
|
||||||
|
? 'bg-blue-500'
|
||||||
|
: willBeSelected
|
||||||
|
? 'bg-blue-200/20'
|
||||||
|
: 'bg-gray-200/20'
|
||||||
|
} transition-colors hover:bg-blue-600`}>
|
||||||
|
{isSelected && (
|
||||||
|
<Check
|
||||||
|
size={14}
|
||||||
|
className='text-white'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{willBeSelected && !isSelected && (
|
||||||
|
<div className='w-1.5 h-1.5 rounded-full bg-blue-400' />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleCloneClick}
|
||||||
|
className='text-gray-400 hover:text-white transition-colors'>
|
||||||
|
<Copy className='w-5 h-5' />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Quality Preferences */}
|
||||||
|
{qualityPreferences.length > 0 && (
|
||||||
|
<div className='mt-6 flex items-center space-x-2 text-sm text-gray-300 overflow-x-auto pb-2'>
|
||||||
|
{qualityPreferences.map((pref, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<span className='whitespace-nowrap'>
|
||||||
|
{pref}
|
||||||
|
</span>
|
||||||
|
{index < qualityPreferences.length - 1 && (
|
||||||
|
<ChevronRight className='w-4 h-4 text-blue-400 flex-shrink-0' />
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quality Preferences */}
|
|
||||||
{qualityPreferences.length > 0 && (
|
|
||||||
<div className='mt-6 flex items-center space-x-2 text-sm text-gray-300 overflow-x-auto pb-2'>
|
|
||||||
{qualityPreferences.map((pref, index) => (
|
|
||||||
<React.Fragment key={index}>
|
|
||||||
<span className='whitespace-nowrap'>
|
|
||||||
{pref}
|
|
||||||
</span>
|
|
||||||
{index < qualityPreferences.length - 1 && (
|
|
||||||
<ChevronRight className='w-4 h-4 text-blue-400 flex-shrink-0' />
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<hr className='border-gray-700 my-6' />
|
<hr className='border-gray-700 my-6' />
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description - Fixed Height with Scroll */}
|
||||||
{content.description && (
|
<div className='flex-1 overflow-hidden'>
|
||||||
<div className='prose prose-invert prose-pre:bg-gray-800 prose-pre:border prose-pre:border-gray-700 max-w-none'>
|
{content.description && (
|
||||||
<ReactMarkdown>
|
<div className='h-full overflow-y-auto prose prose-invert prose-pre:bg-gray-800 prose-pre:border prose-pre:border-gray-700 max-w-none'>
|
||||||
{truncateDescription(
|
<ReactMarkdown>
|
||||||
unsanitize(content.description)
|
{unsanitize(content.description)}
|
||||||
)}
|
</ReactMarkdown>
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<hr className='border-gray-700 my-6' />
|
|
||||||
|
|
||||||
{/* Metadata Row */}
|
|
||||||
<div className='flex flex-wrap items-center justify-between text-sm text-gray-300'>
|
|
||||||
<div className='flex items-center gap-4'>
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<Settings2 className='w-4 h-4 text-blue-400' />
|
|
||||||
<span>
|
|
||||||
{activeCustomFormats} format
|
|
||||||
{activeCustomFormats !== 1 ? 's' : ''}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center gap-2'>
|
{/* Footer Section - Fixed Height */}
|
||||||
<Globe2 className='w-4 h-4 text-blue-400' />
|
<div className='flex-none'>
|
||||||
<span>{parseLanguage(content.language)}</span>
|
<hr className='border-gray-700 my-6' />
|
||||||
</div>
|
|
||||||
|
|
||||||
{content.upgradesAllowed && (
|
{/* Metadata Row */}
|
||||||
|
<div className='flex flex-wrap items-center justify-between text-sm text-gray-300'>
|
||||||
|
<div className='flex items-center gap-4'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<ArrowUpCircle className='w-4 h-4 text-blue-400' />
|
<Settings2 className='w-4 h-4 text-blue-400' />
|
||||||
<span>Upgrades Allowed</span>
|
<span>
|
||||||
|
{activeCustomFormats} format
|
||||||
|
{activeCustomFormats !== 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Globe2 className='w-4 h-4 text-blue-400' />
|
||||||
|
<span>{parseLanguage(content.language)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{content.upgradesAllowed && (
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<ArrowUpCircle className='w-4 h-4 text-blue-400' />
|
||||||
|
<span>Upgrades Allowed</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(sortBy === 'dateModified' ||
|
||||||
|
sortBy === 'dateCreated') && (
|
||||||
|
<span className='text-xs text-gray-400 shrink-0'>
|
||||||
|
{sortBy === 'dateModified'
|
||||||
|
? 'Modified'
|
||||||
|
: 'Created'}
|
||||||
|
:{' '}
|
||||||
|
{formatDate(
|
||||||
|
sortBy === 'dateModified'
|
||||||
|
? profile.modified_date
|
||||||
|
: profile.created_date
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(sortBy === 'dateModified' ||
|
|
||||||
sortBy === 'dateCreated') && (
|
|
||||||
<span className='text-xs text-gray-400 shrink-0'>
|
|
||||||
{sortBy === 'dateModified' ? 'Modified' : 'Created'}
|
|
||||||
:{' '}
|
|
||||||
{formatDate(
|
|
||||||
sortBy === 'dateModified'
|
|
||||||
? profile.modified_date
|
|
||||||
: profile.created_date
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user