mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-07 01:56:53 +00:00
fix(web): render custom HTML and Markdown content consistently (#5760)
* fix(markdown): render announcement markdown consistently - support soft line breaks for announcement markdown without changing the default parser behavior. - add explicit markdown element styles so lists, tables, code blocks, and quotes render correctly when typography styles are unavailable. - apply the announcement markdown mode in both the popover and detail dialog for consistent display. * refactor(markdown): simplify fallback markdown styles - remove duplicate typography utility classes now covered by explicit markdown element fallbacks. - keep the markdown renderer behavior unchanged while reducing class noise. - modernize small helper expressions to satisfy targeted lint checks. * fix(content): render custom HTML consistently - add shared rich content rendering so custom HTML and Markdown use the same path across public pages and announcements. - reuse common URL and HTML detection instead of duplicating content format checks per page. - keep custom home content inside the standard public layout while preserving full-page iframe rendering for external URLs.
This commit is contained in:
+32
-11
@@ -19,6 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import type { TFunction } from 'i18next'
|
||||
import { Bell, Megaphone } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RichContent } from '@/components/rich-content'
|
||||
import { getAnnouncementColorClass } from '@/lib/colors'
|
||||
import { formatDateTimeObject } from '@/lib/time'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -31,7 +32,6 @@ import {
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from '@/components/ui/empty'
|
||||
import { Markdown } from '@/components/ui/markdown'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -44,6 +44,7 @@ import { Separator } from '@/components/ui/separator'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
|
||||
interface AnnouncementItem {
|
||||
id?: number | string
|
||||
type?: string
|
||||
content?: string
|
||||
extra?: string
|
||||
@@ -72,8 +73,9 @@ function getRelativeTime(publishDate: string | Date, t: TFunction): string {
|
||||
const pubDate = new Date(publishDate)
|
||||
|
||||
// If invalid date, return original string
|
||||
if (isNaN(pubDate.getTime()))
|
||||
if (Number.isNaN(pubDate.getTime())) {
|
||||
return typeof publishDate === 'string' ? publishDate : ''
|
||||
}
|
||||
|
||||
const diffMs = now.getTime() - pubDate.getTime()
|
||||
const diffSeconds = Math.floor(diffMs / 1000)
|
||||
@@ -89,26 +91,31 @@ function getRelativeTime(publishDate: string | Date, t: TFunction): string {
|
||||
|
||||
// Return relative time based on difference
|
||||
if (diffSeconds < 60) return t('Just now')
|
||||
if (diffMinutes < 60)
|
||||
if (diffMinutes < 60) {
|
||||
return diffMinutes === 1
|
||||
? t('1 minute ago')
|
||||
: t('{{count}} minutes ago', { count: diffMinutes })
|
||||
if (diffHours < 24)
|
||||
}
|
||||
if (diffHours < 24) {
|
||||
return diffHours === 1
|
||||
? t('1 hour ago')
|
||||
: t('{{count}} hours ago', { count: diffHours })
|
||||
if (diffDays < 7)
|
||||
}
|
||||
if (diffDays < 7) {
|
||||
return diffDays === 1
|
||||
? t('1 day ago')
|
||||
: t('{{count}} days ago', { count: diffDays })
|
||||
if (diffWeeks < 4)
|
||||
}
|
||||
if (diffWeeks < 4) {
|
||||
return diffWeeks === 1
|
||||
? t('1 week ago')
|
||||
: t('{{count}} weeks ago', { count: diffWeeks })
|
||||
if (diffMonths < 12)
|
||||
}
|
||||
if (diffMonths < 12) {
|
||||
return diffMonths === 1
|
||||
? t('1 month ago')
|
||||
: t('{{count}} months ago', { count: diffMonths })
|
||||
}
|
||||
if (diffYears < 2) return t('1 year ago')
|
||||
|
||||
// Over 2 years, show specific date
|
||||
@@ -129,6 +136,19 @@ function AnnouncementDot({ type }: { type?: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
function getAnnouncementRenderKey(announcement: AnnouncementItem): string {
|
||||
if (announcement.id !== undefined && announcement.id !== null) {
|
||||
return `id:${announcement.id}`
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
content: announcement.content ?? '',
|
||||
extra: announcement.extra ?? '',
|
||||
publishDate: announcement.publishDate ?? '',
|
||||
type: announcement.type ?? '',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty state component
|
||||
*/
|
||||
@@ -184,7 +204,7 @@ function NoticeContent({
|
||||
|
||||
return (
|
||||
<ScrollArea className='h-[min(52vh,28rem)] pr-3'>
|
||||
<Markdown>{notice}</Markdown>
|
||||
<RichContent breaks content={notice} />
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
@@ -221,6 +241,7 @@ function AnnouncementsContent({
|
||||
<ScrollArea className='h-[min(52vh,28rem)] pr-3'>
|
||||
<div className='flex flex-col'>
|
||||
{announcements.map((item, idx) => {
|
||||
const announcementKey = getAnnouncementRenderKey(item)
|
||||
const publishDate = item.publishDate
|
||||
? new Date(item.publishDate)
|
||||
: null
|
||||
@@ -232,18 +253,18 @@ function AnnouncementsContent({
|
||||
: ''
|
||||
|
||||
return (
|
||||
<div key={idx}>
|
||||
<div key={announcementKey}>
|
||||
<div className='py-3'>
|
||||
<div className='flex items-start gap-3'>
|
||||
<AnnouncementDot type={item.type} />
|
||||
<div className='flex min-w-0 flex-1 flex-col gap-2'>
|
||||
<div className='text-sm'>
|
||||
<Markdown>{item.content || ''}</Markdown>
|
||||
<RichContent breaks content={item.content || ''} />
|
||||
</div>
|
||||
|
||||
{item.extra ? (
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
<Markdown>{item.extra}</Markdown>
|
||||
<RichContent breaks content={item.extra} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user