sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## Bug Fixes

### Note Card Actions
- Fix broken size change functionality (missing state declaration)
- Implement React 19 useOptimistic for instant UI feedback
- Add startTransition for non-blocking updates
- Ensure smooth animations without page refresh
- All note actions now work: pin, archive, color, size, checklist

### Markdown LaTeX Rendering
- Add remark-math and rehype-katex plugins
- Support inline equations with dollar sign syntax
- Support block equations with double dollar sign syntax
- Import KaTeX CSS for proper styling
- Equations now render correctly instead of showing raw LaTeX

## Technical Details

- Replace undefined currentNote references with optimistic state
- Add optimistic updates before server actions for instant feedback
- Use router.refresh() in transitions for smart cache invalidation
- Install remark-math, rehype-katex, and katex packages

## Testing

- Build passes successfully with no TypeScript errors
- Dev server hot-reloads changes correctly
2026-01-09 22:13:49 +01:00

194 lines
5.0 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
// GET /api/labels/[id] - Get a specific label
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const label = await prisma.label.findUnique({
where: { id }
})
if (!label) {
return NextResponse.json(
{ success: false, error: 'Label not found' },
{ status: 404 }
)
}
return NextResponse.json({
success: true,
data: label
})
} catch (error) {
console.error('GET /api/labels/[id] error:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch label' },
{ status: 500 }
)
}
}
// PUT /api/labels/[id] - Update a label
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const body = await request.json()
const { name, color } = body
// Get the current label first
const currentLabel = await prisma.label.findUnique({
where: { id }
})
if (!currentLabel) {
return NextResponse.json(
{ success: false, error: 'Label not found' },
{ status: 404 }
)
}
const newName = name ? name.trim() : currentLabel.name
// If renaming, update all notes that use this label
if (name && name.trim() !== currentLabel.name) {
// Get all notes that use this label
const allNotes = await prisma.note.findMany({
where: {
userId: currentLabel.userId,
labels: { not: null }
},
select: { id: true, labels: true }
})
// Update the label name in all notes that use it
for (const note of allNotes) {
if (note.labels) {
try {
const noteLabels: string[] = JSON.parse(note.labels)
const updatedLabels = noteLabels.map(l =>
l.toLowerCase() === currentLabel.name.toLowerCase() ? newName : l
)
// Update the note if labels changed
if (JSON.stringify(updatedLabels) !== JSON.stringify(noteLabels)) {
await prisma.note.update({
where: { id: note.id },
data: {
labels: JSON.stringify(updatedLabels)
}
})
}
} catch (e) {
console.error(`Failed to parse labels for note ${note.id}:`, e)
}
}
}
}
// Now update the label record
const label = await prisma.label.update({
where: { id },
data: {
...(name && { name: newName }),
...(color && { color })
}
})
// Revalidate to refresh UI
revalidatePath('/')
return NextResponse.json({
success: true,
data: label
})
} catch (error) {
console.error('PUT /api/labels/[id] error:', error)
return NextResponse.json(
{ success: false, error: 'Failed to update label' },
{ status: 500 }
)
}
}
// DELETE /api/labels/[id] - Delete a label
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
// First, get the label to know its name and userId
const label = await prisma.label.findUnique({
where: { id }
})
if (!label) {
return NextResponse.json(
{ success: false, error: 'Label not found' },
{ status: 404 }
)
}
// Get all notes that use this label
const allNotes = await prisma.note.findMany({
where: {
userId: label.userId,
labels: { not: null }
},
select: { id: true, labels: true }
})
// Remove the label from all notes that use it
for (const note of allNotes) {
if (note.labels) {
try {
const noteLabels: string[] = JSON.parse(note.labels)
const filteredLabels = noteLabels.filter(
l => l.toLowerCase() !== label.name.toLowerCase()
)
// Update the note if labels changed
if (filteredLabels.length !== noteLabels.length) {
await prisma.note.update({
where: { id: note.id },
data: {
labels: filteredLabels.length > 0 ? JSON.stringify(filteredLabels) : null
}
})
}
} catch (e) {
console.error(`Failed to parse labels for note ${note.id}:`, e)
}
}
}
// Now delete the label record
await prisma.label.delete({
where: { id }
})
// Revalidate to refresh UI
revalidatePath('/')
return NextResponse.json({
success: true,
message: `Label "${label.name}" deleted and removed from ${allNotes.length} notes`
})
} catch (error) {
console.error('DELETE /api/labels/[id] error:', error)
return NextResponse.json(
{ success: false, error: 'Failed to delete label' },
{ status: 500 }
)
}
}