mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-30 11:26:24 +02:00
feat: improve note editing and creation workflow
- Enhanced the editor to handle new note creation directly, initializing with an empty state. - Updated save functionality to create a note first if it's new, followed by saving the content. - Modified breadcrumb navigation to accommodate new notes and improve user experience. - Removed the old note creation page to streamline the editing process. - Added error handling and dynamic title updates for better feedback during note creation.
This commit is contained in:
parent
e1e813702a
commit
e9d3d36dfc
6 changed files with 167 additions and 225 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AlertCircle, ArrowLeft, FileText, Loader2, Save, X } from "lucide-react";
|
import { AlertCircle, ArrowLeft, FileText, Loader2, Plus, SquarePen, Save, X } from "lucide-react";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { notesApiService } from "@/lib/apis/notes-api.service";
|
||||||
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface EditorContent {
|
interface EditorContent {
|
||||||
|
|
@ -62,6 +63,8 @@ export default function EditorPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const documentId = params.documentId as string;
|
const documentId = params.documentId as string;
|
||||||
|
const searchSpaceId = Number(params.search_space_id);
|
||||||
|
const isNewNote = documentId === "new";
|
||||||
|
|
||||||
const [document, setDocument] = useState<EditorContent | null>(null);
|
const [document, setDocument] = useState<EditorContent | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
@ -72,8 +75,23 @@ export default function EditorPage() {
|
||||||
const [showUnsavedDialog, setShowUnsavedDialog] = useState(false);
|
const [showUnsavedDialog, setShowUnsavedDialog] = useState(false);
|
||||||
|
|
||||||
// Fetch document content - DIRECT CALL TO FASTAPI
|
// Fetch document content - DIRECT CALL TO FASTAPI
|
||||||
|
// Skip fetching if this is a new note
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDocument() {
|
async function fetchDocument() {
|
||||||
|
// For new notes, initialize with empty state
|
||||||
|
if (isNewNote) {
|
||||||
|
setDocument({
|
||||||
|
document_id: 0,
|
||||||
|
title: "Untitled",
|
||||||
|
document_type: "NOTE",
|
||||||
|
blocknote_document: null,
|
||||||
|
updated_at: null,
|
||||||
|
});
|
||||||
|
setEditorContent(null);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const token = getBearerToken();
|
const token = getBearerToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.error("No auth token found");
|
console.error("No auth token found");
|
||||||
|
|
@ -122,7 +140,7 @@ export default function EditorPage() {
|
||||||
if (documentId) {
|
if (documentId) {
|
||||||
fetchDocument();
|
fetchDocument();
|
||||||
}
|
}
|
||||||
}, [documentId, params.search_space_id]);
|
}, [documentId, params.search_space_id, isNewNote]);
|
||||||
|
|
||||||
// Track changes to mark as unsaved
|
// Track changes to mark as unsaved
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -132,7 +150,7 @@ export default function EditorPage() {
|
||||||
}, [editorContent, document]);
|
}, [editorContent, document]);
|
||||||
|
|
||||||
// Check if this is a NOTE type document
|
// Check if this is a NOTE type document
|
||||||
const isNote = document?.document_type === "NOTE";
|
const isNote = isNewNote || document?.document_type === "NOTE";
|
||||||
|
|
||||||
// Extract title dynamically from editor content for notes, otherwise use document title
|
// Extract title dynamically from editor content for notes, otherwise use document title
|
||||||
const displayTitle = useMemo(() => {
|
const displayTitle = useMemo(() => {
|
||||||
|
|
@ -145,6 +163,7 @@ export default function EditorPage() {
|
||||||
// TODO: Maybe add Auto-save every 30 seconds - DIRECT CALL TO FASTAPI
|
// TODO: Maybe add Auto-save every 30 seconds - DIRECT CALL TO FASTAPI
|
||||||
|
|
||||||
// Save and exit - DIRECT CALL TO FASTAPI
|
// Save and exit - DIRECT CALL TO FASTAPI
|
||||||
|
// For new notes, create the note first, then save
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
const token = getBearerToken();
|
const token = getBearerToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
|
@ -153,42 +172,90 @@ export default function EditorPage() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!editorContent) {
|
|
||||||
toast.error("No content to save");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save blocknote_document and trigger reindexing in background
|
// If this is a new note, create it first
|
||||||
const response = await authenticatedFetch(
|
if (isNewNote) {
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${params.search_space_id}/documents/${documentId}/save`,
|
const title = extractTitleFromBlockNote(editorContent);
|
||||||
{
|
|
||||||
method: "POST",
|
// Create the note first
|
||||||
headers: { "Content-Type": "application/json" },
|
const note = await notesApiService.createNote({
|
||||||
body: JSON.stringify({ blocknote_document: editorContent }),
|
search_space_id: searchSpaceId,
|
||||||
|
title: title,
|
||||||
|
blocknote_document: editorContent || undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there's content, save it properly and trigger reindexing
|
||||||
|
if (editorContent) {
|
||||||
|
const response = await authenticatedFetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${note.id}/save`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ blocknote_document: editorContent }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response
|
||||||
|
.json()
|
||||||
|
.catch(() => ({ detail: "Failed to save document" }));
|
||||||
|
throw new Error(errorData.detail || "Failed to save document");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
setHasUnsavedChanges(false);
|
||||||
const errorData = await response
|
toast.success("Note created successfully! Reindexing in background...");
|
||||||
.json()
|
|
||||||
.catch(() => ({ detail: "Failed to save document" }));
|
// Redirect to editor with the new document ID
|
||||||
throw new Error(errorData.detail || "Failed to save document");
|
setTimeout(() => {
|
||||||
|
router.push(`/dashboard/${searchSpaceId}/editor/${note.id}`);
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
// Existing document - save normally
|
||||||
|
if (!editorContent) {
|
||||||
|
toast.error("No content to save");
|
||||||
|
setSaving(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save blocknote_document and trigger reindexing in background
|
||||||
|
const response = await authenticatedFetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${params.search_space_id}/documents/${documentId}/save`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ blocknote_document: editorContent }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response
|
||||||
|
.json()
|
||||||
|
.catch(() => ({ detail: "Failed to save document" }));
|
||||||
|
throw new Error(errorData.detail || "Failed to save document");
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
toast.success("Document saved! Reindexing in background...");
|
||||||
|
|
||||||
|
// Small delay before redirect to show success message
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push(`/dashboard/${params.search_space_id}/documents`);
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
setHasUnsavedChanges(false);
|
|
||||||
toast.success("Document saved! Reindexing in background...");
|
|
||||||
|
|
||||||
// Small delay before redirect to show success message
|
|
||||||
setTimeout(() => {
|
|
||||||
router.push(`/dashboard/${params.search_space_id}/documents`);
|
|
||||||
}, 500);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving document:", error);
|
console.error("Error saving document:", error);
|
||||||
toast.error(
|
const errorMessage =
|
||||||
error instanceof Error ? error.message : "Failed to save document. Please try again."
|
error instanceof Error
|
||||||
);
|
? error.message
|
||||||
|
: isNewNote
|
||||||
|
? "Failed to create note. Please try again."
|
||||||
|
: "Failed to save document. Please try again.";
|
||||||
|
setError(errorMessage);
|
||||||
|
toast.error(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|
@ -248,7 +315,7 @@ export default function EditorPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!document) {
|
if (!document && !isNewNote) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[400px] p-6">
|
<div className="flex items-center justify-center min-h-[400px] p-6">
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
|
|
@ -286,13 +353,20 @@ export default function EditorPage() {
|
||||||
{saving ? (
|
{saving ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
Saving...
|
{isNewNote ? "Creating..." : "Saving..."}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
isNewNote ? (
|
||||||
<Save className="h-4 w-4" />
|
<>
|
||||||
Save & Exit
|
<SquarePen className="h-4 w-4" />
|
||||||
</>
|
Create Note
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
Save & Exit
|
||||||
|
</>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -301,9 +375,21 @@ export default function EditorPage() {
|
||||||
{/* Editor Container */}
|
{/* Editor Container */}
|
||||||
<div className="flex-1 overflow-hidden relative">
|
<div className="flex-1 overflow-hidden relative">
|
||||||
<div className="h-full w-full overflow-auto p-6">
|
<div className="h-full w-full overflow-auto p-6">
|
||||||
|
{error && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
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">
|
||||||
|
<AlertCircle className="h-5 w-5 shrink-0" />
|
||||||
|
<p className="text-sm">{error}</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<BlockNoteEditor
|
<BlockNoteEditor
|
||||||
initialContent={editorContent}
|
initialContent={isNewNote ? undefined : editorContent}
|
||||||
onChange={setEditorContent}
|
onChange={setEditorContent}
|
||||||
useTitleBlock={isNote}
|
useTitleBlock={isNote}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { AlertCircle, ArrowLeft, FileText, Loader2, Plus } from "lucide-react";
|
|
||||||
import { motion } from "motion/react";
|
|
||||||
import { useParams, useRouter } from "next/navigation";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { BlockNoteEditor } from "@/components/DynamicBlockNoteEditor";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import { notesApiService } from "@/lib/apis/notes-api.service";
|
|
||||||
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
|
||||||
|
|
||||||
// Helper function to extract title from BlockNote document
|
|
||||||
// Takes the text content from the first block (should be a heading)
|
|
||||||
function extractTitleFromBlockNote(blocknoteDocument: any[] | null | undefined): string {
|
|
||||||
if (!blocknoteDocument || !Array.isArray(blocknoteDocument) || blocknoteDocument.length === 0) {
|
|
||||||
return "Untitled";
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstBlock = blocknoteDocument[0];
|
|
||||||
if (!firstBlock) {
|
|
||||||
return "Untitled";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract text from block content
|
|
||||||
// BlockNote blocks have a content array with inline content
|
|
||||||
if (firstBlock.content && Array.isArray(firstBlock.content)) {
|
|
||||||
const textContent = firstBlock.content
|
|
||||||
.map((item: any) => {
|
|
||||||
if (typeof item === "string") return item;
|
|
||||||
if (item?.text) return item.text;
|
|
||||||
return "";
|
|
||||||
})
|
|
||||||
.join("")
|
|
||||||
.trim();
|
|
||||||
return textContent || "Untitled";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Untitled";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NewNotePage() {
|
|
||||||
const params = useParams();
|
|
||||||
const router = useRouter();
|
|
||||||
const searchSpaceId = Number(params.search_space_id);
|
|
||||||
|
|
||||||
const [editorContent, setEditorContent] = useState<any>(null);
|
|
||||||
const [creating, setCreating] = useState(false);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// Extract title dynamically from editor content
|
|
||||||
const dynamicTitle = useMemo(() => {
|
|
||||||
return extractTitleFromBlockNote(editorContent);
|
|
||||||
}, [editorContent]);
|
|
||||||
|
|
||||||
const handleCreate = async () => {
|
|
||||||
setCreating(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Extract title from first block of editor content
|
|
||||||
const title = extractTitleFromBlockNote(editorContent);
|
|
||||||
|
|
||||||
// Create the note first
|
|
||||||
const note = await notesApiService.createNote({
|
|
||||||
search_space_id: searchSpaceId,
|
|
||||||
title: title,
|
|
||||||
blocknote_document: editorContent || undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there's content, save it properly and trigger reindexing
|
|
||||||
if (editorContent) {
|
|
||||||
const token = getBearerToken();
|
|
||||||
if (!token) {
|
|
||||||
toast.error("Please login to save");
|
|
||||||
redirectToLogin();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the save endpoint to properly save blocknote_document and trigger reindexing
|
|
||||||
const response = await authenticatedFetch(
|
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${note.id}/save`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ blocknote_document: editorContent }),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response
|
|
||||||
.json()
|
|
||||||
.catch(() => ({ detail: "Failed to save document" }));
|
|
||||||
throw new Error(errorData.detail || "Failed to save document");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success("Note created successfully! Reindexing in background...");
|
|
||||||
// Redirect to editor
|
|
||||||
router.push(`/dashboard/${searchSpaceId}/editor/${note.id}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error creating note:", error);
|
|
||||||
const errorMessage =
|
|
||||||
error instanceof Error ? error.message : "Failed to create note. Please try again.";
|
|
||||||
setError(errorMessage);
|
|
||||||
toast.error(errorMessage);
|
|
||||||
} finally {
|
|
||||||
setCreating(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
router.back();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
className="flex flex-col h-full w-full"
|
|
||||||
>
|
|
||||||
{/* Toolbar */}
|
|
||||||
<div className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-4 border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 px-6">
|
|
||||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
|
||||||
<FileText className="h-5 w-5 text-muted-foreground shrink-0" />
|
|
||||||
<div className="flex flex-col min-w-0">
|
|
||||||
<h1 className="text-lg font-semibold truncate">{dynamicTitle}</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator orientation="vertical" className="h-6" />
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button variant="outline" onClick={handleBack} disabled={creating} className="gap-2">
|
|
||||||
<ArrowLeft className="h-4 w-4" />
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleCreate} disabled={creating} className="gap-2">
|
|
||||||
{creating ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
|
||||||
Creating...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
Create Note
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Editor Container - matches editor page layout */}
|
|
||||||
<div className="flex-1 overflow-hidden relative">
|
|
||||||
<div className="h-full w-full overflow-auto p-6">
|
|
||||||
{error && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: -10 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
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">
|
|
||||||
<AlertCircle className="h-5 w-5 shrink-0" />
|
|
||||||
<p className="text-sm">{error}</p>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
<div className="max-w-4xl mx-auto">
|
|
||||||
<BlockNoteEditor initialContent={undefined} onChange={setEditorContent} useTitleBlock={true} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -44,6 +44,13 @@ export function DashboardBreadcrumb() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (segments[2] === "editor" && segments[3] && searchSpaceId) {
|
if (segments[2] === "editor" && segments[3] && searchSpaceId) {
|
||||||
const documentId = segments[3];
|
const documentId = segments[3];
|
||||||
|
|
||||||
|
// Skip fetch for "new" notes
|
||||||
|
if (documentId === "new") {
|
||||||
|
setDocumentTitle(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const token = getBearerToken();
|
const token = getBearerToken();
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
|
|
@ -110,7 +117,14 @@ export function DashboardBreadcrumb() {
|
||||||
|
|
||||||
// Handle editor sub-sections (document ID)
|
// Handle editor sub-sections (document ID)
|
||||||
if (section === "editor") {
|
if (section === "editor") {
|
||||||
const documentLabel = documentTitle || subSection;
|
// Handle special cases for editor
|
||||||
|
let documentLabel: string;
|
||||||
|
if (subSection === "new") {
|
||||||
|
documentLabel = "New Note";
|
||||||
|
} else {
|
||||||
|
documentLabel = documentTitle || subSection;
|
||||||
|
}
|
||||||
|
|
||||||
breadcrumbs.push({
|
breadcrumbs.push({
|
||||||
label: t("documents"),
|
label: t("documents"),
|
||||||
href: `/dashboard/${segments[1]}/documents`,
|
href: `/dashboard/${segments[1]}/documents`,
|
||||||
|
|
|
||||||
|
|
@ -138,13 +138,13 @@ export const iconMap: Record<string, LucideIcon> = {
|
||||||
MessageCircleMore,
|
MessageCircleMore,
|
||||||
Settings2,
|
Settings2,
|
||||||
SquareLibrary,
|
SquareLibrary,
|
||||||
|
FileText,
|
||||||
SquareTerminal,
|
SquareTerminal,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Info,
|
Info,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Trash2,
|
Trash2,
|
||||||
Podcast,
|
Podcast,
|
||||||
FileText,
|
|
||||||
Users,
|
Users,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Share,
|
Share,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
Eye,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
@ -149,9 +150,9 @@ export function NavNotes({ notes, onAddNote, defaultOpen = true }: NavNotesProps
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
<SidebarGroup className="group-data-[collapsible=icon]:hidden relative">
|
||||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<div className="flex items-center justify-between group/header">
|
<div className="flex items-center group/header relative">
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<SidebarGroupLabel className="cursor-pointer rounded-md px-2 py-1.5 -mx-2 transition-colors flex items-center gap-1.5">
|
<SidebarGroupLabel className="cursor-pointer rounded-md px-2 py-1.5 -mx-2 transition-colors flex items-center gap-1.5">
|
||||||
<ChevronRight
|
<ChevronRight
|
||||||
|
|
@ -164,19 +165,33 @@ export function NavNotes({ notes, onAddNote, defaultOpen = true }: NavNotesProps
|
||||||
</span>
|
</span>
|
||||||
</SidebarGroupLabel>
|
</SidebarGroupLabel>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
{onAddNote && (
|
<div className="absolute top-1.5 right-1 flex items-center gap-0.5 opacity-0 group-hover/header:opacity-100 transition-opacity">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onAddNote();
|
// Add your view all handler here
|
||||||
|
// router.push('/dashboard/[search_space_id]/notes');
|
||||||
}}
|
}}
|
||||||
className="opacity-0 group-hover/header:opacity-100 transition-opacity p-1 hover:bg-sidebar-accent rounded-md -mr-2 shrink-0"
|
aria-label="View all notes"
|
||||||
aria-label="Add note"
|
className="text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden relative"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5 text-muted-foreground" />
|
<Eye className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
{onAddNote && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onAddNote();
|
||||||
|
}}
|
||||||
|
aria-label="Add note"
|
||||||
|
className="text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden relative"
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
IconTicket,
|
IconTicket,
|
||||||
IconWorldWww,
|
IconWorldWww,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { File, Globe, Link, Microscope, Search, Sparkles, Telescope, Webhook } from "lucide-react";
|
import { File, FileText, Globe, Link, Microscope, Search, Sparkles, Telescope, Webhook } from "lucide-react";
|
||||||
import { EnumConnectorName } from "./connector";
|
import { EnumConnectorName } from "./connector";
|
||||||
|
|
||||||
export const getConnectorIcon = (connectorType: EnumConnectorName | string, className?: string) => {
|
export const getConnectorIcon = (connectorType: EnumConnectorName | string, className?: string) => {
|
||||||
|
|
@ -71,6 +71,8 @@ export const getConnectorIcon = (connectorType: EnumConnectorName | string, clas
|
||||||
return <IconBrandYoutube {...iconProps} />;
|
return <IconBrandYoutube {...iconProps} />;
|
||||||
case "FILE":
|
case "FILE":
|
||||||
return <File {...iconProps} />;
|
return <File {...iconProps} />;
|
||||||
|
case "NOTE":
|
||||||
|
return <FileText {...iconProps} />;
|
||||||
case "EXTENSION":
|
case "EXTENSION":
|
||||||
return <Webhook {...iconProps} />;
|
return <Webhook {...iconProps} />;
|
||||||
case "DEEP":
|
case "DEEP":
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue