"use client"; import { useCallback, useEffect, useState, useRef, Dispatch, SetStateAction, } from "react"; import { Editor as InkeEditor } from "inkejs"; import { JSONContent } from "@tiptap/react"; import useLocalStorage from "@/lib/hooks/use-local-storage"; import { useDebouncedCallback } from "use-debounce"; import { Content_Storage_Key, Default_Debounce_Duration } from "@/lib/consts"; import { ContentItem } from "@/lib/types/note"; import { exportAsJson, exportAsMarkdownFile, fetcher, fomatTmpDate, timeAgo, } from "@/lib/utils"; import Menu from "@/ui/menu"; import UINotFound from "@/ui/layout/not-found"; import { toPng } from "html-to-image"; import { usePDF } from "react-to-pdf"; import { Session } from "next-auth"; import { IResponse } from "@/lib/types/response"; import { ShareNote } from "@prisma/client"; import { LoadingCircle, LoadingDots } from "@/ui/shared/icons"; import { BadgeInfo, ExternalLink, Shapes, Clipboard } from "lucide-react"; import toast, { Toaster } from "react-hot-toast"; import { useCollaborationByLocalId, useCollaborationRoomId, useUserInfoByEmail, useUserShareNotes, } from "./request"; import Link from "next/link"; import Tooltip from "@/ui/shared/tooltip"; import { useSearchParams } from "next/navigation"; import db, { noteTable, updateNote } from "@/store/db.model"; export default function Editor({ id, session, contents, setShowRoomModal, }: { id?: string; session: Session | null; contents: ContentItem[]; setShowRoomModal: Dispatch>; }) { const params = useSearchParams(); const [collaboration, setCollaboration] = useState(false); const ref = useRef(null); const [debounceDuration, setDebounceDuration] = useState( Default_Debounce_Duration, ); const [saveStatus, setSaveStatus] = useState("Saved"); const [isLoading, setLoading] = useState(true); const [isSharing, setSharing] = useState(false); const [isShowShareLink, setShowShareLink] = useState(false); const [currentRoomId, setCurrentRoomId] = useState(""); const [currentIndex, setCurrentIndex] = useState(-1); const [currentContent, setCurrentContent] = useLocalStorage( Content_Storage_Key, {}, ); const [currentPureContent, setPureContent] = useState(""); const { shares } = useUserShareNotes(); const { user } = useUserInfoByEmail(session?.user.email); const { room, isLoading: isLoadingRoom } = useCollaborationRoomId( params.get("work"), ); const { room: localRoom } = useCollaborationByLocalId(id); const { toPDF, targetRef } = usePDF({ filename: "note.pdf" }); useEffect(() => { const roomId = params.get("work"); if (roomId) { if (room && room.code === 200) { setCurrentRoomId(roomId); setCollaboration(true); } if (id && contents.length > 0) { const index = contents.findIndex((item) => item.id === id); if (index !== -1 && contents[index]) { setCurrentContent({}); setCurrentIndex(index); document.title = "Space | Inke"; } } } else { if (id && contents.length > 0) { setLoading(true); const index = contents.findIndex((item) => item.id === id); if (index !== -1 && contents[index]) { setCurrentContent(contents[index].content ?? {}); setCurrentIndex(index); document.title = `${contents[index].title} | Inke`; } } } setLoading(false); }, [id, contents, room]); const debouncedUpdates = useDebouncedCallback( async (value, text, markdown) => { handleUpdateItem(id, value); setPureContent(markdown); }, debounceDuration, ); const handleUpdateItem = (id: string, updatedContent: JSONContent) => { if (currentIndex !== -1) { updateNote({ ...contents[currentIndex], content: updatedContent, updated_at: new Date().getTime(), }); } }; const handleExportImage = useCallback(() => { if (ref.current === null || currentIndex === -1 || saveStatus !== "Saved") { return; } toPng(ref.current, { cacheBust: true, width: ref.current.scrollWidth, height: ref.current.scrollHeight, }) .then((dataUrl) => { const link = document.createElement("a"); link.download = contents[currentIndex].title + ".png"; link.href = dataUrl; link.click(); }) .catch((err) => { console.log(err); }); }, [ref, currentIndex, contents]); const handleExportJson = () => { if (!contents || currentIndex === -1 || saveStatus !== "Saved") return; exportAsJson(contents[currentIndex], contents[currentIndex].title); }; const handleExportMarkdown = () => { if ( currentPureContent.length === 0 || currentIndex === -1 || saveStatus !== "Saved" ) return; exportAsMarkdownFile(currentPureContent, contents[currentIndex].title); }; const handleExportPDF = () => { toPDF(); }; const handleCreateShare = async () => { if (saveStatus !== "Saved") return; setSharing(true); const res = await fetcher>("/api/share", { method: "POST", body: JSON.stringify({ data: contents[currentIndex], }), }); if (res.code !== 200) { toast(res.msg, { icon: "😅", }); } else { toast.success(res.msg, { icon: "🎉", }); if (!isShowShareLink) setShowShareLink(true); } setSharing(false); }; const handleCreateCollaboration = async () => { // 用户当前本地笔记是否已加入协作 if (localRoom && localRoom.code === 200) return; if (!currentRoomId) { setShowRoomModal(true); } else if (currentRoomId && !collaboration) { // url有roomid但是没有加入 console.log("url有roomid但是没有加入", room); } else if (currentRoomId && collaboration) { // url有roomid且已经加入 return; } }; if (isLoading || (params.get("work") && isLoadingRoom)) return (
); if (params.get("work") && room.code !== 200) return ( <>

Wrong collaboration space

You are accessing a multiplayer collaboration space, but there seems to be an unexpected issue:{" "} {room.msg}. Please check your space id ({params.get("work")}) and try it again.

); return ( <>
Created at{" "} {currentIndex !== -1 && fomatTmpDate(contents[currentIndex]?.created_at || 0)}
{saveStatus}{" "} {saveStatus === "Saved" && currentIndex !== -1 && timeAgo(contents[currentIndex]?.updated_at || 0)}

Collaborative Space

{collaboration && room && room.data && ( { navigator.clipboard.writeText( `https://inke.app/invite/${room.data.id}`, ); toast("Copied to clipboard"); }} className="h-4 w-4 cursor-pointer text-cyan-500 hover:text-slate-300 active:text-green-500 " /> )}
{collaboration && room && room.data ? (

This note has enabled multi person collaboration, Copy the{" "} invite link {" "} to invite others to join the collaboration.

) : localRoom && localRoom.code === 200 ? (

This local note is already associated with a collaboration space. Click the link below to jump to the collaboration:{" "} space-{localRoom.data.roomId}

) : (

Now, Inke supports collaborative editing of docs by multiple team members. Start by creating collaborative space. Learn more about{" "} collaboration space .
Note: You need to{" "} sign in first to try this feature.

)}
} fullWidth={false} >
{collaboration && room && room.data ? ( ) : ( )}
{((shares && shares.data && shares.data.find((i) => i.localId === id)) || isShowShareLink) && ( )}

Publish and Share

Click the `Publish` button to save your note remotely and generate a sharing link, allowing you to share your notes with others. Your notes will be uploaded after serialization. e.g{" "} link .

You need to sign in first to try this feature.

} fullWidth={false} > {id && currentIndex === -1 && !isLoading && (collaboration && room && room.data ? (

Sync collaboration space

It seems that you have joined this collaboration space ( {room.data.title}), but this device has not been created. Copy this{" "} invite link {" "} and recreate a local record to enter.

) : ( ))} {contents && currentIndex !== -1 && (
{params.get("work") ? ( setSaveStatus("Unsaved")} onDebouncedUpdate={( json: JSONContent, text: string, markdown: string, ) => { setSaveStatus("Saving..."); if (json) debouncedUpdates(json, text, markdown); setTimeout(() => { setSaveStatus("Saved"); }, 500); }} /> ) : ( setSaveStatus("Unsaved")} onDebouncedUpdate={( json: JSONContent, text: string, markdown: string, ) => { setSaveStatus("Saving..."); if (json) debouncedUpdates(json, text, markdown); setTimeout(() => { setSaveStatus("Saved"); }, 500); }} /> )}
)} ); }