carnet panel3

This commit is contained in:
alma 2025-04-20 17:31:14 +02:00
parent 2bb0fb74a1
commit dea5ae1ff8
3 changed files with 174 additions and 79 deletions

View File

@ -12,6 +12,34 @@ declare global {
const prisma = global.prisma || new PrismaClient();
if (process.env.NODE_ENV !== 'production') global.prisma = prisma;
// Helper function to create WebDAV client
const createWebDAVClient = async (userId: string) => {
const credentials = await prisma.webDAVCredentials.findUnique({
where: { userId },
});
if (!credentials) {
throw new Error('No WebDAV credentials found');
}
const baseURL = process.env.NEXTCLOUD_URL;
if (!baseURL) {
throw new Error('NEXTCLOUD_URL environment variable is not set');
}
const normalizedBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
const webdavURL = `${normalizedBaseURL}/remote.php/dav`;
return {
client: createClient(webdavURL, {
username: credentials.username,
password: credentials.password,
authType: 'password',
}),
username: credentials.username
};
};
export async function GET(request: Request) {
try {
const session = await getServerSession(authOptions);
@ -22,84 +50,118 @@ export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const folder = searchParams.get('folder') || 'Notes';
// Get WebDAV credentials
const credentials = await prisma.webDAVCredentials.findUnique({
where: { userId: session.user.id },
});
if (!credentials) {
console.error('No WebDAV credentials found for user:', session.user.id);
return NextResponse.json({ error: 'No WebDAV credentials found' }, { status: 404 });
}
console.log('Using credentials for user:', credentials.username);
console.log('Accessing folder:', folder);
// Initialize WebDAV client with proper URL construction
const baseURL = process.env.NEXTCLOUD_URL;
if (!baseURL) {
throw new Error('NEXTCLOUD_URL environment variable is not set');
}
// Remove trailing slash if present
const normalizedBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
const { client, username } = await createWebDAVClient(session.user.id);
// Construct the full WebDAV URL
const webdavURL = `${normalizedBaseURL}/remote.php/dav`;
console.log('WebDAV base URL:', webdavURL);
const client = createClient(webdavURL, {
username: credentials.username,
password: credentials.password,
authType: 'password',
});
try {
// List files in the specified folder
const path = `/files/${credentials.username}/Private/${folder}`;
const path = `/files/${username}/Private/${folder}`;
console.log('Fetching contents from path:', path);
const files = await client.getDirectoryContents(path);
console.log('Raw files response:', JSON.stringify(files, null, 2));
// Filter for .md files and format the response
const markdownFiles = files
.filter((file: any) => {
const isMarkdown = file.basename.endsWith('.md');
console.log(`File: ${file.basename}, isMarkdown: ${isMarkdown}`);
return isMarkdown;
})
.map((file: any) => {
const fileData = {
id: file.filename,
title: file.basename.replace('.md', ''),
lastModified: new Date(file.lastmod).toISOString(),
size: file.size,
type: 'file',
mime: file.mime,
etag: file.etag
};
console.log('Formatted file data:', JSON.stringify(fileData, null, 2));
return fileData;
});
.filter((file: any) => file.basename.endsWith('.md'))
.map((file: any) => ({
id: file.filename,
title: file.basename.replace('.md', ''),
lastModified: new Date(file.lastmod).toISOString(),
size: file.size,
type: 'file',
mime: file.mime,
etag: file.etag
}));
console.log('Found markdown files:', markdownFiles.length);
console.log('Final response:', JSON.stringify(markdownFiles, null, 2));
return NextResponse.json(markdownFiles);
} catch (error) {
console.error('Error listing directory contents:', error);
if (error instanceof Error) {
console.error('Error details:', error.message);
console.error('Error stack:', error.stack);
}
return NextResponse.json({ error: 'Failed to list directory contents' }, { status: 500 });
}
} catch (error) {
console.error('Error fetching files:', error);
if (error instanceof Error) {
console.error('Error details:', error.message);
console.error('Error stack:', error.stack);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return NextResponse.json({ error: 'Failed to fetch files' }, { status: 500 });
const { title, content, folder } = await request.json();
if (!title || !content || !folder) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
const { client, username } = await createWebDAVClient(session.user.id);
try {
const path = `/files/${username}/Private/${folder}/${title}.md`;
console.log('Saving note to path:', path);
await client.putFileContents(path, content);
// Get the file details after saving
const fileDetails = await client.stat(path);
return NextResponse.json({
id: fileDetails.filename,
title: fileDetails.basename.replace('.md', ''),
lastModified: new Date(fileDetails.lastmod).toISOString(),
size: fileDetails.size,
type: 'file',
mime: fileDetails.mime,
etag: fileDetails.etag
});
} catch (error) {
console.error('Error saving note:', error);
return NextResponse.json({ error: 'Failed to save note' }, { status: 500 });
}
} catch (error) {
console.error('Error in POST request:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
export async function PUT(request: Request) {
try {
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { id, title, content, folder } = await request.json();
if (!id || !title || !content || !folder) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
const { client, username } = await createWebDAVClient(session.user.id);
try {
const path = `/files/${username}/Private/${folder}/${title}.md`;
console.log('Updating note at path:', path);
await client.putFileContents(path, content);
// Get the updated file details
const fileDetails = await client.stat(path);
return NextResponse.json({
id: fileDetails.filename,
title: fileDetails.basename.replace('.md', ''),
lastModified: new Date(fileDetails.lastmod).toISOString(),
size: fileDetails.size,
type: 'file',
mime: fileDetails.mime,
etag: fileDetails.etag
});
} catch (error) {
console.error('Error updating note:', error);
return NextResponse.json({ error: 'Failed to update note' }, { status: 500 });
}
} catch (error) {
console.error('Error in PUT request:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}

View File

@ -185,10 +185,7 @@ export default function CarnetPage() {
{/* Notes Panel */}
{showNotes && (
<>
<div
className="flex flex-col h-full bg-carnet-bg"
style={{ width: `${notesWidth}px` }}
>
<div className="flex-1 overflow-hidden">
<NotesView
onNoteSelect={handleNoteSelect}
currentFolder={selectedFolder}
@ -206,8 +203,12 @@ export default function CarnetPage() {
)}
{/* Editor Panel */}
<div className="flex-1 flex flex-col h-full bg-carnet-bg">
<Editor note={selectedNote} onSave={handleNoteSave} />
<div className="flex-1 overflow-hidden">
<Editor
note={selectedNote}
onSave={handleNoteSave}
currentFolder={selectedFolder}
/>
</div>
{/* Mobile Navigation Toggle */}

View File

@ -15,11 +15,13 @@ interface Note {
interface EditorProps {
note?: Note | null;
onSave?: (note: Note) => void;
currentFolder?: string;
}
export const Editor: React.FC<EditorProps> = ({ note, onSave }) => {
export const Editor: React.FC<EditorProps> = ({ note, onSave, currentFolder = 'Notes' }) => {
const [title, setTitle] = useState(note?.title || '');
const [content, setContent] = useState(note?.content || '');
const [isSaving, setIsSaving] = useState(false);
useEffect(() => {
if (note) {
@ -36,16 +38,45 @@ export const Editor: React.FC<EditorProps> = ({ note, onSave }) => {
setContent(e.target.value);
};
const handleSave = () => {
if (note?.id) {
onSave?.({
id: note.id,
title,
content,
lastEdited: new Date(),
category: note.category,
tags: note.tags
const handleSave = async () => {
if (!title || !content) return;
setIsSaving(true);
try {
const endpoint = note?.id ? '/api/nextcloud/files' : '/api/nextcloud/files';
const method = note?.id ? 'PUT' : 'POST';
const response = await fetch(endpoint, {
method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: note?.id,
title,
content,
folder: currentFolder
}),
});
if (!response.ok) {
throw new Error('Failed to save note');
}
const savedNote = await response.json();
onSave?.({
id: savedNote.id,
title: savedNote.title,
content,
lastEdited: new Date(savedNote.lastModified),
category: note?.category,
tags: note?.tags
});
} catch (error) {
console.error('Error saving note:', error);
// TODO: Show error message to user
} finally {
setIsSaving(false);
}
};
@ -75,8 +106,9 @@ export const Editor: React.FC<EditorProps> = ({ note, onSave }) => {
<Image className="h-4 w-4 text-carnet-text-muted" />
</button>
<button
className="p-1.5 rounded hover:bg-carnet-hover"
className={`p-1.5 rounded hover:bg-carnet-hover ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`}
onClick={handleSave}
disabled={isSaving}
>
<FileText className="h-4 w-4 text-carnet-text-muted" />
</button>