diff --git a/src/services/highlightCreationService.ts b/src/services/highlightCreationService.ts index 21e79ae3..ebeaaa84 100644 --- a/src/services/highlightCreationService.ts +++ b/src/services/highlightCreationService.ts @@ -26,12 +26,15 @@ export async function createHighlight( // Parse article coordinate to get address pointer const addressPointer = parseArticleCoordinate(article) + // Extract context (previous and next sentences from the same paragraph) + const context = extractContext(selectedText, article.content) + // Create highlight event using the blueprint const highlightEvent = await factory.create( HighlightBlueprint, selectedText, addressPointer, - comment ? { comment } : undefined + context ? { comment, context } : comment ? { comment } : undefined ) // Update the alt tag to identify Boris as the creator @@ -66,3 +69,80 @@ function parseArticleCoordinate(article: NostrEvent): AddressPointer { } } +/** + * Extracts context for a highlight by finding the previous and next sentences + * in the same paragraph as the selected text + */ +function extractContext(selectedText: string, articleContent: string): string | undefined { + if (!selectedText || !articleContent) return undefined + + // Find the position of the selected text in the article + const selectedIndex = articleContent.indexOf(selectedText) + if (selectedIndex === -1) return undefined + + // Split content into paragraphs (by double newlines or single newlines) + const paragraphs = articleContent.split(/\n\n+/) + + // Find which paragraph contains the selected text + let currentPos = 0 + let containingParagraph: string | undefined + + for (const paragraph of paragraphs) { + const paragraphEnd = currentPos + paragraph.length + if (selectedIndex >= currentPos && selectedIndex < paragraphEnd) { + containingParagraph = paragraph + break + } + currentPos = paragraphEnd + 2 // Account for the double newline + } + + if (!containingParagraph) return undefined + + // Split paragraph into sentences (basic sentence splitting) + // This regex splits on periods, exclamation marks, or question marks followed by space or end of string + const sentences = containingParagraph.split(/([.!?]+\s+)/).filter(s => s.trim().length > 0) + + // Reconstruct sentences properly by joining sentence text with punctuation + const reconstructedSentences: string[] = [] + for (let i = 0; i < sentences.length; i++) { + if (sentences[i].match(/^[.!?]+\s*$/)) { + // This is punctuation, attach it to previous sentence + if (reconstructedSentences.length > 0) { + reconstructedSentences[reconstructedSentences.length - 1] += sentences[i] + } + } else { + reconstructedSentences.push(sentences[i]) + } + } + + // Find which sentence contains the selected text + let selectedSentenceIndex = -1 + for (let i = 0; i < reconstructedSentences.length; i++) { + if (reconstructedSentences[i].includes(selectedText)) { + selectedSentenceIndex = i + break + } + } + + if (selectedSentenceIndex === -1) return undefined + + // Build context from previous and next sentences + const contextParts: string[] = [] + + // Add previous sentence if it exists + if (selectedSentenceIndex > 0) { + contextParts.push(reconstructedSentences[selectedSentenceIndex - 1].trim()) + } + + // Add the selected sentence itself + contextParts.push(reconstructedSentences[selectedSentenceIndex].trim()) + + // Add next sentence if it exists + if (selectedSentenceIndex < reconstructedSentences.length - 1) { + contextParts.push(reconstructedSentences[selectedSentenceIndex + 1].trim()) + } + + // Only return context if we have more than just the selected sentence + return contextParts.length > 1 ? contextParts.join(' ') : undefined +} +