Includes all Phase 1 features: - Search-first navigation with auto-complete - Responsive reading interface (desktop/tablet/mobile) - 4 customization presets + full fine-tuning controls - Layered details panel with notes, bookmarks, highlights - Smart offline caching with IndexedDB and auto-sync - Full accessibility (WCAG 2.1 AA) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
884 lines
21 KiB
Markdown
884 lines
21 KiB
Markdown
# Export Functionality - Implementation Plan
|
|
|
|
## 📋 Overview
|
|
|
|
Implement comprehensive export capabilities allowing users to download Bible passages, study notes, highlights, and annotations in multiple formats for offline study, sharing, and printing.
|
|
|
|
**Status:** Planning Phase
|
|
**Priority:** 🔴 High
|
|
**Estimated Time:** 2-3 weeks (80-120 hours)
|
|
**Target Completion:** TBD
|
|
|
|
---
|
|
|
|
## 🎯 Goals & Objectives
|
|
|
|
### Primary Goals
|
|
1. Export Bible passages in multiple formats (PDF, DOCX, Markdown, TXT)
|
|
2. Include user highlights and notes in exports
|
|
3. Provide print-optimized layouts
|
|
4. Support batch exports (multiple chapters/books)
|
|
5. Enable customization of export appearance
|
|
|
|
### User Value Proposition
|
|
- **For students**: Create study materials for offline use
|
|
- **For teachers**: Prepare handouts and lesson materials
|
|
- **For preachers**: Print sermon references
|
|
- **For small groups**: Share study guides
|
|
- **For archiving**: Backup personal annotations
|
|
|
|
---
|
|
|
|
## ✨ Feature Specifications
|
|
|
|
### 1. Export Formats
|
|
|
|
```typescript
|
|
type ExportFormat = 'pdf' | 'docx' | 'markdown' | 'txt' | 'epub' | 'json'
|
|
|
|
interface ExportConfig {
|
|
// Format
|
|
format: ExportFormat
|
|
|
|
// Content selection
|
|
book: string
|
|
startChapter: number
|
|
endChapter: number
|
|
startVerse?: number
|
|
endVerse?: number
|
|
includeHeadings: boolean
|
|
includeVerseNumbers: boolean
|
|
includeChapterNumbers: boolean
|
|
|
|
// User content
|
|
includeHighlights: boolean
|
|
includeNotes: boolean
|
|
includeBookmarks: boolean
|
|
notesPosition: 'inline' | 'footnotes' | 'endnotes' | 'separate'
|
|
|
|
// Appearance
|
|
fontSize: number // 10-16pt
|
|
fontFamily: string
|
|
lineHeight: number // 1.0-2.0
|
|
pageSize: 'A4' | 'Letter' | 'Legal'
|
|
margins: { top: number; right: number; bottom: number; left: number }
|
|
columns: 1 | 2
|
|
|
|
// Header/Footer
|
|
includeHeader: boolean
|
|
headerText: string
|
|
includeFooter: boolean
|
|
footerText: string
|
|
includePageNumbers: boolean
|
|
|
|
// Metadata
|
|
includeTableOfContents: boolean
|
|
includeCoverPage: boolean
|
|
coverTitle: string
|
|
coverSubtitle: string
|
|
author: string
|
|
date: string
|
|
|
|
// Advanced
|
|
versionComparison: string[] // Multiple version IDs for parallel
|
|
colorMode: 'color' | 'grayscale' | 'print'
|
|
}
|
|
```
|
|
|
|
### 2. Export Dialog UI
|
|
|
|
```typescript
|
|
const ExportDialog: React.FC<{
|
|
open: boolean
|
|
onClose: () => void
|
|
defaultSelection?: {
|
|
book: string
|
|
chapter: number
|
|
}
|
|
}> = ({ open, onClose, defaultSelection }) => {
|
|
const [config, setConfig] = useState<ExportConfig>(getDefaultConfig())
|
|
const [estimatedSize, setEstimatedSize] = useState<string>('0 KB')
|
|
const [exporting, setExporting] = useState(false)
|
|
const [progress, setProgress] = useState(0)
|
|
|
|
// Calculate estimated file size
|
|
useEffect(() => {
|
|
const estimate = calculateEstimatedSize(config)
|
|
setEstimatedSize(estimate)
|
|
}, [config])
|
|
|
|
const handleExport = async () => {
|
|
setExporting(true)
|
|
setProgress(0)
|
|
|
|
try {
|
|
const result = await exportContent(config, (percent) => {
|
|
setProgress(percent)
|
|
})
|
|
|
|
// Trigger download
|
|
downloadFile(result.blob, result.filename)
|
|
|
|
onClose()
|
|
} catch (error) {
|
|
console.error('Export failed:', error)
|
|
// Show error to user
|
|
} finally {
|
|
setExporting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
|
<DialogTitle>
|
|
Export Bible Content
|
|
</DialogTitle>
|
|
|
|
<DialogContent>
|
|
<Box sx={{ pt: 2 }}>
|
|
<Tabs value={activeTab} onChange={setActiveTab}>
|
|
<Tab label="Content" />
|
|
<Tab label="Format" />
|
|
<Tab label="Layout" />
|
|
<Tab label="Advanced" />
|
|
</Tabs>
|
|
|
|
<Box sx={{ mt: 3 }}>
|
|
{activeTab === 0 && <ContentSelectionTab config={config} onChange={setConfig} />}
|
|
{activeTab === 1 && <FormatOptionsTab config={config} onChange={setConfig} />}
|
|
{activeTab === 2 && <LayoutSettingsTab config={config} onChange={setConfig} />}
|
|
{activeTab === 3 && <AdvancedOptionsTab config={config} onChange={setConfig} />}
|
|
</Box>
|
|
|
|
{/* Preview */}
|
|
<Box sx={{ mt: 3, p: 2, bgcolor: 'grey.100', borderRadius: 1 }}>
|
|
<Typography variant="caption" color="text.secondary">
|
|
Estimated file size: {estimatedSize}
|
|
</Typography>
|
|
</Box>
|
|
|
|
{/* Progress */}
|
|
{exporting && (
|
|
<Box sx={{ mt: 2 }}>
|
|
<LinearProgress variant="determinate" value={progress} />
|
|
<Typography variant="caption" textAlign="center" display="block" mt={1}>
|
|
Generating {config.format.toUpperCase()}... {progress}%
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</DialogContent>
|
|
|
|
<DialogActions>
|
|
<Button onClick={onClose}>Cancel</Button>
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleExport}
|
|
disabled={exporting}
|
|
startIcon={<DownloadIcon />}
|
|
>
|
|
Export
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
)
|
|
}
|
|
```
|
|
|
|
### 3. PDF Export (using jsPDF)
|
|
|
|
```typescript
|
|
import jsPDF from 'jspdf'
|
|
import 'jspdf-autotable'
|
|
|
|
export const generatePDF = async (
|
|
config: ExportConfig,
|
|
onProgress?: (percent: number) => void
|
|
): Promise<Blob> => {
|
|
const doc = new jsPDF({
|
|
orientation: config.columns === 2 ? 'landscape' : 'portrait',
|
|
unit: 'mm',
|
|
format: config.pageSize.toLowerCase()
|
|
})
|
|
|
|
// Set font
|
|
doc.setFont(config.fontFamily)
|
|
doc.setFontSize(config.fontSize)
|
|
|
|
let currentPage = 1
|
|
|
|
// Add cover page
|
|
if (config.includeCoverPage) {
|
|
addCoverPage(doc, config)
|
|
doc.addPage()
|
|
currentPage++
|
|
}
|
|
|
|
// Add table of contents
|
|
if (config.includeTableOfContents) {
|
|
const toc = await generateTableOfContents(config)
|
|
addTableOfContents(doc, toc)
|
|
doc.addPage()
|
|
currentPage++
|
|
}
|
|
|
|
// Fetch Bible content
|
|
const verses = await fetchVerses(
|
|
config.book,
|
|
config.startChapter,
|
|
config.endChapter,
|
|
config.startVerse,
|
|
config.endVerse
|
|
)
|
|
|
|
const totalVerses = verses.length
|
|
let processedVerses = 0
|
|
|
|
// Group by chapters
|
|
const chapters = groupByChapters(verses)
|
|
|
|
for (const [chapterNum, chapterVerses] of Object.entries(chapters)) {
|
|
// Chapter heading
|
|
if (config.includeChapterNumbers) {
|
|
doc.setFontSize(config.fontSize + 4)
|
|
doc.setFont(config.fontFamily, 'bold')
|
|
doc.text(`Chapter ${chapterNum}`, 20, doc.internal.pageSize.height - 20)
|
|
doc.setFont(config.fontFamily, 'normal')
|
|
doc.setFontSize(config.fontSize)
|
|
}
|
|
|
|
// Add verses
|
|
for (const verse of chapterVerses) {
|
|
const verseText = formatVerseForPDF(verse, config)
|
|
|
|
// Check if we need a new page
|
|
if (doc.internal.pageSize.height - 40 < 20) {
|
|
doc.addPage()
|
|
currentPage++
|
|
}
|
|
|
|
doc.text(verseText, 20, doc.internal.pageSize.height - 40)
|
|
|
|
// Add highlights if enabled
|
|
if (config.includeHighlights && verse.highlights) {
|
|
addHighlightsToPDF(doc, verse.highlights)
|
|
}
|
|
|
|
// Add notes
|
|
if (config.includeNotes && verse.notes) {
|
|
if (config.notesPosition === 'inline') {
|
|
addInlineNote(doc, verse.notes)
|
|
} else if (config.notesPosition === 'footnotes') {
|
|
addFootnote(doc, verse.notes, currentPage)
|
|
}
|
|
}
|
|
|
|
processedVerses++
|
|
if (onProgress) {
|
|
onProgress(Math.round((processedVerses / totalVerses) * 100))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add header/footer to all pages
|
|
if (config.includeHeader || config.includeFooter) {
|
|
const totalPages = doc.getNumberOfPages()
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
doc.setPage(i)
|
|
|
|
if (config.includeHeader) {
|
|
doc.setFontSize(10)
|
|
doc.text(config.headerText, 20, 10)
|
|
}
|
|
|
|
if (config.includeFooter) {
|
|
doc.setFontSize(10)
|
|
const footerText = config.includePageNumbers
|
|
? `${config.footerText} | Page ${i} of ${totalPages}`
|
|
: config.footerText
|
|
doc.text(footerText, 20, doc.internal.pageSize.height - 10)
|
|
}
|
|
}
|
|
}
|
|
|
|
return doc.output('blob')
|
|
}
|
|
|
|
const formatVerseForPDF = (verse: BibleVerse, config: ExportConfig): string => {
|
|
let text = ''
|
|
|
|
if (config.includeVerseNumbers) {
|
|
text += `${verse.verseNum}. `
|
|
}
|
|
|
|
text += verse.text
|
|
|
|
return text
|
|
}
|
|
|
|
const addCoverPage = (doc: jsPDF, config: ExportConfig): void => {
|
|
const pageWidth = doc.internal.pageSize.width
|
|
const pageHeight = doc.internal.pageSize.height
|
|
|
|
// Title
|
|
doc.setFontSize(24)
|
|
doc.setFont(config.fontFamily, 'bold')
|
|
doc.text(config.coverTitle, pageWidth / 2, pageHeight / 2 - 20, { align: 'center' })
|
|
|
|
// Subtitle
|
|
doc.setFontSize(16)
|
|
doc.setFont(config.fontFamily, 'normal')
|
|
doc.text(config.coverSubtitle, pageWidth / 2, pageHeight / 2, { align: 'center' })
|
|
|
|
// Author & Date
|
|
doc.setFontSize(12)
|
|
doc.text(config.author, pageWidth / 2, pageHeight / 2 + 30, { align: 'center' })
|
|
doc.text(config.date, pageWidth / 2, pageHeight / 2 + 40, { align: 'center' })
|
|
}
|
|
```
|
|
|
|
### 4. DOCX Export (using docx library)
|
|
|
|
```typescript
|
|
import { Document, Paragraph, TextRun, AlignmentType, HeadingLevel } from 'docx'
|
|
import { saveAs } from 'file-saver'
|
|
import { Packer } from 'docx'
|
|
|
|
export const generateDOCX = async (
|
|
config: ExportConfig,
|
|
onProgress?: (percent: number) => void
|
|
): Promise<Blob> => {
|
|
const sections = []
|
|
|
|
// Cover page
|
|
if (config.includeCoverPage) {
|
|
sections.push({
|
|
children: [
|
|
new Paragraph({
|
|
text: config.coverTitle,
|
|
heading: HeadingLevel.TITLE,
|
|
alignment: AlignmentType.CENTER,
|
|
spacing: { before: 400, after: 200 }
|
|
}),
|
|
new Paragraph({
|
|
text: config.coverSubtitle,
|
|
alignment: AlignmentType.CENTER,
|
|
spacing: { after: 200 }
|
|
}),
|
|
new Paragraph({
|
|
text: config.author,
|
|
alignment: AlignmentType.CENTER,
|
|
spacing: { after: 100 }
|
|
}),
|
|
new Paragraph({
|
|
text: config.date,
|
|
alignment: AlignmentType.CENTER
|
|
})
|
|
]
|
|
})
|
|
}
|
|
|
|
// Fetch content
|
|
const verses = await fetchVerses(
|
|
config.book,
|
|
config.startChapter,
|
|
config.endChapter
|
|
)
|
|
|
|
const chapters = groupByChapters(verses)
|
|
|
|
for (const [chapterNum, chapterVerses] of Object.entries(chapters)) {
|
|
// Chapter heading
|
|
if (config.includeChapterNumbers) {
|
|
sections.push(
|
|
new Paragraph({
|
|
text: `Chapter ${chapterNum}`,
|
|
heading: HeadingLevel.HEADING_1,
|
|
spacing: { before: 400, after: 200 }
|
|
})
|
|
)
|
|
}
|
|
|
|
// Verses
|
|
for (const verse of chapterVerses) {
|
|
const paragraph = new Paragraph({
|
|
children: []
|
|
})
|
|
|
|
// Verse number
|
|
if (config.includeVerseNumbers) {
|
|
paragraph.addChildElement(
|
|
new TextRun({
|
|
text: `${verse.verseNum} `,
|
|
bold: true
|
|
})
|
|
)
|
|
}
|
|
|
|
// Verse text
|
|
paragraph.addChildElement(
|
|
new TextRun({
|
|
text: verse.text,
|
|
size: config.fontSize * 2 // Convert to half-points
|
|
})
|
|
)
|
|
|
|
sections.push(paragraph)
|
|
|
|
// Highlights
|
|
if (config.includeHighlights && verse.highlights) {
|
|
for (const highlight of verse.highlights) {
|
|
sections.push(
|
|
new Paragraph({
|
|
children: [
|
|
new TextRun({
|
|
text: `[Highlight: ${highlight.color}] ${highlight.text}`,
|
|
italics: true,
|
|
color: highlight.color
|
|
})
|
|
],
|
|
spacing: { before: 100 }
|
|
})
|
|
)
|
|
}
|
|
}
|
|
|
|
// Notes
|
|
if (config.includeNotes && verse.notes) {
|
|
sections.push(
|
|
new Paragraph({
|
|
children: [
|
|
new TextRun({
|
|
text: `Note: ${verse.notes}`,
|
|
italics: true,
|
|
color: '666666'
|
|
})
|
|
],
|
|
spacing: { before: 100, after: 100 }
|
|
})
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
const doc = new Document({
|
|
sections: [{
|
|
properties: {
|
|
page: {
|
|
margin: {
|
|
top: config.margins.top * 56.7, // Convert mm to twips
|
|
right: config.margins.right * 56.7,
|
|
bottom: config.margins.bottom * 56.7,
|
|
left: config.margins.left * 56.7
|
|
}
|
|
}
|
|
},
|
|
children: sections
|
|
}]
|
|
})
|
|
|
|
return await Packer.toBlob(doc)
|
|
}
|
|
```
|
|
|
|
### 5. Markdown Export
|
|
|
|
```typescript
|
|
export const generateMarkdown = async (
|
|
config: ExportConfig
|
|
): Promise<string> => {
|
|
let markdown = ''
|
|
|
|
// Front matter
|
|
if (config.includeCoverPage) {
|
|
markdown += `---\n`
|
|
markdown += `title: ${config.coverTitle}\n`
|
|
markdown += `subtitle: ${config.coverSubtitle}\n`
|
|
markdown += `author: ${config.author}\n`
|
|
markdown += `date: ${config.date}\n`
|
|
markdown += `---\n\n`
|
|
}
|
|
|
|
// Title
|
|
markdown += `# ${config.coverTitle}\n\n`
|
|
|
|
// Fetch content
|
|
const verses = await fetchVerses(
|
|
config.book,
|
|
config.startChapter,
|
|
config.endChapter
|
|
)
|
|
|
|
const chapters = groupByChapters(verses)
|
|
|
|
for (const [chapterNum, chapterVerses] of Object.entries(chapters)) {
|
|
// Chapter heading
|
|
if (config.includeChapterNumbers) {
|
|
markdown += `## Chapter ${chapterNum}\n\n`
|
|
}
|
|
|
|
// Verses
|
|
for (const verse of chapterVerses) {
|
|
if (config.includeVerseNumbers) {
|
|
markdown += `**${verse.verseNum}** `
|
|
}
|
|
|
|
markdown += `${verse.text}\n\n`
|
|
|
|
// Highlights
|
|
if (config.includeHighlights && verse.highlights) {
|
|
for (const highlight of verse.highlights) {
|
|
markdown += `> 🎨 **Highlight (${highlight.color}):** ${highlight.text}\n\n`
|
|
}
|
|
}
|
|
|
|
// Notes
|
|
if (config.includeNotes && verse.notes) {
|
|
markdown += `> 📝 **Note:** ${verse.notes}\n\n`
|
|
}
|
|
}
|
|
|
|
markdown += '\n---\n\n'
|
|
}
|
|
|
|
return markdown
|
|
}
|
|
```
|
|
|
|
### 6. Batch Export
|
|
|
|
```typescript
|
|
interface BatchExportConfig {
|
|
books: string[]
|
|
format: ExportFormat
|
|
separate: boolean // Export each book as separate file
|
|
combinedFilename?: string
|
|
}
|
|
|
|
export const batchExport = async (
|
|
config: BatchExportConfig,
|
|
onProgress?: (current: number, total: number) => void
|
|
): Promise<Blob | Blob[]> => {
|
|
if (config.separate) {
|
|
// Export each book separately
|
|
const blobs: Blob[] = []
|
|
|
|
for (let i = 0; i < config.books.length; i++) {
|
|
const book = config.books[i]
|
|
|
|
const exportConfig: ExportConfig = {
|
|
...getDefaultConfig(),
|
|
book,
|
|
startChapter: 1,
|
|
endChapter: await getLastChapter(book),
|
|
format: config.format
|
|
}
|
|
|
|
const blob = await exportContent(exportConfig)
|
|
blobs.push(blob)
|
|
|
|
if (onProgress) {
|
|
onProgress(i + 1, config.books.length)
|
|
}
|
|
}
|
|
|
|
return blobs
|
|
} else {
|
|
// Export all books in one file
|
|
const exportConfig: ExportConfig = {
|
|
...getDefaultConfig(),
|
|
format: config.format
|
|
// Will loop through all books internally
|
|
}
|
|
|
|
return await exportContent(exportConfig)
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7. Print Optimization
|
|
|
|
```typescript
|
|
const PrintPreview: React.FC<{
|
|
config: ExportConfig
|
|
}> = ({ config }) => {
|
|
const contentRef = useRef<HTMLDivElement>(null)
|
|
|
|
const handlePrint = () => {
|
|
const printWindow = window.open('', '', 'height=800,width=600')
|
|
|
|
if (!printWindow) return
|
|
|
|
const printStyles = `
|
|
<style>
|
|
@page {
|
|
size: ${config.pageSize};
|
|
margin: ${config.margins.top}mm ${config.margins.right}mm
|
|
${config.margins.bottom}mm ${config.margins.left}mm;
|
|
}
|
|
|
|
body {
|
|
font-family: ${config.fontFamily};
|
|
font-size: ${config.fontSize}pt;
|
|
line-height: ${config.lineHeight};
|
|
color: ${config.colorMode === 'grayscale' ? '#000' : 'inherit'};
|
|
}
|
|
|
|
.verse-number {
|
|
font-weight: bold;
|
|
margin-right: 0.5em;
|
|
}
|
|
|
|
.chapter-heading {
|
|
font-size: ${config.fontSize + 4}pt;
|
|
font-weight: bold;
|
|
margin-top: 2em;
|
|
margin-bottom: 1em;
|
|
break-before: page;
|
|
}
|
|
|
|
.highlight {
|
|
background-color: ${config.colorMode === 'grayscale' ? '#ddd' : 'inherit'};
|
|
padding: 0 2px;
|
|
}
|
|
|
|
.note {
|
|
font-style: italic;
|
|
color: #666;
|
|
margin-left: 2em;
|
|
margin-top: 0.5em;
|
|
}
|
|
|
|
@media print {
|
|
.no-print {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
`
|
|
|
|
printWindow.document.write(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>${config.coverTitle}</title>
|
|
${printStyles}
|
|
</head>
|
|
<body>
|
|
${contentRef.current?.innerHTML}
|
|
</body>
|
|
</html>
|
|
`)
|
|
|
|
printWindow.document.close()
|
|
printWindow.focus()
|
|
printWindow.print()
|
|
}
|
|
|
|
return (
|
|
<Box>
|
|
<Button onClick={handlePrint} startIcon={<PrintIcon />}>
|
|
Print Preview
|
|
</Button>
|
|
|
|
<Box
|
|
ref={contentRef}
|
|
sx={{
|
|
p: 3,
|
|
bgcolor: 'white',
|
|
minHeight: '100vh',
|
|
fontFamily: config.fontFamily,
|
|
fontSize: `${config.fontSize}pt`,
|
|
lineHeight: config.lineHeight
|
|
}}
|
|
>
|
|
{/* Rendered content here */}
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
```
|
|
|
|
### 8. Email Export
|
|
|
|
```typescript
|
|
interface EmailExportConfig {
|
|
to: string[]
|
|
subject: string
|
|
message: string
|
|
exportConfig: ExportConfig
|
|
}
|
|
|
|
const EmailExportDialog: React.FC = () => {
|
|
const [config, setConfig] = useState<EmailExportConfig>({
|
|
to: [],
|
|
subject: '',
|
|
message: '',
|
|
exportConfig: getDefaultConfig()
|
|
})
|
|
|
|
const handleSend = async () => {
|
|
// Generate export
|
|
const blob = await exportContent(config.exportConfig)
|
|
|
|
// Convert to base64
|
|
const base64 = await blobToBase64(blob)
|
|
|
|
// Send via API
|
|
await fetch('/api/export/email', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
to: config.to,
|
|
subject: config.subject,
|
|
message: config.message,
|
|
attachment: {
|
|
filename: generateFilename(config.exportConfig),
|
|
content: base64,
|
|
contentType: getMimeType(config.exportConfig.format)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onClose={onClose}>
|
|
<DialogTitle>Email Export</DialogTitle>
|
|
<DialogContent>
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 2 }}>
|
|
<TextField
|
|
label="To"
|
|
placeholder="email@example.com"
|
|
value={config.to.join(', ')}
|
|
onChange={(e) => setConfig({
|
|
...config,
|
|
to: e.target.value.split(',').map(s => s.trim())
|
|
})}
|
|
fullWidth
|
|
/>
|
|
<TextField
|
|
label="Subject"
|
|
value={config.subject}
|
|
onChange={(e) => setConfig({ ...config, subject: e.target.value })}
|
|
fullWidth
|
|
/>
|
|
<TextField
|
|
label="Message"
|
|
value={config.message}
|
|
onChange={(e) => setConfig({ ...config, message: e.target.value })}
|
|
multiline
|
|
rows={4}
|
|
fullWidth
|
|
/>
|
|
</Box>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={onClose}>Cancel</Button>
|
|
<Button onClick={handleSend} variant="contained">
|
|
Send
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 API Endpoints
|
|
|
|
```typescript
|
|
// Generate and download export
|
|
POST /api/export
|
|
Body: ExportConfig
|
|
Response: File (binary)
|
|
|
|
// Email export
|
|
POST /api/export/email
|
|
Body: {
|
|
to: string[]
|
|
subject: string
|
|
message: string
|
|
attachment: {
|
|
filename: string
|
|
content: string (base64)
|
|
contentType: string
|
|
}
|
|
}
|
|
|
|
// Get export templates
|
|
GET /api/export/templates
|
|
Response: { templates: ExportTemplate[] }
|
|
|
|
// Save export preset
|
|
POST /api/export/presets
|
|
Body: { name: string, config: ExportConfig }
|
|
```
|
|
|
|
---
|
|
|
|
## 📅 Implementation Timeline
|
|
|
|
### Week 1: Core Export
|
|
**Day 1-2: Foundation**
|
|
- [ ] Create export dialog UI
|
|
- [ ] Build configuration forms
|
|
- [ ] Implement content fetching
|
|
|
|
**Day 3-4: PDF Export**
|
|
- [ ] Integrate jsPDF
|
|
- [ ] Implement basic PDF generation
|
|
- [ ] Add highlights/notes support
|
|
- [ ] Test layouts
|
|
|
|
**Day 5: DOCX & Markdown**
|
|
- [ ] Implement DOCX export
|
|
- [ ] Implement Markdown export
|
|
- [ ] Test formatting
|
|
|
|
**Deliverable:** Working PDF, DOCX, Markdown exports
|
|
|
|
### Week 2: Advanced Features
|
|
**Day 1-2: Layout Customization**
|
|
- [ ] Add cover page generation
|
|
- [ ] Implement TOC
|
|
- [ ] Add headers/footers
|
|
- [ ] Build print preview
|
|
|
|
**Day 3-4: Batch & Email**
|
|
- [ ] Implement batch export
|
|
- [ ] Build email functionality
|
|
- [ ] Add progress tracking
|
|
- [ ] Test large exports
|
|
|
|
**Day 5: Polish**
|
|
- [ ] Performance optimization
|
|
- [ ] Error handling
|
|
- [ ] UI refinement
|
|
- [ ] Documentation
|
|
|
|
**Deliverable:** Production-ready export system
|
|
|
|
---
|
|
|
|
## 🚀 Deployment Plan
|
|
|
|
### Pre-Launch
|
|
- [ ] Test with various content sizes
|
|
- [ ] Verify all formats generate correctly
|
|
- [ ] Performance testing
|
|
- [ ] Cross-browser testing
|
|
- [ ] Mobile testing
|
|
|
|
### Rollout
|
|
1. **Beta**: Limited users, PDF only
|
|
2. **Staged**: 50% users, all formats
|
|
3. **Full**: 100% deployment
|
|
|
|
---
|
|
|
|
**Document Version:** 1.0
|
|
**Last Updated:** 2025-10-13
|
|
**Owner:** Development Team
|
|
**Status:** Ready for Implementation
|