feat: add inline annotations for highlights
- Add note dialog with multi-line text input - Display notes below highlighted verses with theme-aware styling - Add/edit note button in color picker menu - Update highlight API to save notes - Visual note indicator with left border matching highlight color - Support for removing notes by saving empty text 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -80,7 +80,8 @@ import {
|
||||
Storage,
|
||||
MoreVert,
|
||||
Star,
|
||||
StarBorder
|
||||
StarBorder,
|
||||
Edit
|
||||
} from '@mui/icons-material'
|
||||
|
||||
interface BibleVerse {
|
||||
@@ -242,9 +243,11 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
|
||||
open: boolean
|
||||
verse?: BibleVerse
|
||||
note: string
|
||||
highlightId?: string
|
||||
}>({
|
||||
open: false,
|
||||
note: ''
|
||||
note: '',
|
||||
highlightId: undefined
|
||||
})
|
||||
|
||||
// Copy feedback
|
||||
@@ -1243,6 +1246,58 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
|
||||
handleVerseMenuClose()
|
||||
}
|
||||
|
||||
const handleOpenNoteDialog = (verse: BibleVerse) => {
|
||||
const highlight = highlights[verse.id]
|
||||
setNoteDialog({
|
||||
open: true,
|
||||
verse,
|
||||
note: highlight?.note || '',
|
||||
highlightId: highlight?.id
|
||||
})
|
||||
setHighlightColorPickerAnchor({ element: null, verse: null })
|
||||
handleVerseMenuClose()
|
||||
}
|
||||
|
||||
const handleSaveNote = async () => {
|
||||
if (!noteDialog.verse || !user) return
|
||||
|
||||
const token = localStorage.getItem('authToken')
|
||||
if (!token) return
|
||||
|
||||
try {
|
||||
const { verse, note, highlightId } = noteDialog
|
||||
|
||||
if (highlightId) {
|
||||
// Update existing highlight's note
|
||||
const response = await fetch(`/api/highlights/${highlightId}?locale=${locale}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ note: note.trim() || null })
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setHighlights(prev => ({
|
||||
...prev,
|
||||
[verse.id]: data.highlight
|
||||
}))
|
||||
setCopyFeedback({
|
||||
open: true,
|
||||
message: note.trim() ? 'Note saved' : 'Note removed'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setNoteDialog({ open: false, note: '', highlightId: undefined })
|
||||
} catch (error) {
|
||||
console.error('Error saving note:', error)
|
||||
alert('Failed to save note')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSetFavoriteVersion = async () => {
|
||||
if (!user) {
|
||||
router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}`)}`)
|
||||
@@ -1451,6 +1506,28 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
|
||||
)}
|
||||
{verse.text}
|
||||
</Typography>
|
||||
|
||||
{/* Display highlight note if it exists */}
|
||||
{highlight?.note && (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 1,
|
||||
p: 1.5,
|
||||
backgroundColor: preferences.theme === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.03)',
|
||||
borderLeft: '3px solid',
|
||||
borderColor: getHighlightColor(highlight.color, preferences.theme),
|
||||
borderRadius: 1,
|
||||
fontSize: '0.9em',
|
||||
fontStyle: 'italic',
|
||||
color: preferences.theme === 'dark' ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)'
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{ display: 'block', fontWeight: 600, mb: 0.5, opacity: 0.7 }}>
|
||||
Note:
|
||||
</Typography>
|
||||
{highlight.note}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@@ -2301,24 +2378,77 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
|
||||
))}
|
||||
</Box>
|
||||
{highlightColorPickerAnchor.verse && highlights[highlightColorPickerAnchor.verse.id] && (
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
color="error"
|
||||
size="small"
|
||||
sx={{ mt: 2 }}
|
||||
onClick={() => {
|
||||
if (highlightColorPickerAnchor.verse) {
|
||||
handleRemoveHighlight(highlightColorPickerAnchor.verse)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove Highlight
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ mt: 2 }}
|
||||
startIcon={<Edit />}
|
||||
onClick={() => {
|
||||
if (highlightColorPickerAnchor.verse) {
|
||||
handleOpenNoteDialog(highlightColorPickerAnchor.verse)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{highlights[highlightColorPickerAnchor.verse.id].note ? 'Edit Note' : 'Add Note'}
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
color="error"
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
onClick={() => {
|
||||
if (highlightColorPickerAnchor.verse) {
|
||||
handleRemoveHighlight(highlightColorPickerAnchor.verse)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove Highlight
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Menu>
|
||||
|
||||
{/* Note Dialog */}
|
||||
<Dialog
|
||||
open={noteDialog.open}
|
||||
onClose={() => setNoteDialog({ open: false, note: '', highlightId: undefined })}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
{noteDialog.highlightId ? 'Edit Note' : 'Add Note'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
multiline
|
||||
rows={4}
|
||||
fullWidth
|
||||
placeholder="Add your thoughts, insights, or reflections about this verse..."
|
||||
value={noteDialog.note}
|
||||
onChange={(e) => setNoteDialog(prev => ({ ...prev, note: e.target.value }))}
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
{noteDialog.verse && (
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 2, display: 'block' }}>
|
||||
{currentBook?.name} {selectedChapter}:{noteDialog.verse.verseNum}
|
||||
</Typography>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setNoteDialog({ open: false, note: '', highlightId: undefined })}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSaveNote} variant="contained">
|
||||
Save Note
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Copy Feedback */}
|
||||
<Snackbar
|
||||
open={copyFeedback.open}
|
||||
|
||||
Reference in New Issue
Block a user