Browse Source

render book wikilinks

imwald
Silberengel 3 months ago
parent
commit
5a27bdd9b5
  1. 268
      src/components/Bookstr/BookstrContent.tsx
  2. 92
      src/lib/bookstr-parser.ts
  3. 165
      src/services/client.service.ts

268
src/components/Bookstr/BookstrContent.tsx

@ -3,7 +3,7 @@ import { Event } from 'nostr-tools' @@ -3,7 +3,7 @@ import { Event } from 'nostr-tools'
import { parseBookWikilink, extractBookMetadata, BookReference } from '@/lib/bookstr-parser'
import client from '@/services/client.service'
import { ExtendedKind } from '@/constants'
import { Loader2, AlertCircle, ChevronDown, ChevronUp, ExternalLink } from 'lucide-react'
import { Loader2, AlertCircle, ExternalLink } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Select,
@ -61,13 +61,9 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) { @@ -61,13 +61,9 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) {
const [sections, setSections] = useState<BookSection[]>([])
const [isLoading, setIsLoading] = useState(false) // Start as false, only set to true when actually fetching
const [error, setError] = useState<string | null>(null)
const [expandedSections, setExpandedSections] = useState<Set<number>>(new Set())
const [selectedVersions, setSelectedVersions] = useState<Map<number, string>>(new Map())
const [collapsedCards, setCollapsedCards] = useState<Set<number>>(new Set())
const [cardHeights, setCardHeights] = useState<Map<number, number>>(new Map())
// Track which sections are still loading (by reference key)
const [loadingSections, setLoadingSections] = useState<Set<string>>(new Set())
const cardRefs = useRef<Map<number, HTMLDivElement>>(new Map())
// Parse the wikilink - use a ref to store the last parsed result for comparison
const parsedRef = useRef<ReturnType<typeof parseBookWikilink> & { bookType: string } | null>(null)
@ -701,68 +697,6 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) { @@ -701,68 +697,6 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) {
}
}, [wikilink]) // Depend on wikilink directly - it's a stable string, parsed is derived from it
// Measure card heights - measure BEFORE applying collapse
useEffect(() => {
const timeoutId = setTimeout(() => {
cardRefs.current.forEach((element, index) => {
if (element) {
// IMPORTANT: Temporarily remove ALL constraints to get true height
// This must happen BEFORE any collapse is applied
const originalMaxHeight = element.style.maxHeight
const originalOverflow = element.style.overflow
const originalHeight = element.style.height
// Remove all constraints
element.style.maxHeight = 'none'
element.style.overflow = 'visible'
element.style.height = 'auto'
// Force a reflow to ensure we get the true height
void element.offsetHeight
const height = element.scrollHeight
// Restore original styles
element.style.maxHeight = originalMaxHeight
element.style.overflow = originalOverflow
element.style.height = originalHeight
// Store the TRUE height (before collapse)
setCardHeights(prev => {
const currentHeight = prev.get(index)
if (currentHeight !== height && height > 0) {
const newMap = new Map(prev)
newMap.set(index, height)
logger.debug('BookstrContent: Measured card height', {
sectionIndex: index,
height,
needsCollapse: height > 500,
wasCollapsed: collapsedCards.has(index)
})
// Only auto-collapse if height > 500px and not already manually toggled
if (height > 500) {
setCollapsedCards(prevCollapsed => {
// Only auto-collapse if user hasn't manually expanded it
if (!prevCollapsed.has(index)) {
logger.debug('BookstrContent: Auto-collapsing card', { sectionIndex: index, height })
return new Set(prevCollapsed).add(index)
}
return prevCollapsed
})
}
return newMap
}
return prev
})
}
})
}, 500) // Wait longer for content to fully render
return () => clearTimeout(timeoutId)
}, [sections, collapsedCards])
// Show loading spinner only if we're actively loading AND have no sections
// Once we have sections (even empty placeholders), show them instead
@ -815,52 +749,19 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) { @@ -815,52 +749,19 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) {
})
: section.events
const isExpanded = expandedSections.has(sectionIndex)
const hasVerses = section.originalVerses !== undefined && section.originalVerses.length > 0
const isLast = sectionIndex === sections.length - 1
const cardHeight = cardHeights.get(sectionIndex) || 0
const isCardCollapsed = collapsedCards.has(sectionIndex)
const needsCollapse = cardHeight > 500
// Only show button if card is actually tall (needs collapse) or is currently collapsed
const shouldShowButton = filteredEvents.length > 0 && (needsCollapse || isCardCollapsed)
// Check if this section is still loading
const refKey = `${section.reference.book}-${section.reference.chapter}-${section.reference.verse}`
const isSectionLoading = loadingSections.has(refKey)
// Debug logging
if (filteredEvents.length > 0) {
logger.debug('BookstrContent: Card collapse check', {
sectionIndex,
eventCount: filteredEvents.length,
cardHeight,
isCardCollapsed,
needsCollapse,
shouldShowButton
})
}
return (
<React.Fragment key={sectionIndex}>
<div
ref={(el) => {
if (el) {
cardRefs.current.set(sectionIndex, el)
} else {
cardRefs.current.delete(sectionIndex)
}
}}
className={cn(
'p-3',
!isLast && 'border-b',
needsCollapse && isCardCollapsed && 'overflow-hidden'
!isLast && 'border-b'
)}
style={needsCollapse && isCardCollapsed ? {
maxHeight: '500px',
transition: 'max-height 0.3s ease-out'
} : undefined}
>
{/* Header */}
<div className="flex items-center justify-between gap-2 mb-2">
@ -903,92 +804,14 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) { @@ -903,92 +804,14 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) {
</Button>
</div>
{/* Verses */}
{/* Verses - render all verses together, including ranges */}
{filteredEvents.length > 0 && (
<VerseContent
events={filteredEvents}
originalVerses={section.originalVerses}
/>
)}
</div>
{/* Show more/less button for tall cards - OUTSIDE collapsed div so it's always visible */}
{shouldShowButton ? (
<div className="px-3 pb-3 border-t pt-2">
<Button
variant="ghost"
size="sm"
className="h-6 text-xs w-full"
onClick={() => {
setCollapsedCards(prev => {
const newSet = new Set(prev)
if (newSet.has(sectionIndex)) {
newSet.delete(sectionIndex)
} else {
newSet.add(sectionIndex)
}
return newSet
})
}}
>
{isCardCollapsed ? (
<>
<ChevronDown className="h-3 w-3 mr-1" />
Show more
</>
) : (
<>
<ChevronUp className="h-3 w-3 mr-1" />
Show less
</>
)}
</Button>
</div>
) : null}
{/* Expand/Collapse buttons - only show if events were found */}
{hasVerses && filteredEvents.length > 0 && (
<div className="px-3 pb-3">
<Button
variant="ghost"
size="sm"
className="mt-2 h-6 text-xs"
onClick={() => {
const newExpanded = new Set(expandedSections)
if (newExpanded.has(sectionIndex)) {
newExpanded.delete(sectionIndex)
} else {
newExpanded.add(sectionIndex)
}
setExpandedSections(newExpanded)
}}
>
{isExpanded ? (
<>
<ChevronUp className="h-3 w-3 mr-1" />
Collapse chapter
</>
) : (
<>
<ChevronDown className="h-3 w-3 mr-1" />
Read full chapter
</>
)}
</Button>
</div>
)}
{/* Expanded content */}
{isExpanded && (
<div className="px-3 pb-3 mt-3 pt-3 border-t">
{/* Fetch and display full chapter/book */}
<ExpandedContent
section={section}
selectedVersion={selectedVersion}
originalChapter={section.originalChapter}
originalVerses={section.originalVerses}
/>
</div>
)}
</React.Fragment>
)
})}
@ -997,69 +820,13 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) { @@ -997,69 +820,13 @@ export function BookstrContent({ wikilink, className }: BookstrContentProps) {
)
}
interface ExpandedContentProps {
section: BookSection
selectedVersion: string
originalChapter?: number
interface VerseContentProps {
events: Event[]
originalVerses?: string
}
function ExpandedContent({ section, selectedVersion, originalChapter, originalVerses }: ExpandedContentProps) {
const [expandedEvents, setExpandedEvents] = useState<Event[]>([])
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const fetchExpanded = async () => {
setIsLoading(true)
try {
// Determine book type (default to bible)
const bookType = 'bible' // Could be extracted from section if we store it
const normalizedBook = section.reference.book.toLowerCase().replace(/\s+/g, '-')
// Fetch full chapter or book
const filters: any = {
type: bookType,
book: normalizedBook
}
if (originalChapter !== undefined) {
// Fetch full chapter
filters.chapter = originalChapter
}
// If no chapter specified, fetch entire book
if (selectedVersion) {
filters.version = selectedVersion.toLowerCase()
}
const events = await client.fetchBookstrEvents(filters)
// Sort by chapter and verse
events.sort((a, b) => {
const aMeta = extractBookMetadata(a)
const bMeta = extractBookMetadata(b)
const aChapter = parseInt(aMeta.chapter || '0')
const bChapter = parseInt(bMeta.chapter || '0')
if (aChapter !== bChapter) return aChapter - bChapter
const aVerse = parseInt(aMeta.verse || '0')
const bVerse = parseInt(bMeta.verse || '0')
return aVerse - bVerse
})
setExpandedEvents(events)
} catch (err) {
logger.error('Error fetching expanded content', { error: err })
} finally {
setIsLoading(false)
}
}
fetchExpanded()
}, [section, selectedVersion, originalChapter])
if (isLoading) {
return <div className="text-xs text-muted-foreground">Loading...</div>
}
function VerseContent({ events, originalVerses }: VerseContentProps) {
const [parsedContents, setParsedContents] = useState<Map<string, string>>(new Map())
// Parse original verses to determine which ones should have a border
const originalVerseNumbers = new Set<number>()
@ -1067,6 +834,7 @@ function ExpandedContent({ section, selectedVersion, originalChapter, originalVe @@ -1067,6 +834,7 @@ function ExpandedContent({ section, selectedVersion, originalChapter, originalVe
const verseSpecs = originalVerses.split(',').map(v => v.trim()).filter(v => v)
for (const spec of verseSpecs) {
if (spec.includes('-')) {
// Expand range like "16-18" into 16, 17, 18
const [startStr, endStr] = spec.split('-').map(v => v.trim())
const start = parseInt(startStr)
const end = parseInt(endStr)
@ -1084,22 +852,6 @@ function ExpandedContent({ section, selectedVersion, originalChapter, originalVe @@ -1084,22 +852,6 @@ function ExpandedContent({ section, selectedVersion, originalChapter, originalVe
}
}
return (
<VerseContent
events={expandedEvents}
originalVerseNumbers={originalVerseNumbers}
/>
)
}
interface VerseContentProps {
events: Event[]
originalVerseNumbers?: Set<number>
}
function VerseContent({ events, originalVerseNumbers }: VerseContentProps) {
const [parsedContents, setParsedContents] = useState<Map<string, string>>(new Map())
useEffect(() => {
const parseAll = async () => {
const newParsed = new Map<string, string>()
@ -1132,7 +884,7 @@ function VerseContent({ events, originalVerseNumbers }: VerseContentProps) { @@ -1132,7 +884,7 @@ function VerseContent({ events, originalVerseNumbers }: VerseContentProps) {
const metadata = extractBookMetadata(event)
const verseNum = metadata.verse
const verseNumInt = verseNum ? parseInt(verseNum) : null
const isOriginalVerse = originalVerseNumbers && verseNumInt !== null && originalVerseNumbers.has(verseNumInt)
const isOriginalVerse = originalVerseNumbers.size > 0 && verseNumInt !== null && originalVerseNumbers.has(verseNumInt)
const content = parsedContents.get(event.id) || event.content
return (

92
src/lib/bookstr-parser.ts

@ -80,50 +80,60 @@ export function parseBookWikilink(wikilink: string): { references: BookReference @@ -80,50 +80,60 @@ export function parseBookWikilink(wikilink: string): { references: BookReference
versionPart = pipeParts[pipeParts.length - 1].trim()
}
// Parse title, chapter, section from titlePart
const chapterSectionMatch = titlePart.match(/^(.+?)\s+(\d+|[a-zA-Z0-9_-]+)(?::(.+))?$/)
let title = ''
let chapter: number | undefined
let verse: string | undefined
if (chapterSectionMatch) {
title = normalizeNip54(chapterSectionMatch[1].trim())
const chapterStr = chapterSectionMatch[2]
chapter = /^\d+$/.test(chapterStr) ? parseInt(chapterStr, 10) : undefined
if (chapterSectionMatch[3]) {
verse = chapterSectionMatch[3].trim()
// Parse versions first (needed for references)
const versions = versionPart ? versionPart.split(/\s+/).map(v => normalizeNip54(v).toUpperCase()).filter(v => v) : undefined
// Parse multiple references from titlePart (e.g., "romans 1:16-17, psalms 23:1")
// Split by comma to handle multiple book references
const referenceStrings = titlePart.split(',').map(s => s.trim()).filter(s => s)
const references: BookReference[] = []
for (const refString of referenceStrings) {
// Parse each reference: "book chapter:verse" or "book chapter" or "book"
const chapterSectionMatch = refString.match(/^(.+?)\s+(\d+|[a-zA-Z0-9_-]+)(?::(.+))?$/)
let title = ''
let chapter: number | undefined
let verse: string | undefined
if (chapterSectionMatch) {
title = normalizeNip54(chapterSectionMatch[1].trim())
const chapterStr = chapterSectionMatch[2]
chapter = /^\d+$/.test(chapterStr) ? parseInt(chapterStr, 10) : undefined
if (chapterSectionMatch[3]) {
verse = chapterSectionMatch[3].trim()
}
} else {
title = normalizeNip54(refString)
}
} else {
title = normalizeNip54(titlePart)
}
// Parse versions
const versions = versionPart ? versionPart.split(/\s+/).map(v => normalizeNip54(v).toUpperCase()).filter(v => v) : undefined
// Create reference
const reference: BookReference = {
book: title
}
if (chapter !== undefined) {
reference.chapter = chapter
}
if (verse) {
reference.verse = verse
}
if (versions && versions.length > 0) {
reference.version = versions[0] // Use first version for backward compatibility
}
references.push(reference)
}
// Use collection as bookType (e.g., "bible", "quran", "torah")
// If no collection, default to "bible"
const inferredBookType = collection || 'bible'
// Create reference
const reference: BookReference = {
book: title
}
if (chapter !== undefined) {
reference.chapter = chapter
}
if (verse) {
reference.verse = verse
}
if (versions && versions.length > 0) {
reference.version = versions[0] // Use first version for backward compatibility
}
return { references: [reference], versions, bookType: inferredBookType }
return { references, versions, bookType: inferredBookType }
}
/**
* Extract book metadata from event tags
* Tags: C (collection), T (title), c (chapter), s (section), v (version)
*/
export function extractBookMetadata(event: { tags: string[][] }): {
type?: string
@ -136,19 +146,23 @@ export function extractBookMetadata(event: { tags: string[][] }): { @@ -136,19 +146,23 @@ export function extractBookMetadata(event: { tags: string[][] }): {
for (const [tag, value] of event.tags) {
switch (tag) {
case 'type':
case 'C': // Collection
metadata.type = value
break
case 'book':
case 'T': // Title (book name)
metadata.book = value
break
case 'chapter':
case 'c': // Chapter
metadata.chapter = value
break
case 'verse':
metadata.verse = value
case 's': // Section
// Section might be used for verse or other metadata
// If we don't have verse yet, use section as verse
if (!metadata.verse) {
metadata.verse = value
}
break
case 'version':
case 'v': // Version
metadata.version = value
break
}

165
src/services/client.service.ts

@ -2474,13 +2474,25 @@ class ClientService extends EventTarget { @@ -2474,13 +2474,25 @@ class ClientService extends EventTarget {
let events: NEvent[] = []
try {
// Query ONLY 30040s (publications/indexes) by pubkey and kind
// Query ONLY 30040s (publications/indexes) by pubkey and kind with precise tag filters
const publicationFilter: Filter = {
authors: [publicationPubkey],
kinds: [ExtendedKind.PUBLICATION],
limit: 500
}
// Add precise tag filters for collection, title, and chapter
if (filters.type) {
publicationFilter['#C'] = [filters.type.toLowerCase()]
}
if (filters.book) {
const normalizedBook = filters.book.toLowerCase().replace(/\s+/g, '-')
publicationFilter['#T'] = [normalizedBook]
}
if (filters.chapter !== undefined) {
publicationFilter['#c'] = [filters.chapter.toString()]
}
const allPublications = await this.fetchEvents(prioritizedFallbackRelaysWithCitadel, publicationFilter, {
eoseTimeout: 5000,
globalTimeout: 8000
@ -2536,6 +2548,29 @@ class ClientService extends EventTarget { @@ -2536,6 +2548,29 @@ class ClientService extends EventTarget {
if (d) {
aTagFilter['#d'] = [d]
}
// Add all precise tag filters: C (collection), T (title), c (chapter), s (section/verse), v (version)
if (filters.type) {
aTagFilter['#C'] = [filters.type.toLowerCase()]
}
if (filters.book) {
const normalizedBook = filters.book.toLowerCase().replace(/\s+/g, '-')
aTagFilter['#T'] = [normalizedBook]
}
if (filters.chapter !== undefined) {
aTagFilter['#c'] = [filters.chapter.toString()]
}
if (filters.verse) {
// Section tag (s) is used for verse
// For verse ranges, we'll need to expand and query each verse
// For now, just add the first verse if it's a single verse
const verseParts = filters.verse.split(/[,\s-]+/).map(v => v.trim()).filter(v => v)
if (verseParts.length === 1 && !verseParts[0].includes('-')) {
aTagFilter['#s'] = [verseParts[0]]
}
}
if (filters.version) {
aTagFilter['#v'] = [filters.version.toLowerCase()]
}
try {
const aTagEvents = await this.fetchEvents(prioritizedFallbackRelaysWithCitadel, aTagFilter, {
@ -2612,110 +2647,64 @@ class ClientService extends EventTarget { @@ -2612,110 +2647,64 @@ class ClientService extends EventTarget {
try {
const bookstrPublisherPubkey = '3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690'
// Query ONLY 30040s (publications/indexes) with just type and kind filters
// Query BOTH 30040s (publications/indexes) AND 30041s (content) together
// Only use #T (title) and #c (chapter) in relay filter - filter #C, #s, #v client-side
// This matches wikistr's approach and avoids relay compatibility issues
const publicationFilter: Filter = {
kinds: [ExtendedKind.PUBLICATION],
kinds: [ExtendedKind.PUBLICATION, ExtendedKind.PUBLICATION_CONTENT],
authors: [bookstrPublisherPubkey],
limit: 500
}
// Only add #type filter if we have a type
if (filters.type) {
publicationFilter['#type'] = [filters.type.toLowerCase()]
// Only add #T (title) and #c (chapter) filters - filter rest client-side
if (filters.book) {
// Normalize book name: lowercase, replace spaces with hyphens (NIP-54 style)
// The parser already normalized it, but ensure consistency
const normalizedBook = filters.book.toLowerCase().replace(/\s+/g, '-')
publicationFilter['#T'] = [normalizedBook]
}
if (filters.chapter !== undefined) {
publicationFilter['#c'] = [filters.chapter.toString()]
}
// Don't include #C, #s, or #v in relay filter - filter client-side instead
const publisherPublications = await this.fetchEvents(prioritizedFallbackRelays, publicationFilter, {
eoseTimeout: 5000,
globalTimeout: 8000
})
logger.info('fetchBookstrEventsFromRelays: Fetched 30040 publications', {
logger.info('fetchBookstrEventsFromRelays: Fetched events', {
count: publisherPublications.length,
filters: JSON.stringify(filters)
})
// Filter 30040s client-side to find matching book/chapter
// Note: Don't filter by verse for 30040s - verses are in 30041s
const matchingPublications = publisherPublications.filter(pub => {
return this.eventMatchesBookstrFilters(pub, filters)
// Filter ALL events (both 30040 and 30041) client-side
// This matches wikistr's approach - filter #C, #s, #v client-side
const matchingEvents = publisherPublications.filter(event => {
return this.eventMatchesBookstrFilters(event, filters)
})
logger.info('fetchBookstrEventsFromRelays: Filtered 30040 publications', {
logger.info('fetchBookstrEventsFromRelays: Filtered events', {
total: publisherPublications.length,
matching: matchingPublications.length,
matching: matchingEvents.length,
filters: JSON.stringify(filters)
})
// For each matching 30040, fetch its a-tagged 30041 events (content)
for (const publication of matchingPublications) {
const aTags = publication.tags
.filter(tag => tag[0] === 'a' && tag[1])
.map(tag => tag[1])
logger.info('fetchBookstrEventsFromRelays: Fetching 30041s from matching publication', {
publicationId: publication.id.substring(0, 8),
aTagCount: aTags.length,
filters: JSON.stringify(filters)
})
// Fetch all a-tagged 30041 events in parallel
const aTagPromises = aTags.map(async (aTag) => {
const parts = aTag.split(':')
if (parts.length < 2) return null
const kind = parseInt(parts[0])
const pubkey = parts[1]
const d = parts[2] || ''
// Only fetch 30041 events (content events)
if (kind !== ExtendedKind.PUBLICATION_CONTENT) {
return null
}
const aTagFilter: Filter = {
authors: [pubkey],
kinds: [ExtendedKind.PUBLICATION_CONTENT],
limit: 1
}
if (d) {
aTagFilter['#d'] = [d]
}
try {
const aTagEvents = await this.fetchEvents(prioritizedFallbackRelays, aTagFilter, {
eoseTimeout: 3000,
globalTimeout: 5000
})
// Filter 30041s client-side by book, type, version, chapter, verse
return aTagEvents.filter(event => {
return this.eventMatchesBookstrFilters(event, filters)
})
} catch (err) {
logger.debug('fetchBookstrEventsFromRelays: Error fetching a-tag event', {
aTag,
error: err
})
return []
}
})
// Separate 30040s (publications) and 30041s (content)
// We queried for both kinds, so we get content events directly
const contentEvents = matchingEvents.filter(e => e.kind === ExtendedKind.PUBLICATION_CONTENT)
const aTagResults = await Promise.all(aTagPromises)
const aTagEvents = aTagResults.flat().filter((e): e is NEvent => e !== null)
events.push(...contentEvents)
logger.info('fetchBookstrEventsFromRelays: Fetched 30041s from publication', {
publicationId: publication.id.substring(0, 8),
fetched: aTagEvents.length,
totalSoFar: events.length + aTagEvents.length
})
events.push(...aTagEvents)
}
// Note: We could also process 30040 publications to fetch their a-tagged 30041s,
// but since we already queried for 30041s directly, we should have them.
// If we need more, we can fetch from 30040 a-tags, but for now this is simpler.
if (events.length > 0) {
logger.info('fetchBookstrEventsFromRelays: Successfully fetched content events', {
publicationCount: matchingPublications.length,
eventCount: events.length,
totalQueried: publisherPublications.length,
matchingAfterFilter: matchingEvents.length,
contentEvents: events.length,
filters: JSON.stringify(filters)
})
return events
@ -2865,8 +2854,9 @@ class ClientService extends EventTarget { @@ -2865,8 +2854,9 @@ class ClientService extends EventTarget {
if (filters.book) {
const normalizedBook = filters.book.toLowerCase().replace(/\s+/g, '-')
// Get ALL book tags from the event (events can have multiple book tags)
// Check 'T' (title/book) tags
const eventBookTags = event.tags
.filter(tag => tag[0] === 'book' && tag[1])
.filter(tag => tag[0] === 'T' && tag[1])
.map(tag => tag[1].toLowerCase())
// Check if any of the book tags match
@ -3393,6 +3383,7 @@ class ClientService extends EventTarget { @@ -3393,6 +3383,7 @@ class ClientService extends EventTarget {
/**
* Extract book metadata from event tags (helper method)
* Tags: C (collection), T (title), c (chapter), s (section), v (version)
*/
private extractBookMetadataFromEvent(event: NEvent): {
type?: string
@ -3404,19 +3395,23 @@ class ClientService extends EventTarget { @@ -3404,19 +3395,23 @@ class ClientService extends EventTarget {
const metadata: any = {}
for (const [tag, value] of event.tags) {
switch (tag) {
case 'type':
case 'C': // Collection
metadata.type = value
break
case 'book':
case 'T': // Title (book name)
metadata.book = value
break
case 'chapter':
case 'c': // Chapter
metadata.chapter = value
break
case 'verse':
metadata.verse = value
case 's': // Section
// Section might be used for verse or other metadata
// If we don't have verse yet, use section as verse
if (!metadata.verse) {
metadata.verse = value
}
break
case 'version':
case 'v': // Version
metadata.version = value
break
}

Loading…
Cancel
Save