Browse Source

bug-fixed discussions and tab rows

imwald
Silberengel 4 months ago
parent
commit
f6b175973e
  1. 4
      src/components/Note/MarkdownArticle/MarkdownArticle.tsx
  2. 6
      src/components/NoteInteractions/Tabs.tsx
  3. 6
      src/components/NoteInteractions/index.tsx
  4. 6
      src/components/Tabs/index.tsx
  5. 19
      src/lib/utils.ts
  6. 3
      src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx
  7. 15
      src/pages/primary/DiscussionsPage/ThreadCard.tsx
  8. 26
      src/pages/primary/DiscussionsPage/index.tsx

4
src/components/Note/MarkdownArticle/MarkdownArticle.tsx

@ -8,6 +8,7 @@ import { useMediaExtraction } from '@/hooks' @@ -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({ @@ -561,7 +562,8 @@ export default function MarkdownArticle({
</blockquote>
)}
{/* 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 && (
<h2 className="text-2xl font-bold mb-4 leading-tight break-words">{metadata.title}</h2>
)}
{!hideMetadata && metadata.image && (() => {

6
src/components/NoteInteractions/Tabs.tsx

@ -45,14 +45,14 @@ export function Tabs({ @@ -45,14 +45,14 @@ export function Tabs({
}, [selectedTab, visibleTabs])
return (
<div className="w-fit">
<div className="flex relative">
<div className="w-full">
<div className="flex flex-wrap relative gap-1">
{visibleTabs.map((tab, index) => (
<div
key={tab.value}
ref={(el) => (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)}

6
src/components/NoteInteractions/index.tsx

@ -1,4 +1,3 @@ @@ -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({ @@ -55,10 +54,9 @@ export default function NoteInteractions({
return (
<>
<div className="flex items-center justify-between">
<ScrollArea className="flex-1 w-0">
<div className="flex-1 w-0">
<Tabs selectedTab={type} onTabChange={setType} hideRepostsAndQuotes={isDiscussion} />
<ScrollBar orientation="horizontal" className="opacity-0 pointer-events-none" />
</ScrollArea>
</div>
<Separator orientation="vertical" className="h-6" />
{type === 'replies' && isDiscussion && (
<>

6
src/components/Tabs/index.tsx

@ -111,14 +111,14 @@ export default function Tabs({ @@ -111,14 +111,14 @@ export default function Tabs({
deepBrowsing && lastScrollTop > threshold ? '-translate-y-[calc(100%+12rem)]' : ''
)}
>
<div className="flex-1 w-0 overflow-x-auto scrollbar-hide">
<div className="flex w-fit relative min-w-full">
<div className="flex-1 w-0">
<div className="flex flex-wrap relative gap-1">
{tabs.map((tab, index) => (
<div
key={tab.value}
ref={(el) => (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={() => {

19
src/lib/utils.ts

@ -26,6 +26,25 @@ export function truncateText(text: string, maxWords: number): string { @@ -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

3
src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx

@ -141,9 +141,10 @@ export default function CreateThreadDialog({ @@ -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
})
})

15
src/pages/primary/DiscussionsPage/ThreadCard.tsx

@ -10,6 +10,7 @@ import Username from '@/components/Username' @@ -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({ @@ -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({ @@ -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 (

26
src/pages/primary/DiscussionsPage/index.tsx

@ -904,15 +904,19 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -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"
>
<option value="all">All Topics ({allEventMap.size})</option>
{availableTopics.map(({ topic, count, isDynamic, isMainTopic, isSubtopic }) => (
<option key={topic} value={topic}>
{isDynamic && isMainTopic ? '🔥 ' : ''}
{isDynamic && isSubtopic ? '📌 ' : ''}
{topic} ({count})
{isDynamic && isMainTopic ? ' [Main Topic]' : ''}
{isDynamic && isSubtopic ? ' [Subtopic]' : ''}
</option>
))}
{availableTopics.map(({ topic, count, isDynamic, isMainTopic, isSubtopic }) => {
const isGroupsTopic = topic === 'groups'
return (
<option key={topic} value={topic}>
{isGroupsTopic ? '👥 ' : ''}
{isDynamic && isMainTopic && !isGroupsTopic ? '🔥 ' : ''}
{isDynamic && isSubtopic ? '📌 ' : ''}
{topic} ({count})
{isDynamic && isMainTopic ? ' [Main Topic]' : ''}
{isDynamic && isSubtopic ? ' [Subtopic]' : ''}
</option>
)
})}
</select>
{/* Time Span Dropdown */}
@ -949,13 +953,15 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -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 (
<div key={mainTopic} className="space-y-4">
{/* Main Topic Header */}
<h2 className="text-lg font-semibold mb-3 capitalize flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
<span className="flex items-center gap-2">
{isDynamicMain && <span className="text-orange-500">🔥</span>}
{isGroupsTopic && <span className="text-blue-500">👥</span>}
{isDynamicMain && !isGroupsTopic && <span className="text-orange-500">🔥</span>}
{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')})
</span>
{isDynamicMain && <span className="text-xs bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 px-2 py-1 rounded w-fit">Main Topic</span>}

Loading…
Cancel
Save