mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-29 19:35:20 +02:00
feat: implement save and leave functionality in editor, enhance layout and error handling
This commit is contained in:
parent
664961076c
commit
6cc74689bc
2 changed files with 56 additions and 62 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { AlertCircle, ArrowLeft, FileText, Save } from "lucide-react";
|
import { AlertCircle, ArrowLeft, FileText } from "lucide-react";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
|
@ -297,6 +297,12 @@ export default function EditorPage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSaveAndLeave = async () => {
|
||||||
|
setShowUnsavedDialog(false);
|
||||||
|
setPendingNavigation(null);
|
||||||
|
await handleSave();
|
||||||
|
};
|
||||||
|
|
||||||
const handleCancelLeave = () => {
|
const handleCancelLeave = () => {
|
||||||
setShowUnsavedDialog(false);
|
setShowUnsavedDialog(false);
|
||||||
setPendingNavigation(null);
|
setPendingNavigation(null);
|
||||||
|
|
@ -364,11 +370,21 @@ export default function EditorPage() {
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
className="flex flex-col min-h-screen w-full"
|
className="flex flex-col h-screen w-full overflow-hidden"
|
||||||
>
|
>
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="sticky top-0 z-40 flex h-14 md:h-16 shrink-0 items-center gap-2 md:gap-4 bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 px-3 md:px-6">
|
<div className="flex h-14 md:h-16 shrink-0 items-center border-b bg-background pl-1.5 pr-3 md:pl-3 md:pr-6">
|
||||||
<div className="flex items-center gap-2 md:gap-3 flex-1 min-w-0">
|
<div className="flex items-center gap-1.5 md:gap-2 flex-1 min-w-0">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={handleBack}
|
||||||
|
disabled={saving}
|
||||||
|
className="h-7 w-7 shrink-0 p-0"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Back</span>
|
||||||
|
</Button>
|
||||||
<FileText className="h-4 w-4 md:h-5 md:w-5 text-muted-foreground shrink-0" />
|
<FileText className="h-4 w-4 md:h-5 md:w-5 text-muted-foreground shrink-0" />
|
||||||
<div className="flex flex-col min-w-0">
|
<div className="flex flex-col min-w-0">
|
||||||
<h1 className="text-base md:text-lg font-semibold truncate">{displayTitle}</h1>
|
<h1 className="text-base md:text-lg font-semibold truncate">{displayTitle}</h1>
|
||||||
|
|
@ -377,62 +393,32 @@ export default function EditorPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleBack}
|
|
||||||
disabled={saving}
|
|
||||||
className="gap-1 md:gap-2 px-2 md:px-4 h-8 md:h-10"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="h-3.5 w-3.5 md:h-4 md:w-4" />
|
|
||||||
<span className="text-xs md:text-sm">Back</span>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleSave}
|
|
||||||
disabled={saving}
|
|
||||||
className="gap-1 md:gap-2 px-2 md:px-4 h-8 md:h-10"
|
|
||||||
>
|
|
||||||
{saving ? (
|
|
||||||
<>
|
|
||||||
<Spinner size="sm" className="h-3.5 w-3.5 md:h-4 md:w-4" />
|
|
||||||
<span className="text-xs md:text-sm">{isNewNote ? "Creating" : "Saving"}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Save className="h-3.5 w-3.5 md:h-4 md:w-4" />
|
|
||||||
<span className="text-xs md:text-sm">Save</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Editor Container */}
|
{/* Editor Container */}
|
||||||
<div className="flex-1 min-h-0 overflow-hidden relative">
|
<div className="flex-1 min-h-0 flex flex-col overflow-hidden relative">
|
||||||
<div className="h-full w-full overflow-auto p-3 md:p-6">
|
{error && (
|
||||||
{error && (
|
<motion.div
|
||||||
<motion.div
|
initial={{ opacity: 0, y: -10 }}
|
||||||
initial={{ opacity: 0, y: -10 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
className="px-3 md:px-6 pt-3 md:pt-6"
|
||||||
className="mb-6 max-w-4xl mx-auto"
|
>
|
||||||
>
|
<div className="flex items-center gap-2 p-4 rounded-lg border border-destructive/50 bg-destructive/10 text-destructive max-w-4xl mx-auto">
|
||||||
<div className="flex items-center gap-2 p-4 rounded-lg border border-destructive/50 bg-destructive/10 text-destructive">
|
<AlertCircle className="h-5 w-5 shrink-0" />
|
||||||
<AlertCircle className="h-5 w-5 shrink-0" />
|
<p className="text-sm">{error}</p>
|
||||||
<p className="text-sm">{error}</p>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
</motion.div>
|
)}
|
||||||
)}
|
<div className="flex-1 min-h-0">
|
||||||
<div className="max-w-4xl mx-auto">
|
<PlateEditor
|
||||||
<PlateEditor
|
key={documentId}
|
||||||
key={documentId}
|
markdown={document?.source_markdown ?? ""}
|
||||||
markdown={document?.source_markdown ?? ""}
|
onMarkdownChange={handleMarkdownChange}
|
||||||
onMarkdownChange={handleMarkdownChange}
|
onSave={handleSave}
|
||||||
onSave={handleSave}
|
hasUnsavedChanges={hasUnsavedChanges}
|
||||||
hasUnsavedChanges={hasUnsavedChanges}
|
isSaving={saving}
|
||||||
isSaving={saving}
|
defaultEditing={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -452,7 +438,12 @@ export default function EditorPage() {
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel onClick={handleCancelLeave}>Cancel</AlertDialogCancel>
|
<AlertDialogCancel onClick={handleCancelLeave}>Cancel</AlertDialogCancel>
|
||||||
<AlertDialogAction onClick={handleConfirmLeave}>OK</AlertDialogAction>
|
<AlertDialogAction onClick={handleSaveAndLeave}>
|
||||||
|
Save
|
||||||
|
</AlertDialogAction>
|
||||||
|
<AlertDialogAction onClick={handleConfirmLeave} className="border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground">
|
||||||
|
Leave without saving
|
||||||
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ interface PlateEditorProps {
|
||||||
hasUnsavedChanges?: boolean;
|
hasUnsavedChanges?: boolean;
|
||||||
/** Whether a save is in progress */
|
/** Whether a save is in progress */
|
||||||
isSaving?: boolean;
|
isSaving?: boolean;
|
||||||
|
/** Start the editor in editing mode instead of viewing mode. Ignored when readOnly is true. */
|
||||||
|
defaultEditing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PlateEditor({
|
export function PlateEditor({
|
||||||
|
|
@ -63,14 +65,15 @@ export function PlateEditor({
|
||||||
onSave,
|
onSave,
|
||||||
hasUnsavedChanges = false,
|
hasUnsavedChanges = false,
|
||||||
isSaving = false,
|
isSaving = false,
|
||||||
|
defaultEditing = false,
|
||||||
}: PlateEditorProps) {
|
}: PlateEditorProps) {
|
||||||
const lastMarkdownRef = useRef(markdown);
|
const lastMarkdownRef = useRef(markdown);
|
||||||
|
|
||||||
// Always initialize the editor in readOnly mode (viewing mode).
|
// When readOnly is forced, always start in readOnly.
|
||||||
// For non-forced readOnly, the user can toggle to editing via ModeToolbarButton.
|
// Otherwise, respect defaultEditing to decide initial mode.
|
||||||
// For forced readOnly, the mode button is hidden and readOnly stays true.
|
// The user can still toggle between editing/viewing via ModeToolbarButton.
|
||||||
const editor = usePlateEditor({
|
const editor = usePlateEditor({
|
||||||
readOnly: true,
|
readOnly: readOnly || !defaultEditing,
|
||||||
plugins: [
|
plugins: [
|
||||||
...BasicNodesKit,
|
...BasicNodesKit,
|
||||||
...TableKit,
|
...TableKit,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue