Files
nlcc-itinerary/server/api/notes/download/[sermonId].get.ts
Joshua Ryder 3a50cbebdd feat: Add rich text formatting to sermon notes
Implement comprehensive rich text editing capabilities using Tiptap editor with full formatting toolbar and functionality.

Features:
- Bold, italic, underline, strikethrough text formatting
- Highlight text with yellow marker
- Bullet and numbered lists
- Heading styles (H2, H3)
- Text alignment (left, center, right)
- Undo/redo support
- Clear formatting option
- Intuitive toolbar with icons and tooltips
- Responsive font size support

Technical changes:
- Added Tiptap dependencies to package.json (@tiptap/vue-3, starter-kit, extensions)
- Created RichTextEditor component with full toolbar and formatting options
- Integrated editor into sermon notes section replacing textarea
- Updated download API to convert HTML to formatted plain text
- Updated email API to handle HTML content properly
- Notes now stored as HTML with rich formatting preserved

The editor provides a professional writing experience with all standard formatting tools while maintaining automatic save functionality and seamless integration with email/download features.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 09:47:23 -05:00

138 lines
3.7 KiB
TypeScript

import { getSermonNote, getUserByUsername, getDatabase } from '~/server/utils/database'
import { getSessionUsername } from '~/server/utils/auth'
export default defineEventHandler(async (event) => {
const username = await getSessionUsername(event)
if (!username) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
const sermonId = parseInt(event.context.params?.sermonId || '')
if (!sermonId) {
throw createError({
statusCode: 400,
message: 'Invalid sermon ID'
})
}
// Get user info
const user = getUserByUsername(username)
if (!user) {
throw createError({
statusCode: 400,
message: 'User not found'
})
}
// Get sermon info
const db = getDatabase()
const sermon = db.prepare('SELECT * FROM sermons WHERE id = ?').get(sermonId) as any
if (!sermon) {
throw createError({
statusCode: 404,
message: 'Sermon not found'
})
}
// Get user's notes
const noteRecord = getSermonNote(user.id!, sermonId)
// Convert HTML to plain text for download
const htmlToText = (html: string) => {
if (!html) return 'No notes taken'
return html
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/p>/gi, '\n')
.replace(/<p[^>]*>/gi, '')
.replace(/<\/h[1-6]>/gi, '\n')
.replace(/<h[1-6][^>]*>/gi, '')
.replace(/<li[^>]*>/gi, '• ')
.replace(/<\/li>/gi, '\n')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.trim()
}
const userNotes = htmlToText(noteRecord?.notes || '')
// Format bible references
let bibleReferencesText = ''
try {
const refs = JSON.parse(sermon.bible_references)
bibleReferencesText = refs.map((ref: any) =>
`${ref.reference} (${ref.version})\n${ref.text}`
).join('\n\n')
} catch {
bibleReferencesText = sermon.bible_references
}
// Format date
const formatDate = (dateString: string) => {
const date = new Date(dateString + 'T00:00:00')
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
const dates = [sermon.date]
if (sermon.dates) {
try {
const additionalDates = JSON.parse(sermon.dates)
dates.push(...additionalDates)
} catch {}
}
const sermonDate = dates.map(formatDate).join(' - ')
// Create formatted text content
const textContent = `
================================================================================
New Life Christian Church - SERMON NOTES
================================================================================
Title: ${sermon.title}
Date: ${sermonDate}
================================================================================
BIBLE REFERENCES
================================================================================
${bibleReferencesText}
================================================================================
PERSONAL APPLIANCE
================================================================================
${sermon.personal_appliance}
================================================================================
PASTOR'S CHALLENGE
================================================================================
${sermon.pastors_challenge}
================================================================================
MY NOTES
================================================================================
${userNotes}
`.trim()
// Set headers for file download
setResponseHeaders(event, {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Disposition': `attachment; filename="${sermon.slug}-notes.txt"`
})
return textContent
})