feat: add slides generation tool with multiple slide types
- Add slides.tool.ts with support for title, bullets, chart, stats, table, cards, timeline, quote, comparison, equation, image, summary slide types - Chart types: bar, horizontal-bar, line, donut, radar - Integrate with agent executor and canvas system - Add multilingual support (en/fr) - Various UI improvements and bug fixes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
127
memento-note/app/actions/notes-history.ts
Normal file
127
memento-note/app/actions/notes-history.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
import { parseNote } from '@/lib/utils'
|
||||
import {
|
||||
createNoteHistorySnapshot,
|
||||
parseNoteHistoryEntry,
|
||||
} from '@/lib/note-history'
|
||||
|
||||
export async function getNoteHistory(noteId: string, limit = 30) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return []
|
||||
|
||||
const clampedLimit = Math.min(Math.max(limit, 1), 100)
|
||||
|
||||
const note = await prisma.note.findFirst({
|
||||
where: { id: noteId, userId: session.user.id },
|
||||
select: { id: true, historyEnabled: true },
|
||||
})
|
||||
if (!note || !note.historyEnabled) return []
|
||||
|
||||
const entries = await prisma.noteHistory.findMany({
|
||||
where: { noteId: note.id, userId: session.user.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: clampedLimit,
|
||||
})
|
||||
|
||||
return entries.map(parseNoteHistoryEntry)
|
||||
}
|
||||
|
||||
export async function restoreNoteVersion(noteId: string, historyEntryId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
const [note, historyEntry] = await Promise.all([
|
||||
prisma.note.findFirst({
|
||||
where: { id: noteId, userId: session.user.id },
|
||||
select: { id: true, notebookId: true, historyEnabled: true },
|
||||
}),
|
||||
prisma.noteHistory.findFirst({
|
||||
where: {
|
||||
id: historyEntryId,
|
||||
noteId,
|
||||
userId: session.user.id,
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
if (!note || !note.historyEnabled) throw new Error('History is disabled for this note')
|
||||
if (!historyEntry) throw new Error('History entry not found')
|
||||
|
||||
const userId = session.user.id
|
||||
|
||||
const restored = await prisma.note.update({
|
||||
where: { id: note.id, userId },
|
||||
data: {
|
||||
title: historyEntry.title,
|
||||
content: historyEntry.content,
|
||||
color: historyEntry.color,
|
||||
isPinned: historyEntry.isPinned,
|
||||
isArchived: historyEntry.isArchived,
|
||||
type: historyEntry.type,
|
||||
checkItems: historyEntry.checkItems,
|
||||
labels: historyEntry.labels,
|
||||
images: historyEntry.images,
|
||||
links: historyEntry.links,
|
||||
isMarkdown: historyEntry.isMarkdown,
|
||||
size: historyEntry.size,
|
||||
notebookId: historyEntry.notebookId,
|
||||
contentUpdatedAt: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
revalidatePath('/home')
|
||||
|
||||
return parseNote(restored)
|
||||
}
|
||||
|
||||
export async function commitNoteHistory(noteId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
const note = await prisma.note.findFirst({
|
||||
where: { id: noteId, userId: session.user.id },
|
||||
select: { id: true, historyEnabled: true },
|
||||
})
|
||||
if (!note) throw new Error('Note not found')
|
||||
if (!note.historyEnabled) throw new Error('History is disabled for this note')
|
||||
|
||||
await createNoteHistorySnapshot({
|
||||
noteId: note.id,
|
||||
userId: session.user.id,
|
||||
reason: 'manual-commit',
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteNoteHistoryEntry(noteId: string, historyEntryId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
const entry = await prisma.noteHistory.findFirst({
|
||||
where: { id: historyEntryId, noteId, userId: session.user.id },
|
||||
})
|
||||
if (!entry) throw new Error('History entry not found')
|
||||
|
||||
await prisma.noteHistory.delete({
|
||||
where: { id: historyEntryId },
|
||||
})
|
||||
}
|
||||
|
||||
export async function enableNoteHistory(noteId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
const note = await prisma.note.findFirst({
|
||||
where: { id: noteId, userId: session.user.id },
|
||||
select: { id: true },
|
||||
})
|
||||
if (!note) throw new Error('Note not found')
|
||||
|
||||
await prisma.note.update({
|
||||
where: { id: noteId },
|
||||
data: { historyEnabled: true },
|
||||
})
|
||||
}
|
||||
277
memento-note/app/actions/notes-sharing.ts
Normal file
277
memento-note/app/actions/notes-sharing.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
|
||||
// Add a collaborator to a note (delegates to the share request system)
|
||||
export async function addCollaborator(noteId: string, userEmail: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const result = await createShareRequest(noteId, userEmail, 'view')
|
||||
|
||||
const targetUser = await prisma.user.findUnique({
|
||||
where: { email: userEmail },
|
||||
select: { id: true, name: true, email: true, image: true }
|
||||
})
|
||||
|
||||
if (!targetUser) throw new Error('User not found')
|
||||
|
||||
return { success: true, user: targetUser }
|
||||
} catch (error: unknown) {
|
||||
console.error('Error adding collaborator:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeCollaborator(noteId: string, userId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const note = await prisma.note.findUnique({ where: { id: noteId } })
|
||||
if (!note) throw new Error('Note not found')
|
||||
if (note.userId !== session.user.id) throw new Error('You can only manage collaborators on your own notes')
|
||||
|
||||
await prisma.noteShare.deleteMany({ where: { noteId, userId } })
|
||||
|
||||
return { success: true }
|
||||
} catch (error: unknown) {
|
||||
console.error('Error removing collaborator:', error)
|
||||
throw new Error(error instanceof Error ? error.message : 'Failed to remove collaborator')
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNoteCollaborators(noteId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const note = await prisma.note.findUnique({
|
||||
where: { id: noteId },
|
||||
select: { userId: true }
|
||||
})
|
||||
if (!note) throw new Error('Note not found')
|
||||
|
||||
if (note.userId !== session.user.id) {
|
||||
const share = await prisma.noteShare.findUnique({
|
||||
where: { noteId_userId: { noteId, userId: session.user.id } }
|
||||
})
|
||||
if (!share || share.status !== 'accepted') throw new Error('You do not have access to this note')
|
||||
}
|
||||
|
||||
const shares = await prisma.noteShare.findMany({
|
||||
where: { noteId },
|
||||
include: { user: { select: { id: true, name: true, email: true, image: true } } }
|
||||
})
|
||||
|
||||
return shares.map(share => share.user)
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching collaborators:', error)
|
||||
throw new Error(error instanceof Error ? error.message : 'Failed to fetch collaborators')
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNoteAllUsers(noteId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const note = await prisma.note.findUnique({
|
||||
where: { id: noteId },
|
||||
select: { userId: true }
|
||||
})
|
||||
if (!note) throw new Error('Note not found')
|
||||
|
||||
const share = await prisma.noteShare.findUnique({
|
||||
where: { noteId_userId: { noteId, userId: session.user.id } }
|
||||
})
|
||||
|
||||
const hasAccess = note.userId === session.user.id || (share && share.status === 'accepted')
|
||||
if (!hasAccess) throw new Error('You do not have access to this note')
|
||||
|
||||
const owner = await prisma.user.findUnique({
|
||||
where: { id: note.userId! },
|
||||
select: { id: true, name: true, email: true, image: true }
|
||||
})
|
||||
if (!owner) throw new Error('Owner not found')
|
||||
|
||||
const shares = await prisma.noteShare.findMany({
|
||||
where: { noteId, status: 'accepted' },
|
||||
include: { user: { select: { id: true, name: true, email: true, image: true } } }
|
||||
})
|
||||
|
||||
return [owner, ...shares.map(s => s.user)]
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching note users:', error)
|
||||
throw new Error(error instanceof Error ? error.message : 'Failed to fetch note users')
|
||||
}
|
||||
}
|
||||
|
||||
export async function createShareRequest(
|
||||
noteId: string,
|
||||
recipientEmail: string,
|
||||
permission: 'view' | 'comment' | 'edit' = 'view'
|
||||
) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const note = await prisma.note.findUnique({ where: { id: noteId } })
|
||||
if (!note) throw new Error('Note not found')
|
||||
if (note.userId !== session.user.id) throw new Error('Only the owner can share notes')
|
||||
|
||||
const recipient = await prisma.user.findUnique({ where: { email: recipientEmail } })
|
||||
if (!recipient) throw new Error('User not found')
|
||||
if (recipient.id === session.user.id) throw new Error('You cannot share with yourself')
|
||||
|
||||
const existingShare = await prisma.noteShare.findUnique({
|
||||
where: { noteId_userId: { noteId, userId: recipient.id } }
|
||||
})
|
||||
|
||||
if (existingShare) {
|
||||
if (existingShare.status === 'declined' || existingShare.status === 'removed') {
|
||||
await prisma.noteShare.update({
|
||||
where: { id: existingShare.id },
|
||||
data: { status: 'pending', notifiedAt: new Date(), permission }
|
||||
})
|
||||
} else {
|
||||
throw new Error('Note already shared with this user')
|
||||
}
|
||||
} else {
|
||||
await prisma.noteShare.create({
|
||||
data: {
|
||||
noteId,
|
||||
userId: recipient.id,
|
||||
sharedBy: session.user.id,
|
||||
status: 'pending',
|
||||
permission,
|
||||
notifiedAt: new Date()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
} catch (error: unknown) {
|
||||
console.error('Error creating share request:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPendingShareRequests() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
return await prisma.noteShare.findMany({
|
||||
where: { userId: session.user.id, status: 'pending' },
|
||||
include: {
|
||||
note: { select: { id: true, title: true, content: true, color: true, createdAt: true } },
|
||||
sharer: { select: { id: true, name: true, email: true, image: true } }
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching pending share requests:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function respondToShareRequest(shareId: string, action: 'accept' | 'decline') {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const share = await prisma.noteShare.findUnique({
|
||||
where: { id: shareId },
|
||||
include: { note: true, sharer: true }
|
||||
})
|
||||
if (!share) throw new Error('Share request not found')
|
||||
if (share.userId !== session.user.id) throw new Error('Unauthorized')
|
||||
|
||||
const updatedShare = await prisma.noteShare.update({
|
||||
where: { id: shareId },
|
||||
data: { status: action === 'accept' ? 'accepted' : 'declined', respondedAt: new Date() },
|
||||
include: { note: { select: { title: true } } }
|
||||
})
|
||||
|
||||
revalidatePath('/home')
|
||||
return { success: true, share: updatedShare }
|
||||
} catch (error: unknown) {
|
||||
console.error('Error responding to share request:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAcceptedSharedNotes() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const acceptedShares = await prisma.noteShare.findMany({
|
||||
where: { userId: session.user.id, status: 'accepted' },
|
||||
include: { note: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
return acceptedShares.map(share => share.note)
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching accepted shared notes:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeSharedNoteFromView(shareId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const share = await prisma.noteShare.findUnique({ where: { id: shareId } })
|
||||
if (!share) throw new Error('Share not found')
|
||||
if (share.userId !== session.user.id) throw new Error('Unauthorized')
|
||||
|
||||
await prisma.noteShare.update({ where: { id: shareId }, data: { status: 'removed' } })
|
||||
|
||||
revalidatePath('/home')
|
||||
return { success: true }
|
||||
} catch (error: unknown) {
|
||||
console.error('Error removing shared note from view:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPendingShareCount() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return 0
|
||||
|
||||
try {
|
||||
return await prisma.noteShare.count({
|
||||
where: { userId: session.user.id, status: 'pending' }
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error getting pending share count:', error)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export async function leaveSharedNote(noteId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const share = await prisma.noteShare.findUnique({
|
||||
where: { noteId_userId: { noteId, userId: session.user.id } }
|
||||
})
|
||||
if (!share) throw new Error('Share not found')
|
||||
if (share.userId !== session.user.id) throw new Error('Unauthorized')
|
||||
|
||||
await prisma.noteShare.update({ where: { id: share.id }, data: { status: 'removed' } })
|
||||
|
||||
revalidatePath('/home')
|
||||
return { success: true }
|
||||
} catch (error: unknown) {
|
||||
console.error('Error leaving shared note:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
224
memento-note/app/actions/notes-trash.ts
Normal file
224
memento-note/app/actions/notes-trash.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
import { parseNote } from '@/lib/utils'
|
||||
import { parseImageUrls, cleanupNoteImages, deleteImageFileSafely } from '@/lib/image-cleanup'
|
||||
import { NOTE_LIST_SELECT } from '@/lib/note-select'
|
||||
|
||||
// Soft-delete a note (move to trash)
|
||||
export async function deleteNote(id: string, options?: { skipRevalidation?: boolean }) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
await prisma.note.update({
|
||||
where: { id, userId: session.user.id },
|
||||
data: { trashedAt: new Date() }
|
||||
})
|
||||
if (!options?.skipRevalidation) {
|
||||
revalidatePath('/home')
|
||||
}
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error deleting note:', error)
|
||||
throw new Error('Failed to delete note')
|
||||
}
|
||||
}
|
||||
|
||||
export async function trashNote(id: string, options?: { skipRevalidation?: boolean }) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
await prisma.note.update({
|
||||
where: { id, userId: session.user.id },
|
||||
data: { trashedAt: new Date() }
|
||||
})
|
||||
if (!options?.skipRevalidation) {
|
||||
revalidatePath('/home')
|
||||
}
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error trashing note:', error)
|
||||
throw new Error('Failed to trash note')
|
||||
}
|
||||
}
|
||||
|
||||
export async function restoreNote(id: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
await prisma.note.update({
|
||||
where: { id, userId: session.user.id },
|
||||
data: { trashedAt: null }
|
||||
})
|
||||
revalidatePath('/home')
|
||||
revalidatePath('/trash')
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error restoring note:', error)
|
||||
throw new Error('Failed to restore note')
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTrashedNotes() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return []
|
||||
|
||||
try {
|
||||
const notes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
trashedAt: { not: null }
|
||||
},
|
||||
select: NOTE_LIST_SELECT,
|
||||
orderBy: { trashedAt: 'desc' }
|
||||
})
|
||||
return notes.map(parseNote)
|
||||
} catch (error) {
|
||||
console.error('Error fetching trashed notes:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export async function permanentDeleteNote(id: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const note = await prisma.note.findUnique({
|
||||
where: { id, userId: session.user.id },
|
||||
select: { images: true }
|
||||
})
|
||||
const imageUrls = parseImageUrls(note?.images ?? null)
|
||||
|
||||
await prisma.note.delete({ where: { id, userId: session.user.id } })
|
||||
|
||||
if (imageUrls.length > 0) {
|
||||
await cleanupNoteImages(id, imageUrls)
|
||||
}
|
||||
|
||||
revalidatePath('/trash')
|
||||
revalidatePath('/home')
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error permanently deleting note:', error)
|
||||
throw new Error('Failed to permanently delete note')
|
||||
}
|
||||
}
|
||||
|
||||
export async function emptyTrash() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const trashedNotes = await prisma.note.findMany({
|
||||
where: { userId: session.user.id, trashedAt: { not: null } },
|
||||
select: { id: true, images: true }
|
||||
})
|
||||
|
||||
await prisma.note.deleteMany({
|
||||
where: { userId: session.user.id, trashedAt: { not: null } }
|
||||
})
|
||||
|
||||
for (const note of trashedNotes) {
|
||||
const imageUrls = parseImageUrls(note.images)
|
||||
if (imageUrls.length > 0) {
|
||||
await cleanupNoteImages(note.id, imageUrls)
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.notebook.deleteMany({
|
||||
where: { userId: session.user.id, trashedAt: { not: null } }
|
||||
})
|
||||
|
||||
revalidatePath('/trash')
|
||||
revalidatePath('/home')
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error emptying trash:', error)
|
||||
throw new Error('Failed to empty trash')
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeImageFromNote(noteId: string, imageIndex: number) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const note = await prisma.note.findUnique({
|
||||
where: { id: noteId, userId: session.user.id },
|
||||
select: { images: true },
|
||||
})
|
||||
if (!note) throw new Error('Note not found')
|
||||
|
||||
const imageUrls = parseImageUrls(note.images)
|
||||
if (imageIndex < 0 || imageIndex >= imageUrls.length) throw new Error('Invalid image index')
|
||||
|
||||
const removedUrl = imageUrls[imageIndex]
|
||||
const newImages = imageUrls.filter((_, i) => i !== imageIndex)
|
||||
|
||||
await prisma.note.update({
|
||||
where: { id: noteId },
|
||||
data: { images: newImages.length > 0 ? JSON.stringify(newImages) : null },
|
||||
})
|
||||
|
||||
await deleteImageFileSafely(removedUrl, noteId)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error removing image:', error)
|
||||
throw new Error('Failed to remove image')
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanupOrphanedImages(imageUrls: string[], noteId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return
|
||||
|
||||
try {
|
||||
for (const url of imageUrls) {
|
||||
await deleteImageFileSafely(url, noteId)
|
||||
}
|
||||
} catch {
|
||||
// Silent — best-effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTrashCount() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return 0
|
||||
|
||||
try {
|
||||
const [noteCount, notebookCount] = await Promise.all([
|
||||
prisma.note.count({
|
||||
where: { userId: session.user.id, trashedAt: { not: null } }
|
||||
}),
|
||||
prisma.notebook.count({
|
||||
where: { userId: session.user.id, trashedAt: { not: null } }
|
||||
})
|
||||
])
|
||||
return noteCount + notebookCount
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTrashedNotebooks() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return []
|
||||
|
||||
try {
|
||||
return await prisma.notebook.findMany({
|
||||
where: { userId: session.user.id, trashedAt: { not: null } },
|
||||
include: { _count: { select: { notes: true } } },
|
||||
orderBy: { trashedAt: 'desc' }
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching trashed notebooks:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user