diff --git a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx index 8f3acce..4a8b9c2 100644 --- a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx +++ b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx @@ -8,6 +8,7 @@ import { useMediaExtraction } from '@/hooks' import { cleanUrl } from '@/lib/url' import { ExternalLink } from 'lucide-react' import { Event, kinds } from 'nostr-tools' +import { ExtendedKind } from '@/constants' import React, { useMemo, useEffect, useRef, useState } from 'react' import Markdown from 'react-markdown' import remarkGfm from 'remark-gfm' @@ -561,7 +562,8 @@ export default function MarkdownArticle({ )} {/* Show title inline when metadata is hidden (for nested content) */} - {hideMetadata && metadata.title && ( + {/* Don't show title for discussions - it's already shown by the Note component */} + {hideMetadata && metadata.title && event.kind !== ExtendedKind.DISCUSSION && (

{metadata.title}

)} {!hideMetadata && metadata.image && (() => { diff --git a/src/components/NoteInteractions/Tabs.tsx b/src/components/NoteInteractions/Tabs.tsx index 6b572f1..ada38a1 100644 --- a/src/components/NoteInteractions/Tabs.tsx +++ b/src/components/NoteInteractions/Tabs.tsx @@ -45,14 +45,14 @@ export function Tabs({ }, [selectedTab, visibleTabs]) return ( -
-
+
+
{visibleTabs.map((tab, index) => (
(tabRefs.current[index] = el)} className={cn( - `text-center px-4 py-2 font-semibold clickable cursor-pointer rounded-lg`, + `text-center px-4 py-2 font-semibold whitespace-nowrap clickable cursor-pointer rounded-lg`, selectedTab === tab.value ? '' : 'text-muted-foreground' )} onClick={() => onTabChange(tab.value)} diff --git a/src/components/NoteInteractions/index.tsx b/src/components/NoteInteractions/index.tsx index c9ab644..ca76a10 100644 --- a/src/components/NoteInteractions/index.tsx +++ b/src/components/NoteInteractions/index.tsx @@ -1,4 +1,3 @@ -import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area' import { Separator } from '@/components/ui/separator' import { ExtendedKind } from '@/constants' import { shouldHideInteractions } from '@/lib/event-filtering' @@ -55,10 +54,9 @@ export default function NoteInteractions({ return ( <>
- +
- - +
{type === 'replies' && isDiscussion && ( <> diff --git a/src/components/Tabs/index.tsx b/src/components/Tabs/index.tsx index 177cb77..a194510 100644 --- a/src/components/Tabs/index.tsx +++ b/src/components/Tabs/index.tsx @@ -111,14 +111,14 @@ export default function Tabs({ deepBrowsing && lastScrollTop > threshold ? '-translate-y-[calc(100%+12rem)]' : '' )} > -
-
+
+
{tabs.map((tab, index) => (
(tabRefs.current[index] = el)} className={cn( - `w-fit text-center py-2 px-6 my-1 font-semibold whitespace-nowrap clickable cursor-pointer rounded-lg shrink-0`, + `text-center py-2 px-6 font-semibold whitespace-nowrap clickable cursor-pointer rounded-lg`, value === tab.value ? '' : 'text-muted-foreground' )} onClick={() => { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index fecc7a3..c222128 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -26,6 +26,25 @@ export function truncateText(text: string, maxWords: number): string { return words.slice(0, maxWords).join(' ') + '...' } +/** + * Remove emoji characters from a string + * This regex covers most emoji ranges including: + * - Emoticons (😀-🙏) + * - Misc Symbols & Pictographs (🚀-đŸ—ŋ) + * - Transport & Map Symbols (🚁-đŸ›ŋ) + * - Enclosed characters (â“‚ī¸, ÂŠī¸, etc.) + * - Regional indicator symbols (flags) + * - And other emoji ranges + */ +export function removeEmojis(text: string): string { + if (!text) return '' + + // Comprehensive emoji regex pattern covering major emoji Unicode ranges + const emojiRegex = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F900}-\u{1F9FF}]|[\u{1F018}-\u{1F270}]|[\u{238C}-\u{2454}]|[\u{20D0}-\u{20FF}]|[\u{FE00}-\u{FE0F}]|[\u{FE20}-\u{FE2F}]|[\u{E0020}-\u{E007F}]/gu + + return text.replace(emojiRegex, '').trim().replace(/\s+/g, ' ') +} + export function isSafari() { if (typeof window === 'undefined' || !window.navigator) return false const ua = window.navigator.userAgent diff --git a/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx b/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx index 8297675..5cc6163 100644 --- a/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx +++ b/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx @@ -141,9 +141,10 @@ export default function CreateThreadDialog({ if (dynamicTopics) { // Add dynamic main topics first dynamicTopics.mainTopics.forEach(dynamicTopic => { + const isGroupsTopic = dynamicTopic.id === 'groups' combined.push({ id: dynamicTopic.id, - label: `${dynamicTopic.label} (${dynamicTopic.count}) đŸ”Ĩ`, + label: `${dynamicTopic.label} (${dynamicTopic.count}) ${isGroupsTopic ? 'đŸ‘Ĩ' : 'đŸ”Ĩ'}`, icon: Hash // Use Hash icon for dynamic topics }) }) diff --git a/src/pages/primary/DiscussionsPage/ThreadCard.tsx b/src/pages/primary/DiscussionsPage/ThreadCard.tsx index ad02328..f083230 100644 --- a/src/pages/primary/DiscussionsPage/ThreadCard.tsx +++ b/src/pages/primary/DiscussionsPage/ThreadCard.tsx @@ -10,6 +10,7 @@ import Username from '@/components/Username' import UserAvatar from '@/components/UserAvatar' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { extractAllTopics, extractGroupInfo } from '@/lib/discussion-topics' +import { removeEmojis } from '@/lib/utils' interface ThreadCardProps { thread: NostrEvent @@ -33,9 +34,10 @@ export default function ThreadCard({ const { t } = useTranslation() const { isSmallScreen } = useScreenSize() - // Extract title from tags + // Extract title from tags and remove emojis const titleTag = thread.tags.find(tag => tag[0] === 'title' && tag[1]) - const title = titleTag?.[1] || t('Untitled') + const rawTitle = titleTag?.[1] || t('Untitled') + const title = removeEmojis(rawTitle) || t('Untitled') // Get topic info const topicTag = thread.tags.find(tag => tag[0] === 't' && tag[1]) @@ -67,10 +69,11 @@ export default function ThreadCard({ // Vote counts are no longer displayed, keeping variables for potential future use - // Get content preview - const contentPreview = thread.content.length > 250 - ? thread.content.substring(0, 250) + '...' - : thread.content + // Get content preview - remove emojis first, then truncate + const contentWithoutEmojis = removeEmojis(thread.content) + const contentPreview = contentWithoutEmojis.length > 250 + ? contentWithoutEmojis.substring(0, 250) + '...' + : contentWithoutEmojis return ( diff --git a/src/pages/primary/DiscussionsPage/index.tsx b/src/pages/primary/DiscussionsPage/index.tsx index f97419d..51dd2c8 100644 --- a/src/pages/primary/DiscussionsPage/index.tsx +++ b/src/pages/primary/DiscussionsPage/index.tsx @@ -904,15 +904,19 @@ const DiscussionsPage = forwardRef((_, ref) => { className="w-full sm:w-auto px-3 py-2 bg-white dark:bg-gray-800 text-black dark:text-white border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500" > - {availableTopics.map(({ topic, count, isDynamic, isMainTopic, isSubtopic }) => ( - - ))} + {availableTopics.map(({ topic, count, isDynamic, isMainTopic, isSubtopic }) => { + const isGroupsTopic = topic === 'groups' + return ( + + ) + })} {/* Time Span Dropdown */} @@ -949,13 +953,15 @@ const DiscussionsPage = forwardRef((_, ref) => { {Array.from(groupedEvents.entries()).map(([mainTopic, group]) => { const topicInfo = availableTopics.find(t => t.topic === mainTopic) const isDynamicMain = topicInfo?.isDynamic && topicInfo?.isMainTopic + const isGroupsTopic = mainTopic === 'groups' return (
{/* Main Topic Header */}

- {isDynamicMain && đŸ”Ĩ} + {isGroupsTopic && đŸ‘Ĩ} + {isDynamicMain && !isGroupsTopic && đŸ”Ĩ} {mainTopic} ({group.entries.length + Array.from(group.subtopics.values()).reduce((sum, events) => sum + events.length, 0)} {group.entries.length + Array.from(group.subtopics.values()).reduce((sum, events) => sum + events.length, 0) === 1 ? t('thread') : t('threads')}) {isDynamicMain && Main Topic}