154 lines
4.4 KiB
TypeScript
154 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { Image, FileText, Link, List } from 'lucide-react';
|
|
|
|
interface Note {
|
|
id: string;
|
|
title: string;
|
|
content: string;
|
|
lastModified: string;
|
|
type: string;
|
|
mime: string;
|
|
etag: string;
|
|
}
|
|
|
|
interface EditorProps {
|
|
note?: Note | null;
|
|
onSave?: (note: Note) => void;
|
|
currentFolder?: string;
|
|
}
|
|
|
|
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);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const fetchNoteContent = async () => {
|
|
if (note?.id) {
|
|
setIsLoading(true);
|
|
try {
|
|
const response = await fetch(`/api/nextcloud/files/content?id=${note.id}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch note content');
|
|
}
|
|
const data = await response.json();
|
|
setContent(data.content);
|
|
} catch (error) {
|
|
console.error('Error fetching note content:', error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (note) {
|
|
setTitle(note.title);
|
|
fetchNoteContent();
|
|
} else {
|
|
setTitle('');
|
|
setContent('');
|
|
}
|
|
}, [note]);
|
|
|
|
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setTitle(e.target.value);
|
|
};
|
|
|
|
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
setContent(e.target.value);
|
|
};
|
|
|
|
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?.({
|
|
...savedNote,
|
|
content
|
|
});
|
|
} catch (error) {
|
|
console.error('Error saving note:', error);
|
|
// TODO: Show error message to user
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full bg-carnet-bg">
|
|
{/* Title Bar */}
|
|
<div className="p-4 border-b border-carnet-border">
|
|
<input
|
|
type="text"
|
|
value={title}
|
|
onChange={handleTitleChange}
|
|
placeholder="Titre"
|
|
className="w-full text-xl font-semibold text-carnet-text-primary placeholder-carnet-text-muted focus:outline-none bg-transparent"
|
|
/>
|
|
</div>
|
|
|
|
{/* Toolbar */}
|
|
<div className="px-4 py-2 border-b border-carnet-border">
|
|
<div className="flex space-x-1">
|
|
<button className="p-1.5 rounded hover:bg-carnet-hover">
|
|
<List className="h-4 w-4 text-carnet-text-muted" />
|
|
</button>
|
|
<button className="p-1.5 rounded hover:bg-carnet-hover">
|
|
<Link className="h-4 w-4 text-carnet-text-muted" />
|
|
</button>
|
|
<button className="p-1.5 rounded hover:bg-carnet-hover">
|
|
<Image className="h-4 w-4 text-carnet-text-muted" />
|
|
</button>
|
|
<button
|
|
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>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Editor Area */}
|
|
<div className="flex-1 p-4">
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center h-full">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary"></div>
|
|
</div>
|
|
) : (
|
|
<textarea
|
|
value={content}
|
|
onChange={handleContentChange}
|
|
placeholder="Ecrire..."
|
|
className="w-full h-full resize-none focus:outline-none bg-transparent text-carnet-text-primary placeholder-carnet-text-muted"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|