Browse Source

correctly sort the events

imwald
Silberengel 5 months ago
parent
commit
111c98c2d5
  1. 162
      src/pages/primary/DiscussionsPage/index.tsx

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

@ -22,11 +22,116 @@ import storage from '@/services/local-storage.service' @@ -22,11 +22,116 @@ import storage from '@/services/local-storage.service'
import { useSecondaryPage } from '@/PageManager'
import { toNote } from '@/lib/link'
import { kinds } from 'nostr-tools'
import {
analyzeThreadTopics,
getCategorizedTopic,
getDynamicSubtopics
} from '@/lib/discussion-topics'
// Normalize subtopic hashtags using linguistic rules to group similar variations
function normalizeSubtopic(tag: string): string {
let normalized = tag.toLowerCase().trim()
// Don't normalize very short words (2 chars or less)
if (normalized.length <= 2) {
return normalized
}
// Handle common suffixes to find root forms
// Remove trailing 's' for plurals (but not if word ends in 'ss')
if (normalized.endsWith('s') && !normalized.endsWith('ss')) {
// Special cases for words ending in 'ies' -> 'y' (e.g., stories -> story)
if (normalized.endsWith('ies') && normalized.length > 4) {
return normalized.slice(0, -3) + 'y'
}
// Special cases for words ending in 'es' (e.g., churches -> church, but not always)
if (normalized.endsWith('ches') || normalized.endsWith('shes') || normalized.endsWith('xes') ||
normalized.endsWith('zes') || normalized.endsWith('ses')) {
return normalized.slice(0, -2)
}
// Regular plural: just remove 's'
return normalized.slice(0, -1)
}
// Handle -ing forms (e.g., reading -> read, cooking -> cook)
if (normalized.endsWith('ing') && normalized.length > 5) {
const root = normalized.slice(0, -3)
// Handle doubled consonants (e.g., running -> run, shopping -> shop)
if (root.length >= 2 && root[root.length - 1] === root[root.length - 2]) {
return root.slice(0, -1)
}
return root
}
// Handle -ed forms (e.g., deleted -> delete)
if (normalized.endsWith('ed') && normalized.length > 4) {
const root = normalized.slice(0, -2)
// Handle doubled consonants
if (root.length >= 2 && root[root.length - 1] === root[root.length - 2]) {
return root.slice(0, -1)
}
return root
}
// Handle -er forms (e.g., developer -> develop, but not 'user' -> 'us')
if (normalized.endsWith('er') && normalized.length > 4 && !normalized.endsWith('eer')) {
return normalized.slice(0, -2)
}
// Handle -ly adverbs (e.g., quickly -> quick)
if (normalized.endsWith('ly') && normalized.length > 4) {
return normalized.slice(0, -2)
}
// Handle -y to -ies (e.g., philosophy/philosophical, economy/economics)
// Already handled by the 'ies' -> 'y' rule above
// Handle -ism, -ist, -ian variations (e.g., Buddhism/Buddhist, Christian/Christianity)
if (normalized.endsWith('ism') && normalized.length > 5) {
return normalized.slice(0, -3)
}
if (normalized.endsWith('ist') && normalized.length > 5) {
return normalized.slice(0, -3)
}
if (normalized.endsWith('ity') && normalized.length > 5) {
return normalized.slice(0, -3)
}
if (normalized.endsWith('ian') && normalized.length > 5) {
return normalized.slice(0, -3)
}
if (normalized.endsWith('ians') && normalized.length > 6) {
return normalized.slice(0, -4)
}
return normalized
}
// Function to determine topic based on actual t-tags and hashtags
function getTopicFromTags(allTopics: string[], availableTopicIds: string[]): string {
// Normalize topics to lowercase for case-insensitive matching
const normalizedTopics = allTopics.map(t => t.toLowerCase())
const normalizedTopicIds = availableTopicIds.map(t => t.toLowerCase())
// Check if any of the event's topics match the available topic IDs (case-insensitive)
for (let i = 0; i < normalizedTopics.length; i++) {
const index = normalizedTopicIds.indexOf(normalizedTopics[i])
if (index !== -1) {
return availableTopicIds[index] // Return the original case from availableTopicIds
}
}
// If no specific topic matches, categorize as 'general'
return 'general'
}
// Function to get dynamic subtopics from event topics
function getSubtopicsFromTopics(topics: string[], limit: number = 3): string[] {
// Get the main topic IDs from DISCUSSION_TOPICS
const mainTopicIds = DISCUSSION_TOPICS.map(topic => topic.id)
// Filter out main topic IDs and get unique subtopics
const subtopics = topics.filter(topic => !mainTopicIds.includes(topic))
const uniqueSubtopics = [...new Set(subtopics)]
// Return the most common subtopics, limited by the limit
return uniqueSubtopics.slice(0, limit)
}
// Simple event map type
type EventMapEntry = {
@ -57,8 +162,7 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -57,8 +162,7 @@ const DiscussionsPage = forwardRef((_, ref) => {
const [groupedEvents, setGroupedEvents] = useState<Record<string, NostrEvent[]>>({})
const [searchQuery, setSearchQuery] = useState('')
// Topic analysis for dynamic subtopics
const [topicAnalysis, setTopicAnalysis] = useState<ReturnType<typeof analyzeThreadTopics>>(new Map())
// Available subtopics for the selected topic
const [availableSubtopics, setAvailableSubtopics] = useState<string[]>([])
// State for all available relays
@ -175,11 +279,19 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -175,11 +279,19 @@ const DiscussionsPage = forwardRef((_, ref) => {
return
}
// Extract topics
const tTags = event.tags.filter(tag => tag[0] === 't' && tag[1]).map(tag => tag[1])
const hashtags = (event.content.match(/#\w+/g) || []).map(tag => tag.slice(1))
// Extract topics - normalize subtopics but keep originals for topic detection
const tTagsRaw = event.tags.filter(tag => tag[0] === 't' && tag[1]).map(tag => tag[1].toLowerCase())
// Match hashtags with letters, numbers, hyphens, and underscores
const hashtagsRaw = (event.content.match(/#[\w-]+/g) || []).map(tag => tag.slice(1).toLowerCase())
const allTopicsRaw = [...new Set([...tTagsRaw, ...hashtagsRaw])]
// Determine the main topic from raw tags
const categorizedTopic = getTopicFromTags(allTopicsRaw, availableTopicIds)
// Normalize subtopics for grouping (but not main topic IDs)
const tTags = tTagsRaw.map(tag => normalizeSubtopic(tag))
const hashtags = hashtagsRaw.map(tag => normalizeSubtopic(tag))
const allTopics = [...new Set([...tTags, ...hashtags])]
const categorizedTopic = getCategorizedTopic(event, availableTopicIds)
finalEventMap.set(eventId, {
event,
@ -332,21 +444,14 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -332,21 +444,14 @@ const DiscussionsPage = forwardRef((_, ref) => {
filterAndSortEvents()
}, [filterAndSortEvents])
// Analyze topics whenever event map changes
useEffect(() => {
const events = Array.from(eventMap.values()).map(entry => entry.event)
if (events.length > 0) {
const analysis = analyzeThreadTopics(events, availableTopicIds)
setTopicAnalysis(analysis)
} else {
setTopicAnalysis(new Map())
}
}, [eventMap, availableTopicIds])
// Update available subtopics when topic analysis or selected topic changes
useEffect(() => {
if (selectedTopic && selectedTopic !== 'all') {
const subtopics = getDynamicSubtopics(topicAnalysis.get(selectedTopic), 3)
// Get all topics from events in this topic
const topicEvents = Array.from(eventMap.values()).filter(entry => entry.categorizedTopic === selectedTopic)
const allTopics = topicEvents.flatMap(entry => entry.allTopics)
const subtopics = getSubtopicsFromTopics(allTopics, 3)
// Special case: Always include 'readings' as a subtopic for 'literature'
if (selectedTopic === 'literature' && !subtopics.includes('readings')) {
@ -357,7 +462,7 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -357,7 +462,7 @@ const DiscussionsPage = forwardRef((_, ref) => {
} else {
setAvailableSubtopics([])
}
}, [topicAnalysis, selectedTopic])
}, [eventMap, selectedTopic])
const handleCreateThread = () => {
setShowCreateThread(true)
@ -608,7 +713,12 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -608,7 +713,12 @@ const DiscussionsPage = forwardRef((_, ref) => {
<div className="space-y-6">
{Object.entries(groupedEvents).map(([topicId, topicEvents]) => {
const topicInfo = DISCUSSION_TOPICS.find(t => t.id === topicId)
if (!topicInfo || topicEvents.length === 0) return null
// Skip if no events, but don't skip if topicInfo is missing (shouldn't happen with proper data)
if (topicEvents.length === 0) return null
if (!topicInfo) {
console.warn(`Topic info not found for: ${topicId}`)
return null
}
return (
<div key={topicId} className="space-y-3">
@ -624,7 +734,7 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -624,7 +734,7 @@ const DiscussionsPage = forwardRef((_, ref) => {
const entry = eventMap.get(event.id)
const threadSubtopics = entry?.categorizedTopic === 'literature'
? ['readings']
: getDynamicSubtopics(topicAnalysis.get(entry?.categorizedTopic || 'general'), 3)
: getSubtopicsFromTopics(entry?.allTopics || [], 3)
return (
<ThreadCard
@ -648,7 +758,7 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -648,7 +758,7 @@ const DiscussionsPage = forwardRef((_, ref) => {
const entry = eventMap.get(event.id)
const threadSubtopics = entry?.categorizedTopic === 'literature'
? ['readings']
: getDynamicSubtopics(topicAnalysis.get(entry?.categorizedTopic || 'general'), 3)
: getSubtopicsFromTopics(entry?.allTopics || [], 3)
return (
<ThreadCard

Loading…
Cancel
Save