feat: Excel/Word edit, PDF annotations, FileCard redesign & AI fixes
UI improvements: - ExcelPreview: white Excel-like design (green tabs, gray headers), inline cell editing, XLSX save - WordPreview: Tiptap rich text editor for edit mode, html-docx-js save as .docx - PdfPreview: persistent SVG annotation overlay (highlight/note/pen) saved to DB - FileCard: tag color dots, favorite star, colored bg per file type, relative dates Layout fixes: - AppShell: h-screen overflow-hidden for sticky sidebar, h-full on columns - PreviewEngine: lg:left-64 offset in fullscreen to clear sidebar - WordTiptapEditor: absolute inset-0 layout so toolbar stays visible in fullscreen - Navigation arrows hidden while editing AI fixes: - /api/ai/files: fix session reading (NextResponse.next → new NextResponse), add try/catch - /api/ai/chat: buildFileBlocks now async; adds Word (mammoth) and Excel (xlsx) extraction - AiPanel: MarkdownContent component with remark-gfm + react-syntax-highlighter (Prism) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
16 changed files +2,677 −196
modified
app/api/ai/chat/route.ts
+39 −5
@@ -29,12 +29,12 @@ async function findMentionedFile(message: string, userId?: string) { | ||
| 29 | 29 | } |
| 30 | 30 | |
| 31 | 31 | // Build content blocks for a file to send to Claude |
| 32 | −function buildFileBlocks(file: { | |
| 32 | +async function buildFileBlocks(file: { | |
| 33 | 33 | name: string; |
| 34 | 34 | mimeType: string; |
| 35 | 35 | storagePath: string; |
| 36 | 36 | ocrText: string | null; |
| 37 | −}): Anthropic.ContentBlockParam[] { | |
| 37 | +}): Promise<Anthropic.ContentBlockParam[]> { | |
| 38 | 38 | const filePath = getFilePath(file.storagePath); |
| 39 | 39 | if (!existsSync(filePath)) return []; |
| 40 | 40 | |
@@ -50,7 +50,7 @@ function buildFileBlocks(file: { | ||
| 50 | 50 | ]; |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | − // Images — send as image block | |
| 53 | + // Images — send as image block (base64) | |
| 54 | 54 | if (file.mimeType.startsWith("image/")) { |
| 55 | 55 | const supported = ["image/jpeg", "image/png", "image/gif", "image/webp"]; |
| 56 | 56 | if (supported.includes(file.mimeType)) { |
@@ -68,6 +68,38 @@ function buildFileBlocks(file: { | ||
| 68 | 68 | } |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | + // Word documents — extract text via mammoth | |
| 72 | + if ( | |
| 73 | + file.mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || | |
| 74 | + file.mimeType === "application/msword" | |
| 75 | + ) { | |
| 76 | + const mammoth = await import("mammoth"); | |
| 77 | + const result = await mammoth.extractRawText({ path: filePath }); | |
| 78 | + const text = result.value.substring(0, 50000); | |
| 79 | + if (text.trim()) { | |
| 80 | + return [{ type: "text", text: `Contenu du document Word "${file.name}":\n${text}` }]; | |
| 81 | + } | |
| 82 | + } | |
| 83 | + | |
| 84 | + // Excel spreadsheets — extract as CSV via SheetJS | |
| 85 | + if ( | |
| 86 | + file.mimeType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || | |
| 87 | + file.mimeType === "application/vnd.ms-excel" | |
| 88 | + ) { | |
| 89 | + const XLSX = await import("xlsx"); | |
| 90 | + const workbook = XLSX.readFile(filePath); | |
| 91 | + const parts: string[] = []; | |
| 92 | + for (const sheetName of workbook.SheetNames) { | |
| 93 | + const sheet = workbook.Sheets[sheetName]; | |
| 94 | + const csv = XLSX.utils.sheet_to_csv(sheet); | |
| 95 | + parts.push(`[Feuille: ${sheetName}]\n${csv.substring(0, 15000)}`); | |
| 96 | + } | |
| 97 | + const content = parts.join("\n\n").substring(0, 50000); | |
| 98 | + if (content.trim()) { | |
| 99 | + return [{ type: "text", text: `Contenu du tableur Excel "${file.name}":\n${content}` }]; | |
| 100 | + } | |
| 101 | + } | |
| 102 | + | |
| 71 | 103 | // Text / code / markdown / JSON — read directly |
| 72 | 104 | if ( |
| 73 | 105 | file.mimeType.startsWith("text/") || |
@@ -87,7 +119,9 @@ function buildFileBlocks(file: { | ||
| 87 | 119 | { type: "text", text: `Texte extrait du fichier "${file.name}" (OCR):\n${file.ocrText}` }, |
| 88 | 120 | ]; |
| 89 | 121 | } |
| 90 | − } catch {} | |
| 122 | + } catch (err) { | |
| 123 | + console.error("[chat] buildFileBlocks error:", err); | |
| 124 | + } | |
| 91 | 125 | return []; |
| 92 | 126 | } |
| 93 | 127 | |
@@ -112,7 +146,7 @@ ${fileContext}`; | ||
| 112 | 146 | // Build user message — attach file content if a file is mentioned |
| 113 | 147 | let userContent: Anthropic.ContentBlockParam[]; |
| 114 | 148 | if (mentionedFile) { |
| 115 | − const fileBlocks = buildFileBlocks(mentionedFile); | |
| 149 | + const fileBlocks = await buildFileBlocks(mentionedFile); | |
| 116 | 150 | if (fileBlocks.length > 0) { |
| 117 | 151 | userContent = [...fileBlocks, { type: "text", text: message }]; |
| 118 | 152 | } else { |
modified
app/api/ai/files/route.ts
+27 −23
@@ -4,7 +4,7 @@ import { SessionData, sessionOptions } from "@/lib/session"; | ||
| 4 | 4 | import { prisma } from "@/lib/prisma"; |
| 5 | 5 | |
| 6 | 6 | export async function GET(request: NextRequest) { |
| 7 | − const response = NextResponse.next(); | |
| 7 | + const response = new NextResponse(); | |
| 8 | 8 | const session = await getIronSession<SessionData>(request, response, sessionOptions); |
| 9 | 9 | if (!session.isLoggedIn) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); |
| 10 | 10 | |
@@ -16,26 +16,30 @@ export async function GET(request: NextRequest) { | ||
| 16 | 16 | ? { OR: [{ userId: session.userId as string }, { userId: null as null }] } |
| 17 | 17 | : {}; |
| 18 | 18 | |
| 19 | − const files = await prisma.file.findMany({ | |
| 20 | − where: { | |
| 21 | − AND: [ | |
| 22 | − ownerFilter, | |
| 23 | − ...(q ? [{ name: { contains: q } }] : []), | |
| 24 | − ], | |
| 25 | − }, | |
| 26 | − select: { | |
| 27 | − id: true, | |
| 28 | − name: true, | |
| 29 | − mimeType: true, | |
| 30 | − size: true, | |
| 31 | − aiTags: true, | |
| 32 | − aiDescription: true, | |
| 33 | − ocrText: true, | |
| 34 | − folder: { select: { name: true } }, | |
| 35 | − }, | |
| 36 | − take: 30, | |
| 37 | − orderBy: { createdAt: "desc" }, | |
| 38 | − }); | |
| 39 | − | |
| 40 | − return NextResponse.json(files); | |
| 19 | + try { | |
| 20 | + const files = await prisma.file.findMany({ | |
| 21 | + where: { | |
| 22 | + AND: [ | |
| 23 | + ownerFilter, | |
| 24 | + ...(q ? [{ name: { contains: q } }] : []), | |
| 25 | + ], | |
| 26 | + }, | |
| 27 | + select: { | |
| 28 | + id: true, | |
| 29 | + name: true, | |
| 30 | + mimeType: true, | |
| 31 | + size: true, | |
| 32 | + aiTags: true, | |
| 33 | + aiDescription: true, | |
| 34 | + ocrText: true, | |
| 35 | + folder: { select: { name: true } }, | |
| 36 | + }, | |
| 37 | + take: 30, | |
| 38 | + orderBy: { createdAt: "desc" }, | |
| 39 | + }); | |
| 40 | + return NextResponse.json(files); | |
| 41 | + } catch (err) { | |
| 42 | + console.error("[ai/files] Prisma error:", err); | |
| 43 | + return NextResponse.json({ error: "Failed to fetch files" }, { status: 500 }); | |
| 44 | + } | |
| 41 | 45 | } |
modified
components/ai/AiPanel.tsx
+57 −9
@@ -23,6 +23,9 @@ import { | ||
| 23 | 23 | AlertCircle, |
| 24 | 24 | } from "lucide-react"; |
| 25 | 25 | import ReactMarkdown from "react-markdown"; |
| 26 | +import remarkGfm from "remark-gfm"; | |
| 27 | +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; | |
| 28 | +import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; | |
| 26 | 29 | |
| 27 | 30 | interface AiResult { |
| 28 | 31 | fileId: string; |
@@ -72,6 +75,57 @@ const mimeIcon = (mime: string) => { | ||
| 72 | 75 | const inputClasses = |
| 73 | 76 | "w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-4 py-3 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-purple-400 dark:focus:border-purple-500 placeholder-gray-400 dark:placeholder-gray-500"; |
| 74 | 77 | |
| 78 | +// Markdown renderer with code syntax highlighting | |
| 79 | +function MarkdownContent({ content }: { content: string }) { | |
| 80 | + return ( | |
| 81 | + <div className="prose prose-sm dark:prose-invert max-w-none prose-p:my-1 prose-ul:my-1.5 prose-ol:my-1.5 prose-li:my-0.5 prose-headings:my-2 prose-blockquote:my-1 prose-pre:p-0 prose-pre:bg-transparent prose-pre:my-2 prose-code:before:content-none prose-code:after:content-none prose-table:text-xs prose-th:py-1 prose-td:py-1"> | |
| 82 | + <ReactMarkdown | |
| 83 | + remarkPlugins={[remarkGfm]} | |
| 84 | + components={{ | |
| 85 | + code({ className, children, ...props }) { | |
| 86 | + const match = /language-(\w+)/.exec(className || ""); | |
| 87 | + const codeStr = String(children).replace(/\n$/, ""); | |
| 88 | + const isBlock = match || codeStr.includes("\n"); | |
| 89 | + if (isBlock) { | |
| 90 | + return ( | |
| 91 | + <SyntaxHighlighter | |
| 92 | + style={oneDark as Record<string, React.CSSProperties>} | |
| 93 | + language={match?.[1] || "text"} | |
| 94 | + PreTag="div" | |
| 95 | + className="!rounded-lg !text-xs !my-0 !bg-gray-900" | |
| 96 | + customStyle={{ margin: 0, borderRadius: "0.5rem", fontSize: "0.75rem" }} | |
| 97 | + > | |
| 98 | + {codeStr} | |
| 99 | + </SyntaxHighlighter> | |
| 100 | + ); | |
| 101 | + } | |
| 102 | + return ( | |
| 103 | + <code | |
| 104 | + className="bg-gray-200 dark:bg-white/15 px-1.5 py-0.5 rounded text-[11px] font-mono text-purple-700 dark:text-purple-300" | |
| 105 | + {...props} | |
| 106 | + > | |
| 107 | + {children} | |
| 108 | + </code> | |
| 109 | + ); | |
| 110 | + }, | |
| 111 | + pre({ children }) { | |
| 112 | + return <>{children}</>; | |
| 113 | + }, | |
| 114 | + a({ href, children }) { | |
| 115 | + return ( | |
| 116 | + <a href={href} target="_blank" rel="noopener noreferrer" className="text-purple-500 underline underline-offset-2 hover:text-purple-400"> | |
| 117 | + {children} | |
| 118 | + </a> | |
| 119 | + ); | |
| 120 | + }, | |
| 121 | + }} | |
| 122 | + > | |
| 123 | + {content} | |
| 124 | + </ReactMarkdown> | |
| 125 | + </div> | |
| 126 | + ); | |
| 127 | +} | |
| 128 | + | |
| 75 | 129 | export default function AiPanel() { |
| 76 | 130 | const [open, setOpen] = useState(false); |
| 77 | 131 | const [tab, setTab] = useState<Tab>("chat"); |
@@ -384,9 +438,7 @@ export default function AiPanel() { | ||
| 384 | 438 | }`} |
| 385 | 439 | > |
| 386 | 440 | {msg.role === "assistant" ? ( |
| 387 | − <div className="prose prose-sm dark:prose-invert max-w-none prose-p:my-1 prose-ul:my-1 prose-li:my-0.5 prose-headings:my-1"> | |
| 388 | − <ReactMarkdown>{msg.content}</ReactMarkdown> | |
| 389 | − </div> | |
| 441 | + <MarkdownContent content={msg.content} /> | |
| 390 | 442 | ) : ( |
| 391 | 443 | msg.content |
| 392 | 444 | )} |
@@ -401,9 +453,7 @@ export default function AiPanel() { | ||
| 401 | 453 | </div> |
| 402 | 454 | <div className="max-w-[82%] px-3 py-2.5 rounded-2xl rounded-tl-sm bg-gray-100 dark:bg-white/10 text-sm text-gray-900 dark:text-gray-100 leading-relaxed"> |
| 403 | 455 | {streamingContent ? ( |
| 404 | − <div className="prose prose-sm dark:prose-invert max-w-none prose-p:my-1"> | |
| 405 | − <ReactMarkdown>{streamingContent}</ReactMarkdown> | |
| 406 | − </div> | |
| 456 | + <MarkdownContent content={streamingContent} /> | |
| 407 | 457 | ) : ( |
| 408 | 458 | <span className="flex items-center gap-1"> |
| 409 | 459 | {[0, 150, 300].map((d) => ( |
@@ -729,9 +779,7 @@ export default function AiPanel() { | ||
| 729 | 779 | {analyserResult && ( |
| 730 | 780 | <div className="bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg p-3.5"> |
| 731 | 781 | <p className="text-xs font-medium text-gray-500 uppercase tracking-wider mb-2">{analyserResult.type}</p> |
| 732 | − <div className="prose prose-sm dark:prose-invert max-w-none prose-p:my-1"> | |
| 733 | − <ReactMarkdown>{analyserResult.content}</ReactMarkdown> | |
| 734 | − </div> | |
| 782 | + <MarkdownContent content={analyserResult.content} /> | |
| 735 | 783 | </div> |
| 736 | 784 | )} |
| 737 | 785 | </div> |
modified
components/files/FileCard.tsx
+120 −53
@@ -11,19 +11,12 @@ import { | ||
| 11 | 11 | File, |
| 12 | 12 | Archive, |
| 13 | 13 | MoreVertical, |
| 14 | + Star, | |
| 14 | 15 | } from "lucide-react"; |
| 15 | −import { formatFileSize, formatDate, getFileIcon } from "@/lib/utils"; | |
| 16 | +import { formatFileSize, getFileIcon } from "@/lib/utils"; | |
| 16 | 17 | import { useState, useRef, useEffect } from "react"; |
| 17 | 18 | import FileActions from "./FileActions"; |
| 18 | − | |
| 19 | −interface FileData { | |
| 20 | − id: string; | |
| 21 | − name: string; | |
| 22 | − size: number; | |
| 23 | − mimeType: string; | |
| 24 | − createdAt: string; | |
| 25 | − folderId?: string | null; | |
| 26 | −} | |
| 19 | +import type { FileData } from "@/types/files"; | |
| 27 | 20 | |
| 28 | 21 | interface FileCardProps { |
| 29 | 22 | file: FileData; |
@@ -46,25 +39,59 @@ const iconMap: Record<string, React.ComponentType<{ className?: string }>> = { | ||
| 46 | 39 | }; |
| 47 | 40 | |
| 48 | 41 | const iconColorMap: Record<string, string> = { |
| 49 | − image: "text-emerald-400 dark:text-emerald-500", | |
| 50 | − video: "text-purple-400 dark:text-purple-500", | |
| 51 | − audio: "text-pink-400 dark:text-pink-500", | |
| 52 | − pdf: "text-red-400 dark:text-red-500", | |
| 53 | − code: "text-amber-400 dark:text-amber-500", | |
| 54 | − spreadsheet: "text-green-500 dark:text-green-400", | |
| 55 | − archive: "text-orange-400 dark:text-orange-500", | |
| 56 | − document: "text-blue-500 dark:text-blue-400", | |
| 57 | − presentation: "text-orange-500 dark:text-orange-400", | |
| 58 | − file: "text-gray-400 dark:text-gray-500", | |
| 42 | + image: "text-emerald-500", | |
| 43 | + video: "text-purple-500", | |
| 44 | + audio: "text-pink-500", | |
| 45 | + pdf: "text-red-500", | |
| 46 | + code: "text-amber-500", | |
| 47 | + spreadsheet: "text-green-600", | |
| 48 | + archive: "text-orange-500", | |
| 49 | + document: "text-blue-500", | |
| 50 | + presentation: "text-orange-500", | |
| 51 | + file: "text-gray-400", | |
| 52 | +}; | |
| 53 | + | |
| 54 | +// Soft tinted bg per file type (light + dark) | |
| 55 | +const bgTintMap: Record<string, string> = { | |
| 56 | + image: "bg-emerald-50 dark:bg-emerald-950/30", | |
| 57 | + video: "bg-purple-50 dark:bg-purple-950/30", | |
| 58 | + audio: "bg-pink-50 dark:bg-pink-950/30", | |
| 59 | + pdf: "bg-red-50 dark:bg-red-950/30", | |
| 60 | + code: "bg-amber-50 dark:bg-amber-950/30", | |
| 61 | + spreadsheet: "bg-green-50 dark:bg-green-950/30", | |
| 62 | + archive: "bg-orange-50 dark:bg-orange-950/30", | |
| 63 | + document: "bg-blue-50 dark:bg-blue-950/30", | |
| 64 | + presentation: "bg-orange-50 dark:bg-orange-950/30", | |
| 65 | + file: "bg-gray-50 dark:bg-white/[0.03]", | |
| 59 | 66 | }; |
| 60 | 67 | |
| 68 | +function formatRelativeDate(dateStr: string): string { | |
| 69 | + const now = Date.now(); | |
| 70 | + const then = new Date(dateStr).getTime(); | |
| 71 | + const diff = now - then; | |
| 72 | + const minutes = Math.floor(diff / 60000); | |
| 73 | + const hours = Math.floor(diff / 3600000); | |
| 74 | + const days = Math.floor(diff / 86400000); | |
| 75 | + const months = Math.floor(days / 30); | |
| 76 | + if (minutes < 1) return "à l'instant"; | |
| 77 | + if (minutes < 60) return `il y a ${minutes}m`; | |
| 78 | + if (hours < 24) return `il y a ${hours}h`; | |
| 79 | + if (days < 30) return `il y a ${days}j`; | |
| 80 | + if (months < 12) return `il y a ${months} mois`; | |
| 81 | + return new Date(dateStr).toLocaleDateString("fr-CA", { year: "numeric", month: "short" }); | |
| 82 | +} | |
| 83 | + | |
| 61 | 84 | export default function FileCard({ file, onPreview, onRefresh, viewMode }: FileCardProps) { |
| 62 | 85 | const [showActions, setShowActions] = useState(false); |
| 63 | 86 | const menuRef = useRef<HTMLDivElement>(null); |
| 64 | 87 | |
| 65 | 88 | const iconType = getFileIcon(file.mimeType); |
| 66 | 89 | const Icon = iconMap[iconType] || File; |
| 67 | − const iconColor = iconColorMap[iconType] || "text-gray-400 dark:text-gray-500"; | |
| 90 | + const iconColor = iconColorMap[iconType] || "text-gray-400"; | |
| 91 | + const bgTint = bgTintMap[iconType] || "bg-gray-50 dark:bg-white/[0.03]"; | |
| 92 | + const isImage = file.mimeType.startsWith("image/"); | |
| 93 | + const isFavorite = file.metadata?.isFavorite; | |
| 94 | + const tags = file.tags ?? []; | |
| 68 | 95 | |
| 69 | 96 | useEffect(() => { |
| 70 | 97 | function handleClick(e: MouseEvent) { |
@@ -76,42 +103,79 @@ export default function FileCard({ file, onPreview, onRefresh, viewMode }: FileC | ||
| 76 | 103 | return () => document.removeEventListener("mousedown", handleClick); |
| 77 | 104 | }, []); |
| 78 | 105 | |
| 79 | − const isImage = file.mimeType.startsWith("image/"); | |
| 106 | + // Tags as color dots — max 4 visible, then +N | |
| 107 | + function TagDots() { | |
| 108 | + if (tags.length === 0) return null; | |
| 109 | + const visible = tags.slice(0, 4); | |
| 110 | + const overflow = tags.length - 4; | |
| 111 | + return ( | |
| 112 | + <div className="flex items-center gap-1 mt-1.5 flex-wrap"> | |
| 113 | + {visible.map(({ tag }) => ( | |
| 114 | + <span | |
| 115 | + key={tag.id} | |
| 116 | + className="w-2.5 h-2.5 rounded-full flex-shrink-0 border border-white/20" | |
| 117 | + style={{ backgroundColor: tag.color }} | |
| 118 | + title={tag.name} | |
| 119 | + /> | |
| 120 | + ))} | |
| 121 | + {overflow > 0 && ( | |
| 122 | + <span className="text-[10px] text-gray-400 leading-none">+{overflow}</span> | |
| 123 | + )} | |
| 124 | + </div> | |
| 125 | + ); | |
| 126 | + } | |
| 80 | 127 | |
| 81 | 128 | if (viewMode === "list") { |
| 82 | 129 | return ( |
| 83 | 130 | <motion.div |
| 84 | 131 | initial={{ opacity: 0 }} |
| 85 | 132 | animate={{ opacity: 1 }} |
| 86 | − className="flex items-center gap-3 px-3 py-3.5 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg cursor-pointer group transition-colors" | |
| 133 | + className="flex items-center gap-3 px-3 py-3 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg cursor-pointer group transition-colors" | |
| 87 | 134 | onClick={() => onPreview(file)} |
| 88 | 135 | > |
| 89 | − <Icon className={`w-5 h-5 flex-shrink-0 ${iconColor}`} /> | |
| 90 | − <span className="text-sm text-gray-700 dark:text-gray-200 flex-1 truncate">{file.name}</span> | |
| 91 | − <span className="text-sm text-gray-500 hidden sm:block">{formatFileSize(file.size)}</span> | |
| 92 | − <span className="text-xs text-gray-500 hidden md:block">{formatDate(file.createdAt)}</span> | |
| 93 | − <div className="relative" ref={menuRef}> | |
| 136 | + {/* Icon */} | |
| 137 | + <div className={`w-8 h-8 rounded-md flex items-center justify-center flex-shrink-0 ${bgTint}`}> | |
| 138 | + <Icon className={`w-4 h-4 ${iconColor}`} /> | |
| 139 | + </div> | |
| 140 | + | |
| 141 | + {/* Name */} | |
| 142 | + <span className="text-sm text-gray-700 dark:text-gray-200 flex-1 truncate min-w-0">{file.name}</span> | |
| 143 | + | |
| 144 | + {/* Tags dots (list) */} | |
| 145 | + {tags.length > 0 && ( | |
| 146 | + <div className="hidden sm:flex items-center gap-1 flex-shrink-0"> | |
| 147 | + {tags.slice(0, 3).map(({ tag }) => ( | |
| 148 | + <span | |
| 149 | + key={tag.id} | |
| 150 | + className="w-2 h-2 rounded-full" | |
| 151 | + style={{ backgroundColor: tag.color }} | |
| 152 | + title={tag.name} | |
| 153 | + /> | |
| 154 | + ))} | |
| 155 | + </div> | |
| 156 | + )} | |
| 157 | + | |
| 158 | + {isFavorite && <Star className="w-3.5 h-3.5 text-amber-400 fill-amber-400 flex-shrink-0 hidden sm:block" />} | |
| 159 | + <span className="text-sm text-gray-500 hidden sm:block flex-shrink-0">{formatFileSize(file.size)}</span> | |
| 160 | + <span className="text-xs text-gray-400 hidden md:block flex-shrink-0 min-w-[80px] text-right">{formatRelativeDate(file.createdAt)}</span> | |
| 161 | + | |
| 162 | + {/* Actions menu */} | |
| 163 | + <div className="relative flex-shrink-0" ref={menuRef}> | |
| 94 | 164 | <button |
| 95 | − onClick={(e) => { | |
| 96 | − e.stopPropagation(); | |
| 97 | − setShowActions(!showActions); | |
| 98 | − }} | |
| 165 | + onClick={(e) => { e.stopPropagation(); setShowActions(!showActions); }} | |
| 99 | 166 | className="min-w-[44px] min-h-[44px] flex items-center justify-center text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-white sm:opacity-0 sm:group-hover:opacity-100 transition-opacity" |
| 100 | 167 | > |
| 101 | 168 | <MoreVertical className="w-4 h-4" /> |
| 102 | 169 | </button> |
| 103 | 170 | {showActions && ( |
| 104 | − <FileActions | |
| 105 | − file={file} | |
| 106 | − onClose={() => setShowActions(false)} | |
| 107 | − onRefresh={onRefresh} | |
| 108 | − /> | |
| 171 | + <FileActions file={file} onClose={() => setShowActions(false)} onRefresh={onRefresh} /> | |
| 109 | 172 | )} |
| 110 | 173 | </div> |
| 111 | 174 | </motion.div> |
| 112 | 175 | ); |
| 113 | 176 | } |
| 114 | 177 | |
| 178 | + // Grid view | |
| 115 | 179 | return ( |
| 116 | 180 | <motion.div |
| 117 | 181 | initial={{ opacity: 0, scale: 0.95 }} |
@@ -119,8 +183,8 @@ export default function FileCard({ file, onPreview, onRefresh, viewMode }: FileC | ||
| 119 | 183 | className={`group relative bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl hover:border-gray-300 dark:hover:border-white/20 transition-colors cursor-pointer shadow-sm dark:shadow-none ${showActions ? "z-20" : ""}`} |
| 120 | 184 | onClick={() => onPreview(file)} |
| 121 | 185 | > |
| 122 | − {/* Preview thumbnail — overflow-hidden stays here for image crop only */} | |
| 123 | − <div className="aspect-square flex items-center justify-center bg-gray-50 dark:bg-white/[0.02] rounded-t-xl overflow-hidden"> | |
| 186 | + {/* Thumbnail */} | |
| 187 | + <div className={`aspect-square flex items-center justify-center rounded-t-xl overflow-hidden ${isImage ? "" : bgTint}`}> | |
| 124 | 188 | {isImage ? ( |
| 125 | 189 | <img |
| 126 | 190 | src={`/api/files/${file.id}/preview`} |
@@ -129,36 +193,39 @@ export default function FileCard({ file, onPreview, onRefresh, viewMode }: FileC | ||
| 129 | 193 | loading="lazy" |
| 130 | 194 | /> |
| 131 | 195 | ) : ( |
| 132 | − <Icon className={`w-12 h-12 ${iconColor}`} /> | |
| 196 | + <Icon className={`w-14 h-14 ${iconColor}`} /> | |
| 133 | 197 | )} |
| 134 | 198 | </div> |
| 135 | 199 | |
| 136 | − {/* Actions button — outside overflow-hidden so dropdown is never clipped */} | |
| 200 | + {/* Favorite star — top left */} | |
| 201 | + {isFavorite && ( | |
| 202 | + <div className="absolute top-2 left-2 z-10"> | |
| 203 | + <Star className="w-4 h-4 text-amber-400 fill-amber-400 drop-shadow" /> | |
| 204 | + </div> | |
| 205 | + )} | |
| 206 | + | |
| 207 | + {/* Actions button — top right */} | |
| 137 | 208 | <div className="absolute top-2 right-2 z-20" ref={menuRef}> |
| 138 | 209 | <button |
| 139 | − onClick={(e) => { | |
| 140 | − e.stopPropagation(); | |
| 141 | − setShowActions(!showActions); | |
| 142 | − }} | |
| 210 | + onClick={(e) => { e.stopPropagation(); setShowActions(!showActions); }} | |
| 143 | 211 | className="p-2 bg-white/80 dark:bg-black/50 rounded-lg text-gray-500 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white sm:opacity-0 sm:group-hover:opacity-100 transition-opacity backdrop-blur-sm min-w-[40px] min-h-[40px] flex items-center justify-center" |
| 144 | 212 | > |
| 145 | 213 | <MoreVertical className="w-4 h-4" /> |
| 146 | 214 | </button> |
| 147 | 215 | {showActions && ( |
| 148 | − <FileActions | |
| 149 | − file={file} | |
| 150 | − onClose={() => setShowActions(false)} | |
| 151 | − onRefresh={onRefresh} | |
| 152 | − /> | |
| 216 | + <FileActions file={file} onClose={() => setShowActions(false)} onRefresh={onRefresh} /> | |
| 153 | 217 | )} |
| 154 | 218 | </div> |
| 155 | 219 | |
| 156 | 220 | {/* Info */} |
| 157 | 221 | <div className="p-3"> |
| 158 | − <p className="text-sm text-gray-700 dark:text-gray-200 truncate">{file.name}</p> | |
| 159 | − <p className="text-sm text-gray-500 mt-1"> | |
| 160 | − {formatFileSize(file.size)} | |
| 222 | + <p className="text-sm text-gray-700 dark:text-gray-200 truncate font-medium">{file.name}</p> | |
| 223 | + <p className="text-xs text-gray-500 mt-0.5 flex items-center gap-1.5"> | |
| 224 | + <span>{formatFileSize(file.size)}</span> | |
| 225 | + <span className="text-gray-300 dark:text-gray-700">·</span> | |
| 226 | + <span>{formatRelativeDate(file.createdAt)}</span> | |
| 161 | 227 | </p> |
| 228 | + <TagDots /> | |
| 162 | 229 | </div> |
| 163 | 230 | </motion.div> |
| 164 | 231 | ); |
modified
components/files/FileExplorer.tsx
+1 −9
@@ -12,15 +12,7 @@ import SearchBar from "../ui/SearchBar"; | ||
| 12 | 12 | import Button from "../ui/Button"; |
| 13 | 13 | import BulkActions from "./BulkActions"; |
| 14 | 14 | import { motion, AnimatePresence } from "framer-motion"; |
| 15 | − | |
| 16 | −interface FileData { | |
| 17 | − id: string; | |
| 18 | − name: string; | |
| 19 | − size: number; | |
| 20 | − mimeType: string; | |
| 21 | − createdAt: string; | |
| 22 | − folderId?: string | null; | |
| 23 | −} | |
| 15 | +import type { FileData } from "@/types/files"; | |
| 24 | 16 | |
| 25 | 17 | interface FolderData { |
| 26 | 18 | id: string; |
modified
components/layout/AppShell.tsx
+9 −9
@@ -15,19 +15,19 @@ export default function AppShell({ children }: AppShellProps) { | ||
| 15 | 15 | const [sidebarOpen, setSidebarOpen] = useState(false); |
| 16 | 16 | |
| 17 | 17 | return ( |
| 18 | − <div className="min-h-screen bg-gray-50 dark:bg-[#0a0a0a] flex transition-colors"> | |
| 19 | − {/* Desktop Sidebar */} | |
| 20 | − <div className="hidden lg:block w-64 flex-shrink-0"> | |
| 18 | + <div className="h-screen overflow-hidden bg-gray-50 dark:bg-[#0a0a0a] flex transition-colors"> | |
| 19 | + {/* Desktop Sidebar — fixed height, scrolls independently */} | |
| 20 | + <div className="hidden lg:flex flex-col w-64 flex-shrink-0 h-full overflow-y-auto"> | |
| 21 | 21 | <Sidebar /> |
| 22 | 22 | </div> |
| 23 | 23 | |
| 24 | 24 | {/* Mobile Sidebar Overlay */} |
| 25 | 25 | <MobileNav isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} /> |
| 26 | 26 | |
| 27 | − {/* Main Content */} | |
| 28 | − <div className="flex-1 flex flex-col min-h-screen min-w-0"> | |
| 29 | − {/* Mobile Header - safe area + larger touch targets */} | |
| 30 | − <div className="lg:hidden flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-white/10 bg-white dark:bg-[#0a0a0a] transition-colors safe-top"> | |
| 27 | + {/* Main Content — fills remaining space, scrolls independently */} | |
| 28 | + <div className="flex-1 flex flex-col min-w-0 h-full overflow-hidden"> | |
| 29 | + {/* Mobile Header */} | |
| 30 | + <div className="lg:hidden flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-white/10 bg-white dark:bg-[#0a0a0a] transition-colors safe-top shrink-0"> | |
| 31 | 31 | <button |
| 32 | 32 | onClick={() => setSidebarOpen(true)} |
| 33 | 33 | className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white p-3 -ml-1 min-w-[44px] min-h-[44px] flex items-center justify-center active:bg-gray-100 dark:active:bg-white/10 rounded-xl transition-colors" |
@@ -43,8 +43,8 @@ export default function AppShell({ children }: AppShellProps) { | ||
| 43 | 43 | </div> |
| 44 | 44 | </div> |
| 45 | 45 | |
| 46 | − {/* Content - with safe area bottom for notched devices */} | |
| 47 | − <main className="flex-1 overflow-auto pb-safe"> | |
| 46 | + {/* Content */} | |
| 47 | + <main className="flex-1 overflow-auto pb-safe min-h-0"> | |
| 48 | 48 | {children} |
| 49 | 49 | </main> |
| 50 | 50 | </div> |
modified
components/layout/Sidebar.tsx
+1 −1
@@ -107,7 +107,7 @@ export default function Sidebar({ onNavigate }: SidebarProps) { | ||
| 107 | 107 | const navItems = isAdmin ? [...userNavItems, ...adminNavItems] : userNavItems; |
| 108 | 108 | |
| 109 | 109 | return ( |
| 110 | − <div className="h-screen flex flex-col bg-white dark:bg-[#0a0a0a] border-r border-gray-200 dark:border-white/10 transition-colors"> | |
| 110 | + <div className="h-full flex flex-col bg-white dark:bg-[#0a0a0a] border-r border-gray-200 dark:border-white/10 transition-colors"> | |
| 111 | 111 | {/* Logo */} |
| 112 | 112 | <div className="px-4 py-4 flex items-center gap-2"> |
| 113 | 113 | <div className="w-9 h-9 rounded-xl bg-gray-100 dark:bg-white/10 flex items-center justify-center flex-shrink-0"> |
modified
components/preview/PreviewEngine.tsx
+2 −2
@@ -82,7 +82,7 @@ export default function PreviewEngine({ | ||
| 82 | 82 | animate={{ opacity: 1 }} |
| 83 | 83 | exit={{ opacity: 0 }} |
| 84 | 84 | className={`fixed inset-0 z-50 flex flex-col bg-white dark:bg-[#0a0a0a] transition-colors ${ |
| 85 | − fullscreen ? "" : "lg:relative lg:inset-auto lg:h-full" | |
| 85 | + fullscreen ? "lg:left-64" : "lg:relative lg:inset-auto lg:h-full" | |
| 86 | 86 | }`} |
| 87 | 87 | > |
| 88 | 88 | <PreviewToolbar |
@@ -107,7 +107,7 @@ export default function PreviewEngine({ | ||
| 107 | 107 | fullscreen={fullscreen} |
| 108 | 108 | /> |
| 109 | 109 | |
| 110 | − {files.length > 1 && onNavigate && ( | |
| 110 | + {files.length > 1 && onNavigate && !isEditing && ( | |
| 111 | 111 | <PreviewNavigation |
| 112 | 112 | files={files} |
| 113 | 113 | currentFile={file} |
added
components/preview/plugins/ExcelPreview.tsx
+394 −0
@@ -0,0 +1,394 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useMemo, useCallback } from "react"; | |
| 4 | +import { Loader2, Download, Table2, ArrowUpDown, Save, Edit3 } from "lucide-react"; | |
| 5 | +import type { PreviewPluginProps } from "./types"; | |
| 6 | + | |
| 7 | +// Column index → Excel-style letter (0 → A, 25 → Z, 26 → AA …) | |
| 8 | +function colLetter(n: number): string { | |
| 9 | + let result = ""; | |
| 10 | + let idx = n; | |
| 11 | + do { | |
| 12 | + result = String.fromCharCode(65 + (idx % 26)) + result; | |
| 13 | + idx = Math.floor(idx / 26) - 1; | |
| 14 | + } while (idx >= 0); | |
| 15 | + return result; | |
| 16 | +} | |
| 17 | + | |
| 18 | +interface SheetData { | |
| 19 | + name: string; | |
| 20 | + rows: (string | number | boolean | null)[][]; | |
| 21 | + colCount: number; | |
| 22 | +} | |
| 23 | + | |
| 24 | +interface SortConfig { | |
| 25 | + col: number; | |
| 26 | + dir: "asc" | "desc"; | |
| 27 | +} | |
| 28 | + | |
| 29 | +const PAGE_SIZE = 100; | |
| 30 | + | |
| 31 | +export default function ExcelPreview({ file, isEditing, onSave }: PreviewPluginProps) { | |
| 32 | + const [sheets, setSheets] = useState<SheetData[]>([]); | |
| 33 | + const [activeSheet, setActiveSheet] = useState(0); | |
| 34 | + const [loading, setLoading] = useState(true); | |
| 35 | + const [error, setError] = useState(false); | |
| 36 | + const [saving, setSaving] = useState(false); | |
| 37 | + // editData mirrors sheets data for in-memory editing | |
| 38 | + const [editData, setEditData] = useState<(string | number | null)[][][]>([]); | |
| 39 | + | |
| 40 | + useEffect(() => { | |
| 41 | + let cancelled = false; | |
| 42 | + | |
| 43 | + const parse = async () => { | |
| 44 | + setLoading(true); | |
| 45 | + setError(false); | |
| 46 | + try { | |
| 47 | + const res = await fetch(`/api/files/${file.id}/download`); | |
| 48 | + if (!res.ok) throw new Error("Download failed"); | |
| 49 | + const arrayBuffer = await res.arrayBuffer(); | |
| 50 | + | |
| 51 | + const XLSX = await import("xlsx"); | |
| 52 | + const workbook = XLSX.read(new Uint8Array(arrayBuffer), { type: "array", cellDates: true }); | |
| 53 | + | |
| 54 | + const parsed: SheetData[] = workbook.SheetNames.map((name) => { | |
| 55 | + const ws = workbook.Sheets[name]; | |
| 56 | + const raw = XLSX.utils.sheet_to_json<(string | number | boolean | null)[]>(ws, { | |
| 57 | + header: 1, | |
| 58 | + defval: null, | |
| 59 | + blankrows: true, | |
| 60 | + }); | |
| 61 | + const colCount = raw.reduce((max, row) => Math.max(max, row.length), 0); | |
| 62 | + return { name, rows: raw as (string | number | boolean | null)[][], colCount }; | |
| 63 | + }); | |
| 64 | + | |
| 65 | + if (!cancelled) { | |
| 66 | + setSheets(parsed); | |
| 67 | + setActiveSheet(0); | |
| 68 | + // Init edit data as string copies | |
| 69 | + setEditData(parsed.map((s) => s.rows.map((row) => row.map((c) => (c === null ? null : String(c)))))); | |
| 70 | + } | |
| 71 | + } catch { | |
| 72 | + if (!cancelled) setError(true); | |
| 73 | + } finally { | |
| 74 | + if (!cancelled) setLoading(false); | |
| 75 | + } | |
| 76 | + }; | |
| 77 | + | |
| 78 | + parse(); | |
| 79 | + return () => { cancelled = true; }; | |
| 80 | + }, [file.id]); | |
| 81 | + | |
| 82 | + const handleCellChange = useCallback((sheetIdx: number, rowIdx: number, colIdx: number, value: string) => { | |
| 83 | + setEditData((prev) => { | |
| 84 | + const next = prev.map((s) => s.map((r) => [...r])); | |
| 85 | + // Ensure nested arrays exist | |
| 86 | + while (next[sheetIdx].length <= rowIdx) next[sheetIdx].push([]); | |
| 87 | + while (next[sheetIdx][rowIdx].length <= colIdx) next[sheetIdx][rowIdx].push(null); | |
| 88 | + next[sheetIdx][rowIdx][colIdx] = value; | |
| 89 | + return next; | |
| 90 | + }); | |
| 91 | + }, []); | |
| 92 | + | |
| 93 | + const handleSave = useCallback(async () => { | |
| 94 | + if (!onSave) return; | |
| 95 | + setSaving(true); | |
| 96 | + try { | |
| 97 | + const XLSX = await import("xlsx"); | |
| 98 | + const wb = XLSX.utils.book_new(); | |
| 99 | + editData.forEach((sheetRows, si) => { | |
| 100 | + const ws = XLSX.utils.aoa_to_sheet(sheetRows.map((row) => | |
| 101 | + row.map((c) => { | |
| 102 | + if (c === null || c === "") return null; | |
| 103 | + const num = Number(c); | |
| 104 | + return isNaN(num) ? c : num; | |
| 105 | + }) | |
| 106 | + )); | |
| 107 | + XLSX.utils.book_append_sheet(wb, ws, sheets[si]?.name || `Sheet${si + 1}`); | |
| 108 | + }); | |
| 109 | + const array = XLSX.write(wb, { bookType: "xlsx", type: "array" }); | |
| 110 | + const blob = new Blob([array], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); | |
| 111 | + await onSave(blob, true); | |
| 112 | + } finally { | |
| 113 | + setSaving(false); | |
| 114 | + } | |
| 115 | + }, [editData, sheets, onSave]); | |
| 116 | + | |
| 117 | + if (loading) { | |
| 118 | + return ( | |
| 119 | + <div className="flex flex-col items-center justify-center h-full gap-3 text-gray-400 bg-white"> | |
| 120 | + <Loader2 className="w-8 h-8 animate-spin text-[#217346]" /> | |
| 121 | + <p className="text-sm text-gray-500">Chargement du classeur Excel…</p> | |
| 122 | + </div> | |
| 123 | + ); | |
| 124 | + } | |
| 125 | + | |
| 126 | + if (error || sheets.length === 0) { | |
| 127 | + return ( | |
| 128 | + <div className="flex flex-col items-center justify-center h-full gap-4 text-gray-400 p-8 bg-white"> | |
| 129 | + <Table2 className="w-16 h-16 text-[#217346] opacity-50" /> | |
| 130 | + <p className="text-sm text-center text-gray-500">Impossible d'afficher ce fichier Excel.</p> | |
| 131 | + <button | |
| 132 | + onClick={() => window.open(`/api/files/${file.id}/download`, "_blank")} | |
| 133 | + className="flex items-center gap-2 px-4 py-2 bg-[#217346] hover:bg-[#1a5c38] rounded text-sm text-white transition-colors" | |
| 134 | + > | |
| 135 | + <Download className="w-4 h-4" /> | |
| 136 | + Télécharger | |
| 137 | + </button> | |
| 138 | + </div> | |
| 139 | + ); | |
| 140 | + } | |
| 141 | + | |
| 142 | + const currentSheet = sheets[activeSheet]; | |
| 143 | + const currentEditRows = editData[activeSheet] ?? currentSheet.rows.map((r) => r.map((c) => (c === null ? null : String(c)))); | |
| 144 | + | |
| 145 | + return ( | |
| 146 | + <div className="flex flex-col h-full bg-white text-gray-900"> | |
| 147 | + {/* Toolbar */} | |
| 148 | + <div className="flex items-center gap-2 px-3 py-1.5 bg-[#f2f2f2] border-b border-[#d0d7de] shrink-0"> | |
| 149 | + <span className="text-xs text-gray-500 font-medium">{file.name}</span> | |
| 150 | + <div className="flex-1" /> | |
| 151 | + {isEditing && ( | |
| 152 | + <button | |
| 153 | + onClick={handleSave} | |
| 154 | + disabled={saving} | |
| 155 | + className="flex items-center gap-1.5 px-3 py-1.5 text-xs bg-[#217346] hover:bg-[#1a5c38] text-white rounded transition-colors disabled:opacity-60" | |
| 156 | + > | |
| 157 | + {saving ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Save className="w-3.5 h-3.5" />} | |
| 158 | + Sauvegarder | |
| 159 | + </button> | |
| 160 | + )} | |
| 161 | + {!isEditing && ( | |
| 162 | + <span className="text-xs text-gray-400 flex items-center gap-1"> | |
| 163 | + <Edit3 className="w-3.5 h-3.5" /> | |
| 164 | + Mode lecture | |
| 165 | + </span> | |
| 166 | + )} | |
| 167 | + <button | |
| 168 | + onClick={() => window.open(`/api/files/${file.id}/download`, "_blank")} | |
| 169 | + className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 hover:text-[#217346] transition-colors" | |
| 170 | + > | |
| 171 | + <Download className="w-3.5 h-3.5" /> | |
| 172 | + </button> | |
| 173 | + </div> | |
| 174 | + | |
| 175 | + {/* Sheet tabs — Excel style */} | |
| 176 | + <div className="flex items-end gap-px px-2 pt-1 bg-[#f2f2f2] border-b border-[#d0d7de] overflow-x-auto shrink-0"> | |
| 177 | + {sheets.map((sheet, i) => ( | |
| 178 | + <button | |
| 179 | + key={sheet.name} | |
| 180 | + onClick={() => setActiveSheet(i)} | |
| 181 | + className={`px-4 py-1.5 text-xs rounded-t transition-colors whitespace-nowrap border border-b-0 ${ | |
| 182 | + i === activeSheet | |
| 183 | + ? "bg-[#217346] text-white border-[#217346]" | |
| 184 | + : "bg-[#e0e0e0] text-gray-600 border-[#c0c0c0] hover:bg-[#d0d0d0]" | |
| 185 | + }`} | |
| 186 | + > | |
| 187 | + {sheet.name} | |
| 188 | + </button> | |
| 189 | + ))} | |
| 190 | + <div className="ml-auto flex items-center gap-2 pb-1"> | |
| 191 | + <span className="text-[10px] text-gray-500 whitespace-nowrap"> | |
| 192 | + {currentSheet.rows.length} lignes · {currentSheet.colCount} col. | |
| 193 | + </span> | |
| 194 | + </div> | |
| 195 | + </div> | |
| 196 | + | |
| 197 | + {/* Sheet viewer */} | |
| 198 | + <SheetViewer | |
| 199 | + sheet={currentSheet} | |
| 200 | + editRows={currentEditRows} | |
| 201 | + sheetIdx={activeSheet} | |
| 202 | + isEditing={isEditing} | |
| 203 | + onCellChange={handleCellChange} | |
| 204 | + key={activeSheet} | |
| 205 | + /> | |
| 206 | + </div> | |
| 207 | + ); | |
| 208 | +} | |
| 209 | + | |
| 210 | +function SheetViewer({ | |
| 211 | + sheet, | |
| 212 | + editRows, | |
| 213 | + sheetIdx, | |
| 214 | + isEditing, | |
| 215 | + onCellChange, | |
| 216 | +}: { | |
| 217 | + sheet: SheetData; | |
| 218 | + editRows: (string | number | null)[][]; | |
| 219 | + sheetIdx: number; | |
| 220 | + isEditing: boolean; | |
| 221 | + onCellChange: (si: number, ri: number, ci: number, v: string) => void; | |
| 222 | +}) { | |
| 223 | + const [sortConfig, setSortConfig] = useState<SortConfig | null>(null); | |
| 224 | + const [page, setPage] = useState(0); | |
| 225 | + | |
| 226 | + const headerRow = editRows[0] ?? []; | |
| 227 | + const dataRows = editRows.slice(1); | |
| 228 | + const colCount = sheet.colCount; | |
| 229 | + | |
| 230 | + const sortedRows = useMemo(() => { | |
| 231 | + if (!sortConfig || isEditing) return dataRows; | |
| 232 | + return [...dataRows].sort((a, b) => { | |
| 233 | + const aVal = a[sortConfig.col] ?? ""; | |
| 234 | + const bVal = b[sortConfig.col] ?? ""; | |
| 235 | + const numA = parseFloat(String(aVal)); | |
| 236 | + const numB = parseFloat(String(bVal)); | |
| 237 | + if (!isNaN(numA) && !isNaN(numB)) { | |
| 238 | + return sortConfig.dir === "asc" ? numA - numB : numB - numA; | |
| 239 | + } | |
| 240 | + return sortConfig.dir === "asc" | |
| 241 | + ? String(aVal).localeCompare(String(bVal)) | |
| 242 | + : String(bVal).localeCompare(String(aVal)); | |
| 243 | + }); | |
| 244 | + }, [dataRows, sortConfig, isEditing]); | |
| 245 | + | |
| 246 | + const pageRows = sortedRows.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE); | |
| 247 | + const totalPages = Math.ceil(sortedRows.length / PAGE_SIZE); | |
| 248 | + | |
| 249 | + const toggleSort = useCallback((col: number) => { | |
| 250 | + if (isEditing) return; | |
| 251 | + setSortConfig((prev) => { | |
| 252 | + if (prev?.col === col) return prev.dir === "asc" ? { col, dir: "desc" } : null; | |
| 253 | + return { col, dir: "asc" }; | |
| 254 | + }); | |
| 255 | + setPage(0); | |
| 256 | + }, [isEditing]); | |
| 257 | + | |
| 258 | + const formatCell = (val: string | number | null | undefined): string => { | |
| 259 | + if (val === null || val === undefined) return ""; | |
| 260 | + return String(val); | |
| 261 | + }; | |
| 262 | + | |
| 263 | + const isNumeric = (val: string | number | null | undefined): boolean => | |
| 264 | + typeof val === "number" || (!isNaN(parseFloat(String(val))) && val !== null && val !== ""); | |
| 265 | + | |
| 266 | + return ( | |
| 267 | + <div className="flex flex-col flex-1 overflow-hidden bg-white"> | |
| 268 | + <div className="flex-1 overflow-auto"> | |
| 269 | + <table className="text-xs border-collapse min-w-max"> | |
| 270 | + {/* Column letter row */} | |
| 271 | + <thead className="sticky top-0 z-10"> | |
| 272 | + <tr> | |
| 273 | + {/* Row number gutter header */} | |
| 274 | + <th className="w-10 min-w-[2.5rem] border border-[#d0d7de] px-2 py-1 bg-[#f2f2f2] text-gray-500 font-normal select-none" /> | |
| 275 | + {Array.from({ length: colCount }, (_, i) => ( | |
| 276 | + <th | |
| 277 | + key={i} | |
| 278 | + className="border border-[#d0d7de] px-1 py-1 bg-[#f2f2f2] text-gray-500 font-normal text-center min-w-[80px]" | |
| 279 | + > | |
| 280 | + {colLetter(i)} | |
| 281 | + </th> | |
| 282 | + ))} | |
| 283 | + </tr> | |
| 284 | + | |
| 285 | + {/* Header data row */} | |
| 286 | + <tr> | |
| 287 | + <td className="border border-[#d0d7de] px-2 py-1 bg-[#f2f2f2] text-gray-400 text-center font-normal select-none"> | |
| 288 | + 1 | |
| 289 | + </td> | |
| 290 | + {Array.from({ length: colCount }, (_, i) => { | |
| 291 | + const val = headerRow[i] ?? null; | |
| 292 | + const sorted = sortConfig?.col === i; | |
| 293 | + return ( | |
| 294 | + <th | |
| 295 | + key={i} | |
| 296 | + className="border border-[#d0d7de] bg-[#f2f2f2] text-gray-700 font-semibold text-left p-0" | |
| 297 | + > | |
| 298 | + {isEditing ? ( | |
| 299 | + <input | |
| 300 | + type="text" | |
| 301 | + value={formatCell(val)} | |
| 302 | + onChange={(e) => onCellChange(sheetIdx, 0, i, e.target.value)} | |
| 303 | + className="w-full px-2 py-1 bg-white border-0 outline-none focus:ring-2 focus:ring-[#217346] focus:ring-inset text-gray-900 font-semibold" | |
| 304 | + /> | |
| 305 | + ) : ( | |
| 306 | + <button | |
| 307 | + onClick={() => toggleSort(i)} | |
| 308 | + className="flex items-center gap-1 w-full px-2 py-1 hover:bg-[#e8f0e8] transition-colors" | |
| 309 | + > | |
| 310 | + <span className="truncate max-w-[120px]">{formatCell(val)}</span> | |
| 311 | + <ArrowUpDown className={`w-3 h-3 shrink-0 ${sorted ? "text-[#217346]" : "text-gray-400"}`} /> | |
| 312 | + </button> | |
| 313 | + )} | |
| 314 | + </th> | |
| 315 | + ); | |
| 316 | + })} | |
| 317 | + </tr> | |
| 318 | + </thead> | |
| 319 | + | |
| 320 | + <tbody> | |
| 321 | + {pageRows.map((row, rowIdx) => { | |
| 322 | + const actualRowIdx = page * PAGE_SIZE + rowIdx + 1; // 0-based in editRows (1 = first data row) | |
| 323 | + return ( | |
| 324 | + <tr | |
| 325 | + key={rowIdx} | |
| 326 | + className={rowIdx % 2 === 0 ? "bg-white" : "bg-[#f9f9f9]"} | |
| 327 | + > | |
| 328 | + {/* Row number */} | |
| 329 | + <td className="border border-[#e0e0e0] px-2 py-0.5 text-gray-400 text-center bg-[#f2f2f2] select-none"> | |
| 330 | + {actualRowIdx + 1} | |
| 331 | + </td> | |
| 332 | + {Array.from({ length: colCount }, (_, colIdx) => { | |
| 333 | + const val = row[colIdx] ?? null; | |
| 334 | + const numeric = isNumeric(val); | |
| 335 | + return ( | |
| 336 | + <td | |
| 337 | + key={colIdx} | |
| 338 | + className={`border border-[#e0e0e0] p-0 ${!isEditing && numeric ? "text-right" : ""}`} | |
| 339 | + > | |
| 340 | + {isEditing ? ( | |
| 341 | + <input | |
| 342 | + type="text" | |
| 343 | + value={formatCell(val)} | |
| 344 | + onChange={(e) => onCellChange(sheetIdx, actualRowIdx, colIdx, e.target.value)} | |
| 345 | + className="w-full min-w-[80px] px-2 py-0.5 bg-transparent border-0 outline-none focus:ring-2 focus:ring-[#217346] focus:ring-inset text-gray-900" | |
| 346 | + /> | |
| 347 | + ) : ( | |
| 348 | + <span className={`block px-2 py-0.5 ${numeric ? "text-[#1155cc] text-right" : "text-gray-800"}`}> | |
| 349 | + {formatCell(val)} | |
| 350 | + </span> | |
| 351 | + )} | |
| 352 | + </td> | |
| 353 | + ); | |
| 354 | + })} | |
| 355 | + </tr> | |
| 356 | + ); | |
| 357 | + })} | |
| 358 | + | |
| 359 | + {pageRows.length === 0 && ( | |
| 360 | + <tr> | |
| 361 | + <td colSpan={colCount + 1} className="border border-[#e0e0e0] px-4 py-8 text-center text-gray-400"> | |
| 362 | + Feuille vide | |
| 363 | + </td> | |
| 364 | + </tr> | |
| 365 | + )} | |
| 366 | + </tbody> | |
| 367 | + </table> | |
| 368 | + </div> | |
| 369 | + | |
| 370 | + {/* Pagination */} | |
| 371 | + {totalPages > 1 && ( | |
| 372 | + <div className="flex items-center justify-between px-3 py-2 border-t border-[#d0d7de] bg-[#f2f2f2] shrink-0"> | |
| 373 | + <button | |
| 374 | + onClick={() => setPage((p) => Math.max(0, p - 1))} | |
| 375 | + disabled={page === 0} | |
| 376 | + className="px-2 py-1 text-xs text-gray-500 hover:text-[#217346] disabled:opacity-30 transition-colors" | |
| 377 | + > | |
| 378 | + ← Précédent | |
| 379 | + </button> | |
| 380 | + <span className="text-xs text-gray-500"> | |
| 381 | + Lignes {page * PAGE_SIZE + 2}–{Math.min((page + 1) * PAGE_SIZE + 1, sortedRows.length + 1)} sur {sortedRows.length + 1} | |
| 382 | + </span> | |
| 383 | + <button | |
| 384 | + onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))} | |
| 385 | + disabled={page >= totalPages - 1} | |
| 386 | + className="px-2 py-1 text-xs text-gray-500 hover:text-[#217346] disabled:opacity-30 transition-colors" | |
| 387 | + > | |
| 388 | + Suivant → | |
| 389 | + </button> | |
| 390 | + </div> | |
| 391 | + )} | |
| 392 | + </div> | |
| 393 | + ); | |
| 394 | +} | |
modified
components/preview/plugins/PdfPreview.tsx
+368 −60
@@ -1,108 +1,213 @@ | ||
| 1 | 1 | "use client"; |
| 2 | 2 | |
| 3 | 3 | import { useEffect, useRef, useState, useCallback } from "react"; |
| 4 | −import { ChevronLeft, ChevronRight, ZoomIn, ZoomOut } from "lucide-react"; | |
| 4 | +import { | |
| 5 | + ChevronLeft, ChevronRight, ZoomIn, ZoomOut, | |
| 6 | + Highlighter, StickyNote, Pen, Trash2, MousePointer, | |
| 7 | +} from "lucide-react"; | |
| 5 | 8 | import type { PreviewPluginProps } from "./types"; |
| 6 | 9 | |
| 7 | −export default function PdfPreview({ file }: PreviewPluginProps) { | |
| 10 | +// ── Annotation types ────────────────────────────────────────────────────────── | |
| 11 | + | |
| 12 | +type AnnotationTool = "pointer" | "highlight" | "note" | "pen"; | |
| 13 | + | |
| 14 | +interface HighlightAnnotation { | |
| 15 | + id: string; | |
| 16 | + type: "highlight"; | |
| 17 | + x: number; y: number; w: number; h: number; | |
| 18 | + color: string; | |
| 19 | +} | |
| 20 | + | |
| 21 | +interface NoteAnnotation { | |
| 22 | + id: string; | |
| 23 | + type: "note"; | |
| 24 | + x: number; y: number; | |
| 25 | + text: string; | |
| 26 | +} | |
| 27 | + | |
| 28 | +interface PenAnnotation { | |
| 29 | + id: string; | |
| 30 | + type: "pen"; | |
| 31 | + path: string; // SVG path data | |
| 32 | + color: string; | |
| 33 | +} | |
| 34 | + | |
| 35 | +type Annotation = HighlightAnnotation | NoteAnnotation | PenAnnotation; | |
| 36 | + | |
| 37 | +interface PageAnnotations { | |
| 38 | + [page: number]: Annotation[]; | |
| 39 | +} | |
| 40 | + | |
| 41 | +// ── Tiny uid ────────────────────────────────────────────────────────────────── | |
| 42 | +function uid() { | |
| 43 | + return Math.random().toString(36).slice(2, 10); | |
| 44 | +} | |
| 45 | + | |
| 46 | +// ── Main component ──────────────────────────────────────────────────────────── | |
| 47 | +export default function PdfPreview({ file, isEditing }: PreviewPluginProps) { | |
| 8 | 48 | const canvasRef = useRef<HTMLCanvasElement>(null); |
| 49 | + const overlayRef = useRef<SVGSVGElement>(null); | |
| 9 | 50 | const containerRef = useRef<HTMLDivElement>(null); |
| 10 | 51 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 11 | 52 | const pdfDocRef = useRef<any>(null); |
| 12 | 53 | const renderTaskRef = useRef<{ cancel: () => void } | null>(null); |
| 54 | + | |
| 13 | 55 | const [pageNum, setPageNum] = useState(1); |
| 14 | 56 | const [totalPages, setTotalPages] = useState(0); |
| 15 | 57 | const [scale, setScale] = useState(1.2); |
| 16 | 58 | const [loading, setLoading] = useState(true); |
| 17 | 59 | const [error, setError] = useState(false); |
| 18 | 60 | |
| 61 | + // Annotation state | |
| 62 | + const [tool, setTool] = useState<AnnotationTool>("pointer"); | |
| 63 | + const [pageAnnotations, setPageAnnotations] = useState<PageAnnotations>({}); | |
| 64 | + const [editingNote, setEditingNote] = useState<{ id: string; page: number } | null>(null); | |
| 65 | + | |
| 66 | + // Drawing state for highlight | |
| 67 | + const drawingRef = useRef<{ startX: number; startY: number } | null>(null); | |
| 68 | + const [drawRect, setDrawRect] = useState<{ x: number; y: number; w: number; h: number } | null>(null); | |
| 69 | + | |
| 70 | + // Drawing state for pen | |
| 71 | + const penRef = useRef<string | null>(null); | |
| 72 | + const [penPath, setPenPath] = useState<string | null>(null); | |
| 73 | + | |
| 74 | + // Canvas dimensions (for overlay sizing) | |
| 75 | + const [canvasDims, setCanvasDims] = useState({ w: 0, h: 0 }); | |
| 76 | + | |
| 77 | + // ── PDF render ───────────────────────────────────────────────────────────── | |
| 19 | 78 | const renderPage = useCallback(async (num: number, currentScale: number) => { |
| 20 | 79 | const pdf = pdfDocRef.current; |
| 21 | 80 | if (!pdf || !canvasRef.current) return; |
| 22 | − | |
| 23 | 81 | try { |
| 24 | − // Cancel any in-progress render | |
| 25 | 82 | if (renderTaskRef.current) { |
| 26 | 83 | renderTaskRef.current.cancel(); |
| 27 | 84 | renderTaskRef.current = null; |
| 28 | 85 | } |
| 29 | − | |
| 30 | 86 | const page = await pdf.getPage(num); |
| 31 | 87 | const viewport = page.getViewport({ scale: currentScale }); |
| 32 | 88 | const canvas = canvasRef.current; |
| 33 | 89 | const context = canvas.getContext("2d"); |
| 34 | 90 | if (!context) return; |
| 35 | − | |
| 36 | 91 | canvas.height = viewport.height; |
| 37 | 92 | canvas.width = viewport.width; |
| 38 | − | |
| 93 | + setCanvasDims({ w: viewport.width, h: viewport.height }); | |
| 39 | 94 | const renderTask = page.render({ canvasContext: context, viewport }); |
| 40 | 95 | renderTaskRef.current = renderTask; |
| 41 | 96 | await renderTask.promise; |
| 42 | 97 | renderTaskRef.current = null; |
| 43 | 98 | } catch (e: unknown) { |
| 44 | − // Ignore cancelled render errors | |
| 45 | 99 | if (e && typeof e === "object" && "name" in e && (e as { name: string }).name === "RenderingCancelledException") return; |
| 46 | 100 | } |
| 47 | 101 | }, []); |
| 48 | 102 | |
| 49 | 103 | useEffect(() => { |
| 50 | 104 | let cancelled = false; |
| 51 | − | |
| 52 | 105 | const loadPdf = async () => { |
| 53 | 106 | try { |
| 54 | 107 | const pdfjsLib = await import("pdfjs-dist"); |
| 55 | − | |
| 56 | − // Use local worker file copied to /public | |
| 57 | 108 | pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdf.worker.min.mjs"; |
| 58 | − | |
| 59 | 109 | const loadingTask = pdfjsLib.getDocument({ |
| 60 | 110 | url: `/api/files/${file.id}/preview`, |
| 61 | 111 | cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@5.4.624/cmaps/", |
| 62 | 112 | cMapPacked: true, |
| 63 | 113 | }); |
| 64 | − | |
| 65 | 114 | const pdf = await loadingTask.promise; |
| 66 | 115 | if (cancelled) return; |
| 67 | − | |
| 68 | 116 | pdfDocRef.current = pdf; |
| 69 | 117 | setTotalPages(pdf.numPages); |
| 70 | 118 | setLoading(false); |
| 71 | − | |
| 72 | − // Render first page after state is set | |
| 73 | 119 | await renderPage(1, 1.2); |
| 74 | 120 | } catch (err) { |
| 75 | 121 | console.error("PDF load error:", err); |
| 76 | − if (!cancelled) { | |
| 77 | − setError(true); | |
| 78 | − setLoading(false); | |
| 79 | − } | |
| 122 | + if (!cancelled) { setError(true); setLoading(false); } | |
| 80 | 123 | } |
| 81 | 124 | }; |
| 82 | − | |
| 83 | 125 | loadPdf(); |
| 84 | 126 | return () => { |
| 85 | 127 | cancelled = true; |
| 86 | − if (renderTaskRef.current) { | |
| 87 | − renderTaskRef.current.cancel(); | |
| 88 | − } | |
| 128 | + if (renderTaskRef.current) renderTaskRef.current.cancel(); | |
| 89 | 129 | }; |
| 90 | 130 | }, [file.id, renderPage]); |
| 91 | 131 | |
| 92 | − // Re-render when page or scale changes (but not on initial load) | |
| 93 | 132 | useEffect(() => { |
| 94 | − if (!loading && pdfDocRef.current) { | |
| 95 | − renderPage(pageNum, scale); | |
| 96 | − } | |
| 133 | + if (!loading && pdfDocRef.current) renderPage(pageNum, scale); | |
| 97 | 134 | }, [pageNum, scale, renderPage, loading]); |
| 98 | 135 | |
| 136 | + // ── Load annotations ─────────────────────────────────────────────────────── | |
| 137 | + useEffect(() => { | |
| 138 | + const load = async () => { | |
| 139 | + try { | |
| 140 | + const res = await fetch(`/api/files/${file.id}/annotations`); | |
| 141 | + if (!res.ok) return; | |
| 142 | + const data = await res.json(); | |
| 143 | + if (data.annotations) { | |
| 144 | + try { | |
| 145 | + const parsed = JSON.parse(data.annotations); | |
| 146 | + if (parsed?.pageAnnotations) setPageAnnotations(parsed.pageAnnotations); | |
| 147 | + } catch { | |
| 148 | + // ignore | |
| 149 | + } | |
| 150 | + } | |
| 151 | + } catch { | |
| 152 | + // ignore | |
| 153 | + } | |
| 154 | + }; | |
| 155 | + load(); | |
| 156 | + }, [file.id]); | |
| 157 | + | |
| 158 | + // ── Save annotations ─────────────────────────────────────────────────────── | |
| 159 | + const saveAnnotations = useCallback(async (pa: PageAnnotations) => { | |
| 160 | + try { | |
| 161 | + await fetch(`/api/files/${file.id}/annotations`, { | |
| 162 | + method: "PUT", | |
| 163 | + headers: { "Content-Type": "application/json" }, | |
| 164 | + body: JSON.stringify({ annotations: JSON.stringify({ pageAnnotations: pa }) }), | |
| 165 | + }); | |
| 166 | + } catch { | |
| 167 | + // ignore | |
| 168 | + } | |
| 169 | + }, [file.id]); | |
| 170 | + | |
| 171 | + const addAnnotation = useCallback((ann: Annotation) => { | |
| 172 | + setPageAnnotations((prev) => { | |
| 173 | + const next = { ...prev, [pageNum]: [...(prev[pageNum] ?? []), ann] }; | |
| 174 | + saveAnnotations(next); | |
| 175 | + return next; | |
| 176 | + }); | |
| 177 | + }, [pageNum, saveAnnotations]); | |
| 178 | + | |
| 179 | + const deleteAnnotation = useCallback((id: string) => { | |
| 180 | + setPageAnnotations((prev) => { | |
| 181 | + const next = { | |
| 182 | + ...prev, | |
| 183 | + [pageNum]: (prev[pageNum] ?? []).filter((a) => a.id !== id), | |
| 184 | + }; | |
| 185 | + saveAnnotations(next); | |
| 186 | + return next; | |
| 187 | + }); | |
| 188 | + }, [pageNum, saveAnnotations]); | |
| 189 | + | |
| 190 | + const updateNoteText = useCallback((id: string, text: string) => { | |
| 191 | + setPageAnnotations((prev) => { | |
| 192 | + const next = { | |
| 193 | + ...prev, | |
| 194 | + [pageNum]: (prev[pageNum] ?? []).map((a) => | |
| 195 | + a.id === id ? { ...a, text } : a | |
| 196 | + ), | |
| 197 | + }; | |
| 198 | + saveAnnotations(next); | |
| 199 | + return next; | |
| 200 | + }); | |
| 201 | + }, [pageNum, saveAnnotations]); | |
| 202 | + | |
| 203 | + // ── Keyboard nav ─────────────────────────────────────────────────────────── | |
| 99 | 204 | const goToPage = useCallback((num: number) => { |
| 100 | 205 | setPageNum(Math.max(1, Math.min(num, totalPages))); |
| 101 | 206 | }, [totalPages]); |
| 102 | 207 | |
| 103 | 208 | useEffect(() => { |
| 104 | 209 | const handleKey = (e: KeyboardEvent) => { |
| 105 | − if (e.target instanceof HTMLInputElement) return; | |
| 210 | + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; | |
| 106 | 211 | if (e.key === "ArrowLeft" || e.key === "ArrowUp") { e.preventDefault(); goToPage(pageNum - 1); } |
| 107 | 212 | if (e.key === "ArrowRight" || e.key === "ArrowDown") { e.preventDefault(); goToPage(pageNum + 1); } |
| 108 | 213 | }; |
@@ -110,15 +215,83 @@ export default function PdfPreview({ file }: PreviewPluginProps) { | ||
| 110 | 215 | return () => window.removeEventListener("keydown", handleKey); |
| 111 | 216 | }, [pageNum, goToPage]); |
| 112 | 217 | |
| 218 | + // ── Mouse handlers on overlay ────────────────────────────────────────────── | |
| 219 | + const getRelativePos = (e: React.MouseEvent<SVGSVGElement>) => { | |
| 220 | + const rect = e.currentTarget.getBoundingClientRect(); | |
| 221 | + return { | |
| 222 | + x: (e.clientX - rect.left) / rect.width * canvasDims.w, | |
| 223 | + y: (e.clientY - rect.top) / rect.height * canvasDims.h, | |
| 224 | + }; | |
| 225 | + }; | |
| 226 | + | |
| 227 | + const handleMouseDown = useCallback((e: React.MouseEvent<SVGSVGElement>) => { | |
| 228 | + if (!isEditing) return; | |
| 229 | + const pos = getRelativePos(e); | |
| 230 | + | |
| 231 | + if (tool === "highlight") { | |
| 232 | + drawingRef.current = { startX: pos.x, startY: pos.y }; | |
| 233 | + setDrawRect({ x: pos.x, y: pos.y, w: 0, h: 0 }); | |
| 234 | + e.preventDefault(); | |
| 235 | + } | |
| 236 | + | |
| 237 | + if (tool === "note") { | |
| 238 | + addAnnotation({ id: uid(), type: "note", x: pos.x, y: pos.y, text: "" }); | |
| 239 | + } | |
| 240 | + | |
| 241 | + if (tool === "pen") { | |
| 242 | + penRef.current = `M ${pos.x} ${pos.y}`; | |
| 243 | + setPenPath(penRef.current); | |
| 244 | + e.preventDefault(); | |
| 245 | + } | |
| 246 | + // eslint-disable-next-line react-hooks/exhaustive-deps | |
| 247 | + }, [isEditing, tool, canvasDims, addAnnotation]); | |
| 248 | + | |
| 249 | + const handleMouseMove = useCallback((e: React.MouseEvent<SVGSVGElement>) => { | |
| 250 | + if (!isEditing) return; | |
| 251 | + const pos = getRelativePos(e); | |
| 252 | + | |
| 253 | + if (tool === "highlight" && drawingRef.current) { | |
| 254 | + const dx = pos.x - drawingRef.current.startX; | |
| 255 | + const dy = pos.y - drawingRef.current.startY; | |
| 256 | + setDrawRect({ | |
| 257 | + x: dx < 0 ? pos.x : drawingRef.current.startX, | |
| 258 | + y: dy < 0 ? pos.y : drawingRef.current.startY, | |
| 259 | + w: Math.abs(dx), | |
| 260 | + h: Math.abs(dy), | |
| 261 | + }); | |
| 262 | + } | |
| 263 | + | |
| 264 | + if (tool === "pen" && penRef.current) { | |
| 265 | + penRef.current += ` L ${pos.x} ${pos.y}`; | |
| 266 | + setPenPath(penRef.current); | |
| 267 | + } | |
| 268 | + // eslint-disable-next-line react-hooks/exhaustive-deps | |
| 269 | + }, [isEditing, tool, canvasDims]); | |
| 270 | + | |
| 271 | + const handleMouseUp = useCallback(() => { | |
| 272 | + if (!isEditing) return; | |
| 273 | + | |
| 274 | + if (tool === "highlight" && drawingRef.current && drawRect && drawRect.w > 5 && drawRect.h > 5) { | |
| 275 | + addAnnotation({ id: uid(), type: "highlight", ...drawRect, color: "#fef08a" }); | |
| 276 | + } | |
| 277 | + drawingRef.current = null; | |
| 278 | + setDrawRect(null); | |
| 279 | + | |
| 280 | + if (tool === "pen" && penRef.current) { | |
| 281 | + addAnnotation({ id: uid(), type: "pen", path: penRef.current, color: "#ef4444" }); | |
| 282 | + penRef.current = null; | |
| 283 | + setPenPath(null); | |
| 284 | + } | |
| 285 | + }, [isEditing, tool, drawRect, addAnnotation]); | |
| 286 | + | |
| 287 | + // ── Current page annotations ─────────────────────────────────────────────── | |
| 288 | + const currentAnnotations = pageAnnotations[pageNum] ?? []; | |
| 289 | + | |
| 290 | + // ── Error fallback ───────────────────────────────────────────────────────── | |
| 113 | 291 | if (error) { |
| 114 | − // Fallback to browser's built-in PDF viewer via iframe | |
| 115 | 292 | return ( |
| 116 | 293 | <div className="w-full h-full"> |
| 117 | − <iframe | |
| 118 | − src={`/api/files/${file.id}/preview`} | |
| 119 | − className="w-full h-full border-0" | |
| 120 | − title={file.name} | |
| 121 | − /> | |
| 294 | + <iframe src={`/api/files/${file.id}/preview`} className="w-full h-full border-0" title={file.name} /> | |
| 122 | 295 | </div> |
| 123 | 296 | ); |
| 124 | 297 | } |
@@ -126,7 +299,8 @@ export default function PdfPreview({ file }: PreviewPluginProps) { | ||
| 126 | 299 | return ( |
| 127 | 300 | <div className="flex flex-col h-full"> |
| 128 | 301 | {/* Toolbar */} |
| 129 | − <div className="flex items-center gap-2 px-3 py-2 border-b border-white/10 bg-white/5 flex-shrink-0"> | |
| 302 | + <div className="flex items-center gap-2 px-3 py-2 border-b border-white/10 bg-white/5 flex-shrink-0 flex-wrap"> | |
| 303 | + {/* Navigation */} | |
| 130 | 304 | <button |
| 131 | 305 | onClick={() => goToPage(pageNum - 1)} |
| 132 | 306 | disabled={pageNum <= 1} |
@@ -155,44 +329,178 @@ export default function PdfPreview({ file }: PreviewPluginProps) { | ||
| 155 | 329 | |
| 156 | 330 | <div className="w-px h-4 bg-white/10 mx-1" /> |
| 157 | 331 | |
| 158 | − <button | |
| 159 | − onClick={() => setScale((s) => Math.min(s + 0.2, 3))} | |
| 160 | − className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 161 | − title="Zoom in" | |
| 162 | − > | |
| 332 | + {/* Zoom */} | |
| 333 | + <button onClick={() => setScale((s) => Math.min(s + 0.2, 3))} className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10"> | |
| 163 | 334 | <ZoomIn className="w-4 h-4" /> |
| 164 | 335 | </button> |
| 165 | − <span className="text-xs text-gray-500 min-w-[3rem] text-center"> | |
| 166 | − {Math.round(scale * 100)}% | |
| 167 | − </span> | |
| 168 | − <button | |
| 169 | − onClick={() => setScale((s) => Math.max(s - 0.2, 0.4))} | |
| 170 | − className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 171 | − title="Zoom out" | |
| 172 | − > | |
| 336 | + <span className="text-xs text-gray-500 min-w-[3rem] text-center">{Math.round(scale * 100)}%</span> | |
| 337 | + <button onClick={() => setScale((s) => Math.max(s - 0.2, 0.4))} className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10"> | |
| 173 | 338 | <ZoomOut className="w-4 h-4" /> |
| 174 | 339 | </button> |
| 175 | − | |
| 176 | − <button | |
| 177 | − onClick={() => setScale(1.2)} | |
| 178 | − className="text-[10px] text-gray-500 hover:text-white px-1.5 py-0.5 rounded hover:bg-white/10 transition-colors" | |
| 179 | − > | |
| 340 | + <button onClick={() => setScale(1.2)} className="text-[10px] text-gray-500 hover:text-white px-1.5 py-0.5 rounded hover:bg-white/10 transition-colors"> | |
| 180 | 341 | Reset |
| 181 | 342 | </button> |
| 343 | + | |
| 344 | + {/* Annotation tools (only in edit mode) */} | |
| 345 | + {isEditing && ( | |
| 346 | + <> | |
| 347 | + <div className="w-px h-4 bg-white/10 mx-1" /> | |
| 348 | + <span className="text-[10px] text-gray-500 mr-1">Annoter :</span> | |
| 349 | + | |
| 350 | + {([ | |
| 351 | + { id: "pointer", Icon: MousePointer, label: "Sélection" }, | |
| 352 | + { id: "highlight", Icon: Highlighter, label: "Surligner" }, | |
| 353 | + { id: "note", Icon: StickyNote, label: "Note" }, | |
| 354 | + { id: "pen", Icon: Pen, label: "Dessin" }, | |
| 355 | + ] as const).map(({ id, Icon, label }) => ( | |
| 356 | + <button | |
| 357 | + key={id} | |
| 358 | + onClick={() => setTool(id)} | |
| 359 | + title={label} | |
| 360 | + className={`p-1.5 rounded transition-colors text-xs flex items-center gap-1 ${ | |
| 361 | + tool === id | |
| 362 | + ? "bg-yellow-500/20 text-yellow-300 ring-1 ring-yellow-400/50" | |
| 363 | + : "text-gray-400 hover:text-white hover:bg-white/10" | |
| 364 | + }`} | |
| 365 | + > | |
| 366 | + <Icon className="w-3.5 h-3.5" /> | |
| 367 | + </button> | |
| 368 | + ))} | |
| 369 | + | |
| 370 | + {currentAnnotations.length > 0 && ( | |
| 371 | + <span className="text-[10px] text-gray-500 ml-1"> | |
| 372 | + {currentAnnotations.length} annot. | |
| 373 | + </span> | |
| 374 | + )} | |
| 375 | + </> | |
| 376 | + )} | |
| 182 | 377 | </div> |
| 183 | 378 | |
| 184 | − {/* Canvas */} | |
| 379 | + {/* Canvas + Overlay */} | |
| 185 | 380 | <div ref={containerRef} className="flex-1 overflow-auto flex justify-center p-4 bg-gray-900/50"> |
| 186 | 381 | {loading ? ( |
| 187 | 382 | <div className="flex items-center justify-center h-full"> |
| 188 | 383 | <div className="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin" /> |
| 189 | 384 | </div> |
| 190 | 385 | ) : ( |
| 191 | − <canvas | |
| 192 | − ref={canvasRef} | |
| 193 | − className="shadow-2xl max-w-full" | |
| 194 | − style={{ background: "white" }} | |
| 195 | − /> | |
| 386 | + <div className="relative inline-block" style={{ lineHeight: 0 }}> | |
| 387 | + <canvas | |
| 388 | + ref={canvasRef} | |
| 389 | + className="shadow-2xl max-w-full block" | |
| 390 | + style={{ background: "white" }} | |
| 391 | + /> | |
| 392 | + | |
| 393 | + {/* SVG Annotation Overlay */} | |
| 394 | + <svg | |
| 395 | + ref={overlayRef} | |
| 396 | + className={`absolute inset-0 w-full h-full ${isEditing && tool !== "pointer" ? "cursor-crosshair" : "cursor-default"}`} | |
| 397 | + viewBox={`0 0 ${canvasDims.w} ${canvasDims.h}`} | |
| 398 | + preserveAspectRatio="none" | |
| 399 | + onMouseDown={handleMouseDown} | |
| 400 | + onMouseMove={handleMouseMove} | |
| 401 | + onMouseUp={handleMouseUp} | |
| 402 | + > | |
| 403 | + {/* Render saved annotations */} | |
| 404 | + {currentAnnotations.map((ann) => { | |
| 405 | + if (ann.type === "highlight") { | |
| 406 | + return ( | |
| 407 | + <g key={ann.id}> | |
| 408 | + <rect | |
| 409 | + x={ann.x} y={ann.y} width={ann.w} height={ann.h} | |
| 410 | + fill={ann.color} | |
| 411 | + fillOpacity={0.35} | |
| 412 | + stroke={ann.color} | |
| 413 | + strokeWidth={1} | |
| 414 | + strokeOpacity={0.6} | |
| 415 | + className="cursor-pointer" | |
| 416 | + onClick={(e) => { | |
| 417 | + if (isEditing && tool === "pointer") { | |
| 418 | + e.stopPropagation(); | |
| 419 | + deleteAnnotation(ann.id); | |
| 420 | + } | |
| 421 | + }} | |
| 422 | + /> | |
| 423 | + {isEditing && tool === "pointer" && ( | |
| 424 | + <foreignObject x={ann.x + ann.w - 12} y={ann.y - 12} width={16} height={16}> | |
| 425 | + <button | |
| 426 | + title="Supprimer" | |
| 427 | + onClick={(e) => { e.stopPropagation(); deleteAnnotation(ann.id); }} | |
| 428 | + className="w-4 h-4 bg-red-500 text-white rounded-full text-[8px] flex items-center justify-center hover:bg-red-600" | |
| 429 | + > | |
| 430 | + × | |
| 431 | + </button> | |
| 432 | + </foreignObject> | |
| 433 | + )} | |
| 434 | + </g> | |
| 435 | + ); | |
| 436 | + } | |
| 437 | + | |
| 438 | + if (ann.type === "pen") { | |
| 439 | + return ( | |
| 440 | + <g key={ann.id}> | |
| 441 | + <path | |
| 442 | + d={ann.path} | |
| 443 | + fill="none" | |
| 444 | + stroke={ann.color} | |
| 445 | + strokeWidth={2} | |
| 446 | + strokeLinecap="round" | |
| 447 | + strokeLinejoin="round" | |
| 448 | + opacity={0.8} | |
| 449 | + /> | |
| 450 | + </g> | |
| 451 | + ); | |
| 452 | + } | |
| 453 | + | |
| 454 | + if (ann.type === "note") { | |
| 455 | + return ( | |
| 456 | + <foreignObject key={ann.id} x={ann.x} y={ann.y} width={160} height={90}> | |
| 457 | + <div className="relative"> | |
| 458 | + <textarea | |
| 459 | + className="w-full h-full bg-yellow-100 text-gray-800 text-[10px] p-1.5 rounded shadow-md resize-none border border-yellow-300 outline-none" | |
| 460 | + placeholder="Note..." | |
| 461 | + value={ann.text} | |
| 462 | + onChange={(e) => { | |
| 463 | + if (isEditing) updateNoteText(ann.id, e.target.value); | |
| 464 | + }} | |
| 465 | + readOnly={!isEditing} | |
| 466 | + rows={4} | |
| 467 | + /> | |
| 468 | + {isEditing && ( | |
| 469 | + <button | |
| 470 | + onClick={() => deleteAnnotation(ann.id)} | |
| 471 | + className="absolute -top-1.5 -right-1.5 w-4 h-4 bg-red-500 text-white rounded-full text-[9px] flex items-center justify-center hover:bg-red-600 z-10" | |
| 472 | + > | |
| 473 | + <Trash2 className="w-2.5 h-2.5" /> | |
| 474 | + </button> | |
| 475 | + )} | |
| 476 | + </div> | |
| 477 | + </foreignObject> | |
| 478 | + ); | |
| 479 | + } | |
| 480 | + | |
| 481 | + return null; | |
| 482 | + })} | |
| 483 | + | |
| 484 | + {/* Live drawing preview — highlight rect */} | |
| 485 | + {drawRect && drawRect.w > 0 && ( | |
| 486 | + <rect | |
| 487 | + x={drawRect.x} y={drawRect.y} width={drawRect.w} height={drawRect.h} | |
| 488 | + fill="#fef08a" fillOpacity={0.35} | |
| 489 | + stroke="#ca8a04" strokeWidth={1} strokeDasharray="4 2" | |
| 490 | + /> | |
| 491 | + )} | |
| 492 | + | |
| 493 | + {/* Live pen path preview */} | |
| 494 | + {penPath && ( | |
| 495 | + <path | |
| 496 | + d={penPath} | |
| 497 | + fill="none" stroke="#ef4444" strokeWidth={2} | |
| 498 | + strokeLinecap="round" strokeLinejoin="round" | |
| 499 | + opacity={0.8} | |
| 500 | + /> | |
| 501 | + )} | |
| 502 | + </svg> | |
| 503 | + </div> | |
| 196 | 504 | )} |
| 197 | 505 | </div> |
| 198 | 506 | </div> |
modified
components/preview/plugins/WordPreview.tsx
+48 −18
@@ -1,27 +1,47 @@ | ||
| 1 | 1 | "use client"; |
| 2 | 2 | |
| 3 | −import { useState, useEffect } from "react"; | |
| 3 | +import { useState, useEffect, useRef } from "react"; | |
| 4 | 4 | import { Loader2, Download, FileText } from "lucide-react"; |
| 5 | +import dynamic from "next/dynamic"; | |
| 5 | 6 | import type { PreviewPluginProps } from "./types"; |
| 6 | 7 | |
| 7 | −export default function WordPreview({ file }: PreviewPluginProps) { | |
| 8 | +// Load Tiptap editor only on demand (heavy bundle) | |
| 9 | +const WordTiptapEditor = dynamic(() => import("./WordTiptapEditor"), { | |
| 10 | + ssr: false, | |
| 11 | + loading: () => ( | |
| 12 | + <div className="flex flex-col items-center justify-center h-full gap-3 text-gray-400"> | |
| 13 | + <Loader2 className="w-8 h-8 animate-spin text-blue-400" /> | |
| 14 | + <p className="text-sm">Chargement de l'éditeur…</p> | |
| 15 | + </div> | |
| 16 | + ), | |
| 17 | +}); | |
| 18 | + | |
| 19 | +export default function WordPreview({ file, isEditing, onSave }: PreviewPluginProps) { | |
| 8 | 20 | const [html, setHtml] = useState<string | null>(null); |
| 9 | 21 | const [error, setError] = useState(false); |
| 10 | 22 | const [loading, setLoading] = useState(true); |
| 23 | + // Incremented when returning from edit mode to force re-fetch from server | |
| 24 | + const [fetchKey, setFetchKey] = useState(0); | |
| 25 | + const prevEditingRef = useRef(isEditing); | |
| 11 | 26 | |
| 27 | + // When switching from edit → view, re-fetch to show saved content | |
| 12 | 28 | useEffect(() => { |
| 13 | − let cancelled = false; | |
| 29 | + if (!isEditing && prevEditingRef.current) { | |
| 30 | + setFetchKey((k) => k + 1); | |
| 31 | + } | |
| 32 | + prevEditingRef.current = isEditing; | |
| 33 | + }, [isEditing]); | |
| 14 | 34 | |
| 35 | + useEffect(() => { | |
| 36 | + let cancelled = false; | |
| 15 | 37 | const convert = async () => { |
| 16 | 38 | setLoading(true); |
| 17 | 39 | setError(false); |
| 40 | + setHtml(null); | |
| 18 | 41 | try { |
| 19 | − // Fetch the raw file bytes | |
| 20 | − const res = await fetch(`/api/files/${file.id}/download`); | |
| 42 | + const res = await fetch(`/api/files/${file.id}/download?t=${Date.now()}`); | |
| 21 | 43 | if (!res.ok) throw new Error("Download failed"); |
| 22 | 44 | const arrayBuffer = await res.arrayBuffer(); |
| 23 | − | |
| 24 | − // mammoth converts docx → clean HTML | |
| 25 | 45 | const mammoth = await import("mammoth"); |
| 26 | 46 | const result = await mammoth.convertToHtml( |
| 27 | 47 | { arrayBuffer }, |
@@ -35,21 +55,36 @@ export default function WordPreview({ file }: PreviewPluginProps) { | ||
| 35 | 55 | ], |
| 36 | 56 | } |
| 37 | 57 | ); |
| 38 | − | |
| 39 | − if (!cancelled) { | |
| 40 | − setHtml(result.value || "<p>Document vide.</p>"); | |
| 41 | − } | |
| 58 | + if (!cancelled) setHtml(result.value || "<p>Document vide.</p>"); | |
| 42 | 59 | } catch { |
| 43 | 60 | if (!cancelled) setError(true); |
| 44 | 61 | } finally { |
| 45 | 62 | if (!cancelled) setLoading(false); |
| 46 | 63 | } |
| 47 | 64 | }; |
| 48 | − | |
| 49 | 65 | convert(); |
| 50 | 66 | return () => { cancelled = true; }; |
| 51 | − }, [file.id]); | |
| 67 | + }, [file.id, fetchKey]); | |
| 52 | 68 | |
| 69 | + // Edit mode — render Tiptap editor once html is ready | |
| 70 | + if (isEditing) { | |
| 71 | + if (loading) { | |
| 72 | + return ( | |
| 73 | + <div className="flex flex-col items-center justify-center h-full gap-3 text-gray-400"> | |
| 74 | + <Loader2 className="w-8 h-8 animate-spin text-blue-400" /> | |
| 75 | + <p className="text-sm">Chargement du document…</p> | |
| 76 | + </div> | |
| 77 | + ); | |
| 78 | + } | |
| 79 | + return ( | |
| 80 | + <WordTiptapEditor | |
| 81 | + initialHtml={html ?? "<p>Document vide.</p>"} | |
| 82 | + onSave={onSave ? async (blob, v) => onSave(blob, v) : undefined} | |
| 83 | + /> | |
| 84 | + ); | |
| 85 | + } | |
| 86 | + | |
| 87 | + // Read mode | |
| 53 | 88 | if (loading) { |
| 54 | 89 | return ( |
| 55 | 90 | <div className="flex flex-col items-center justify-center h-full gap-3 text-gray-400"> |
@@ -77,10 +112,8 @@ export default function WordPreview({ file }: PreviewPluginProps) { | ||
| 77 | 112 | |
| 78 | 113 | return ( |
| 79 | 114 | <div className="h-full overflow-auto bg-gray-50 dark:bg-[#1a1a1a]"> |
| 80 | − {/* Page-like container */} | |
| 81 | 115 | <div className="max-w-3xl mx-auto my-6 px-4 sm:px-6"> |
| 82 | 116 | <div className="bg-white dark:bg-[#242424] shadow-md rounded-sm px-8 py-10 sm:px-14 sm:py-12 min-h-[500px]"> |
| 83 | − {/* Download link */} | |
| 84 | 117 | <div className="flex items-center justify-between mb-6 pb-4 border-b border-gray-100 dark:border-white/10"> |
| 85 | 118 | <div className="flex items-center gap-2"> |
| 86 | 119 | <FileText className="w-4 h-4 text-blue-500" /> |
@@ -96,8 +129,6 @@ export default function WordPreview({ file }: PreviewPluginProps) { | ||
| 96 | 129 | Télécharger |
| 97 | 130 | </button> |
| 98 | 131 | </div> |
| 99 | − | |
| 100 | − {/* Rendered Word content */} | |
| 101 | 132 | <div |
| 102 | 133 | className="word-content prose prose-sm sm:prose dark:prose-invert max-w-none" |
| 103 | 134 | dangerouslySetInnerHTML={{ __html: html ?? "" }} |
@@ -105,7 +136,6 @@ export default function WordPreview({ file }: PreviewPluginProps) { | ||
| 105 | 136 | </div> |
| 106 | 137 | </div> |
| 107 | 138 | |
| 108 | − {/* Scoped styles for Word content */} | |
| 109 | 139 | <style jsx global>{` |
| 110 | 140 | .word-content h1 { font-size: 1.6rem; font-weight: 700; margin-bottom: 0.5rem; margin-top: 1.5rem; } |
| 111 | 141 | .word-content h2 { font-size: 1.3rem; font-weight: 600; margin-bottom: 0.4rem; margin-top: 1.2rem; } |
added
components/preview/plugins/WordTiptapEditor.tsx
+174 −0
@@ -0,0 +1,174 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +// This component is imported dynamically from WordPreview (ssr: false already set there) | |
| 4 | +// so we can safely import Tiptap dependencies at the top level. | |
| 5 | +import { useEditor, EditorContent } from "@tiptap/react"; | |
| 6 | +import StarterKit from "@tiptap/starter-kit"; | |
| 7 | +import UnderlineExt from "@tiptap/extension-underline"; | |
| 8 | +import TextAlign from "@tiptap/extension-text-align"; | |
| 9 | +import Highlight from "@tiptap/extension-highlight"; | |
| 10 | +import { | |
| 11 | + Bold, | |
| 12 | + Italic, | |
| 13 | + Underline, | |
| 14 | + List, | |
| 15 | + AlignLeft, | |
| 16 | + AlignCenter, | |
| 17 | + AlignRight, | |
| 18 | + Save, | |
| 19 | + Type, | |
| 20 | + Loader2, | |
| 21 | +} from "lucide-react"; | |
| 22 | +import { useCallback, useState } from "react"; | |
| 23 | + | |
| 24 | +interface WordTiptapEditorProps { | |
| 25 | + initialHtml: string; | |
| 26 | + onSave?: (blob: Blob, asNewVersion?: boolean) => Promise<void>; | |
| 27 | +} | |
| 28 | + | |
| 29 | +export default function WordTiptapEditor({ initialHtml, onSave }: WordTiptapEditorProps) { | |
| 30 | + const [saving, setSaving] = useState(false); | |
| 31 | + | |
| 32 | + const editor = useEditor({ | |
| 33 | + immediatelyRender: false, | |
| 34 | + extensions: [ | |
| 35 | + StarterKit, | |
| 36 | + UnderlineExt, | |
| 37 | + TextAlign.configure({ types: ["heading", "paragraph"] }), | |
| 38 | + Highlight, | |
| 39 | + ], | |
| 40 | + content: initialHtml, | |
| 41 | + editorProps: { | |
| 42 | + attributes: { | |
| 43 | + class: "word-content prose prose-sm sm:prose dark:prose-invert max-w-none focus:outline-none min-h-[400px] tiptap-editor", | |
| 44 | + }, | |
| 45 | + }, | |
| 46 | + }); | |
| 47 | + | |
| 48 | + const handleSave = useCallback(async () => { | |
| 49 | + if (!editor || !onSave) return; | |
| 50 | + setSaving(true); | |
| 51 | + try { | |
| 52 | + const html = editor.getHTML(); | |
| 53 | + const fullHtml = `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`; | |
| 54 | + // Dynamically import browser-compatible dist bundle (avoids Node fs dep) | |
| 55 | + const htmlDocx = await import("html-docx-js/dist/html-docx"); | |
| 56 | + const blob = htmlDocx.asBlob(fullHtml, { | |
| 57 | + orientation: "portrait", | |
| 58 | + margins: { top: 720, bottom: 720, left: 1080, right: 1080 }, | |
| 59 | + }); | |
| 60 | + await onSave(blob, true); | |
| 61 | + } finally { | |
| 62 | + setSaving(false); | |
| 63 | + } | |
| 64 | + }, [editor, onSave]); | |
| 65 | + | |
| 66 | + const btn = (active: boolean, onClick: () => void, title: string, children: React.ReactNode) => ( | |
| 67 | + <button | |
| 68 | + onMouseDown={(e) => { e.preventDefault(); onClick(); }} | |
| 69 | + title={title} | |
| 70 | + className={`p-1.5 rounded transition-colors min-w-[32px] min-h-[32px] flex items-center justify-center ${ | |
| 71 | + active | |
| 72 | + ? "bg-blue-100 dark:bg-blue-900/40 text-blue-600 dark:text-blue-400" | |
| 73 | + : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/10" | |
| 74 | + }`} | |
| 75 | + > | |
| 76 | + {children} | |
| 77 | + </button> | |
| 78 | + ); | |
| 79 | + | |
| 80 | + if (!editor) return ( | |
| 81 | + <div className="flex items-center justify-center h-full gap-3"> | |
| 82 | + <Loader2 className="w-6 h-6 animate-spin text-blue-400" /> | |
| 83 | + </div> | |
| 84 | + ); | |
| 85 | + | |
| 86 | + return ( | |
| 87 | + <div className="absolute inset-0 flex flex-col bg-gray-50 dark:bg-[#1a1a1a]"> | |
| 88 | + {/* Toolbar — sticky so it never scrolls out of view */} | |
| 89 | + <div className="flex items-center gap-0.5 px-3 py-2 bg-white dark:bg-[#242424] border-b border-gray-200 dark:border-white/10 overflow-x-auto shrink-0 flex-nowrap"> | |
| 90 | + {btn(editor.isActive("bold"), () => editor.chain().focus().toggleBold().run(), "Gras", <Bold className="w-3.5 h-3.5" />)} | |
| 91 | + {btn(editor.isActive("italic"), () => editor.chain().focus().toggleItalic().run(), "Italique", <Italic className="w-3.5 h-3.5" />)} | |
| 92 | + {btn(editor.isActive("underline"), () => editor.chain().focus().toggleUnderline().run(), "Souligné", <Underline className="w-3.5 h-3.5" />)} | |
| 93 | + | |
| 94 | + <div className="w-px h-5 bg-gray-200 dark:bg-white/10 mx-1" /> | |
| 95 | + | |
| 96 | + {btn( | |
| 97 | + editor.isActive("heading", { level: 1 }), | |
| 98 | + () => editor.chain().focus().toggleHeading({ level: 1 }).run(), | |
| 99 | + "Titre 1", | |
| 100 | + <span className="text-xs font-bold">H1</span> | |
| 101 | + )} | |
| 102 | + {btn( | |
| 103 | + editor.isActive("heading", { level: 2 }), | |
| 104 | + () => editor.chain().focus().toggleHeading({ level: 2 }).run(), | |
| 105 | + "Titre 2", | |
| 106 | + <span className="text-xs font-bold">H2</span> | |
| 107 | + )} | |
| 108 | + {btn( | |
| 109 | + editor.isActive("heading", { level: 3 }), | |
| 110 | + () => editor.chain().focus().toggleHeading({ level: 3 }).run(), | |
| 111 | + "Titre 3", | |
| 112 | + <span className="text-xs font-bold">H3</span> | |
| 113 | + )} | |
| 114 | + {btn(false, () => editor.chain().focus().setParagraph().run(), "Paragraphe", <Type className="w-3.5 h-3.5" />)} | |
| 115 | + | |
| 116 | + <div className="w-px h-5 bg-gray-200 dark:bg-white/10 mx-1" /> | |
| 117 | + | |
| 118 | + {btn(editor.isActive("bulletList"), () => editor.chain().focus().toggleBulletList().run(), "Liste à puces", <List className="w-3.5 h-3.5" />)} | |
| 119 | + | |
| 120 | + <div className="w-px h-5 bg-gray-200 dark:bg-white/10 mx-1" /> | |
| 121 | + | |
| 122 | + {btn(editor.isActive({ textAlign: "left" }), () => editor.chain().focus().setTextAlign("left").run(), "Gauche", <AlignLeft className="w-3.5 h-3.5" />)} | |
| 123 | + {btn(editor.isActive({ textAlign: "center" }), () => editor.chain().focus().setTextAlign("center").run(), "Centré", <AlignCenter className="w-3.5 h-3.5" />)} | |
| 124 | + {btn(editor.isActive({ textAlign: "right" }), () => editor.chain().focus().setTextAlign("right").run(), "Droite", <AlignRight className="w-3.5 h-3.5" />)} | |
| 125 | + | |
| 126 | + <div className="flex-1" /> | |
| 127 | + | |
| 128 | + <button | |
| 129 | + onClick={handleSave} | |
| 130 | + disabled={saving} | |
| 131 | + className="flex items-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors disabled:opacity-60" | |
| 132 | + > | |
| 133 | + {saving ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Save className="w-3.5 h-3.5" />} | |
| 134 | + Sauvegarder | |
| 135 | + </button> | |
| 136 | + </div> | |
| 137 | + | |
| 138 | + {/* Editor content area */} | |
| 139 | + <div className="flex-1 overflow-auto py-6"> | |
| 140 | + <div className="max-w-3xl mx-auto px-4 sm:px-6"> | |
| 141 | + <div className="bg-white dark:bg-[#242424] shadow-md rounded-sm px-8 py-10 sm:px-14 sm:py-12 min-h-[500px]"> | |
| 142 | + <EditorContent editor={editor} /> | |
| 143 | + </div> | |
| 144 | + </div> | |
| 145 | + </div> | |
| 146 | + | |
| 147 | + <style jsx global>{` | |
| 148 | + .tiptap-editor { min-height: 400px; outline: none; } | |
| 149 | + .tiptap-editor p.is-editor-empty:first-child::before { | |
| 150 | + content: attr(data-placeholder); | |
| 151 | + color: #9ca3af; | |
| 152 | + pointer-events: none; | |
| 153 | + float: left; | |
| 154 | + height: 0; | |
| 155 | + } | |
| 156 | + .word-content h1 { font-size: 1.6rem; font-weight: 700; margin-bottom: 0.5rem; margin-top: 1.5rem; } | |
| 157 | + .word-content h2 { font-size: 1.3rem; font-weight: 600; margin-bottom: 0.4rem; margin-top: 1.2rem; } | |
| 158 | + .word-content h3 { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.3rem; margin-top: 1rem; } | |
| 159 | + .word-content p { margin-bottom: 0.6rem; line-height: 1.7; } | |
| 160 | + .word-content ul, .word-content ol { padding-left: 1.5rem; margin-bottom: 0.6rem; } | |
| 161 | + .word-content li { margin-bottom: 0.2rem; } | |
| 162 | + .word-content table { border-collapse: collapse; width: 100%; margin-bottom: 1rem; } | |
| 163 | + .word-content td, .word-content th { border: 1px solid #d1d5db; padding: 0.4rem 0.6rem; text-align: left; } | |
| 164 | + .word-content th { background: #f3f4f6; font-weight: 600; } | |
| 165 | + .dark .word-content td, .dark .word-content th { border-color: rgba(255,255,255,0.15); } | |
| 166 | + .dark .word-content th { background: rgba(255,255,255,0.08); } | |
| 167 | + .word-content strong { font-weight: 700; } | |
| 168 | + .word-content em { font-style: italic; } | |
| 169 | + .word-content a { color: #3b82f6; text-decoration: underline; } | |
| 170 | + .word-content img { max-width: 100%; height: auto; margin: 0.5rem 0; } | |
| 171 | + `}</style> | |
| 172 | + </div> | |
| 173 | + ); | |
| 174 | +} | |
modified
components/preview/plugins/index.ts
+19 −0
@@ -12,6 +12,7 @@ const CsvJsonPreview = dynamic(() => import("./CsvJsonPreview"), { ssr: false }) | ||
| 12 | 12 | const MarkdownPreview = dynamic(() => import("./MarkdownPreview"), { ssr: false }); |
| 13 | 13 | const ArchivePreview = dynamic(() => import("./ArchivePreview"), { ssr: false }); |
| 14 | 14 | const WordPreview = dynamic(() => import("./WordPreview"), { ssr: false }); |
| 15 | +const ExcelPreview = dynamic(() => import("./ExcelPreview"), { ssr: false }); | |
| 15 | 16 | const FallbackPreview = dynamic(() => import("./FallbackPreview"), { ssr: false }); |
| 16 | 17 | |
| 17 | 18 | const WORD_MIMES = [ |
@@ -20,6 +21,14 @@ const WORD_MIMES = [ | ||
| 20 | 21 | "application/vnd.oasis.opendocument.text", // .odt |
| 21 | 22 | ]; |
| 22 | 23 | |
| 24 | +const EXCEL_MIMES = [ | |
| 25 | + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx | |
| 26 | + "application/vnd.ms-excel", // .xls | |
| 27 | + "application/vnd.ms-excel.sheet.macroEnabled.12", // .xlsm | |
| 28 | + "application/vnd.ms-excel.sheet.binary.macroEnabled.12", // .xlsb | |
| 29 | + "application/vnd.oasis.opendocument.spreadsheet", // .ods | |
| 30 | +]; | |
| 31 | + | |
| 23 | 32 | const plugins: PreviewPlugin[] = [ |
| 24 | 33 | { |
| 25 | 34 | id: "image", |
@@ -48,6 +57,7 @@ const plugins: PreviewPlugin[] = [ | ||
| 48 | 57 | canHandle: (mime) => mime === "application/pdf", |
| 49 | 58 | priority: 10, |
| 50 | 59 | component: PdfPreview as any, |
| 60 | + supportsEdit: true, | |
| 51 | 61 | }, |
| 52 | 62 | { |
| 53 | 63 | id: "csv-json", |
@@ -85,6 +95,15 @@ const plugins: PreviewPlugin[] = [ | ||
| 85 | 95 | canHandle: (mime) => WORD_MIMES.includes(mime), |
| 86 | 96 | priority: 15, |
| 87 | 97 | component: WordPreview as any, |
| 98 | + supportsEdit: true, | |
| 99 | + }, | |
| 100 | + { | |
| 101 | + id: "excel", | |
| 102 | + name: "Excel", | |
| 103 | + canHandle: (mime) => EXCEL_MIMES.includes(mime), | |
| 104 | + priority: 15, | |
| 105 | + component: ExcelPreview as any, | |
| 106 | + supportsEdit: true, | |
| 88 | 107 | }, |
| 89 | 108 | { |
| 90 | 109 | id: "archive", |
modified
package-lock.json
+1390 −4
@@ -31,10 +31,17 @@ | ||
| 31 | 31 | "@codemirror/theme-one-dark": "^6.1.3", |
| 32 | 32 | "@codemirror/view": "^6.39.14", |
| 33 | 33 | "@prisma/client": "^5.22.0", |
| 34 | + "@tiptap/extension-highlight": "^3.20.0", | |
| 35 | + "@tiptap/extension-text-align": "^3.20.0", | |
| 36 | + "@tiptap/extension-underline": "^3.20.0", | |
| 37 | + "@tiptap/pm": "^3.20.0", | |
| 38 | + "@tiptap/react": "^3.20.0", | |
| 39 | + "@tiptap/starter-kit": "^3.20.0", | |
| 34 | 40 | "archiver": "^7.0.1", |
| 35 | 41 | "bcryptjs": "^3.0.3", |
| 36 | 42 | "diff": "^8.0.3", |
| 37 | 43 | "framer-motion": "^12.34.0", |
| 44 | + "html-docx-js": "^0.3.1", | |
| 38 | 45 | "iron-session": "^8.0.4", |
| 39 | 46 | "jszip": "^3.10.1", |
| 40 | 47 | "lucide-react": "^0.564.0", |
@@ -54,14 +61,17 @@ | ||
| 54 | 61 | "react-hot-toast": "^2.6.0", |
| 55 | 62 | "react-markdown": "^10.1.0", |
| 56 | 63 | "react-syntax-highlighter": "^16.1.0", |
| 64 | + "remark-gfm": "^4.0.1", | |
| 57 | 65 | "sharp": "^0.34.5", |
| 58 | 66 | "uuid": "^13.0.0", |
| 59 | − "wavesurfer.js": "^7.12.1" | |
| 67 | + "wavesurfer.js": "^7.12.1", | |
| 68 | + "xlsx": "^0.18.5" | |
| 60 | 69 | }, |
| 61 | 70 | "devDependencies": { |
| 62 | 71 | "@types/archiver": "^7.0.0", |
| 63 | 72 | "@types/bcryptjs": "^2.4.6", |
| 64 | 73 | "@types/diff": "^7.0.2", |
| 74 | + "@types/html-docx-js": "^0.3.4", | |
| 65 | 75 | "@types/mime-types": "^3.0.1", |
| 66 | 76 | "@types/node": "^20", |
| 67 | 77 | "@types/papaparse": "^5.5.2", |
@@ -919,6 +929,34 @@ | ||
| 919 | 929 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" |
| 920 | 930 | } |
| 921 | 931 | }, |
| 932 | + "node_modules/@floating-ui/core": { | |
| 933 | + "version": "1.7.4", | |
| 934 | + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", | |
| 935 | + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", | |
| 936 | + "license": "MIT", | |
| 937 | + "optional": true, | |
| 938 | + "dependencies": { | |
| 939 | + "@floating-ui/utils": "^0.2.10" | |
| 940 | + } | |
| 941 | + }, | |
| 942 | + "node_modules/@floating-ui/dom": { | |
| 943 | + "version": "1.7.5", | |
| 944 | + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", | |
| 945 | + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", | |
| 946 | + "license": "MIT", | |
| 947 | + "optional": true, | |
| 948 | + "dependencies": { | |
| 949 | + "@floating-ui/core": "^1.7.4", | |
| 950 | + "@floating-ui/utils": "^0.2.10" | |
| 951 | + } | |
| 952 | + }, | |
| 953 | + "node_modules/@floating-ui/utils": { | |
| 954 | + "version": "0.2.10", | |
| 955 | + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", | |
| 956 | + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", | |
| 957 | + "license": "MIT", | |
| 958 | + "optional": true | |
| 959 | + }, | |
| 922 | 960 | "node_modules/@humanwhocodes/config-array": { |
| 923 | 961 | "version": "0.13.0", |
| 924 | 962 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", |
@@ -2296,6 +2334,12 @@ | ||
| 2296 | 2334 | "@prisma/debug": "5.22.0" |
| 2297 | 2335 | } |
| 2298 | 2336 | }, |
| 2337 | + "node_modules/@remirror/core-constants": { | |
| 2338 | + "version": "3.0.0", | |
| 2339 | + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", | |
| 2340 | + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", | |
| 2341 | + "license": "MIT" | |
| 2342 | + }, | |
| 2299 | 2343 | "node_modules/@rtsao/scc": { |
| 2300 | 2344 | "version": "1.1.0", |
| 2301 | 2345 | "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", |
@@ -2335,6 +2379,466 @@ | ||
| 2335 | 2379 | "tslib": "^2.4.0" |
| 2336 | 2380 | } |
| 2337 | 2381 | }, |
| 2382 | + "node_modules/@tiptap/core": { | |
| 2383 | + "version": "3.20.0", | |
| 2384 | + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.0.tgz", | |
| 2385 | + "integrity": "sha512-aC9aROgia/SpJqhsXFiX9TsligL8d+oeoI8W3u00WI45s0VfsqjgeKQLDLF7Tu7hC+7F02teC84SAHuup003VQ==", | |
| 2386 | + "license": "MIT", | |
| 2387 | + "funding": { | |
| 2388 | + "type": "github", | |
| 2389 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2390 | + }, | |
| 2391 | + "peerDependencies": { | |
| 2392 | + "@tiptap/pm": "^3.20.0" | |
| 2393 | + } | |
| 2394 | + }, | |
| 2395 | + "node_modules/@tiptap/extension-blockquote": { | |
| 2396 | + "version": "3.20.0", | |
| 2397 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.20.0.tgz", | |
| 2398 | + "integrity": "sha512-LQzn6aGtL4WXz2+rYshl/7/VnP2qJTpD7fWL96GXAzhqviPEY1bJES7poqJb3MU/gzl8VJUVzVzU1VoVfUKlbA==", | |
| 2399 | + "license": "MIT", | |
| 2400 | + "funding": { | |
| 2401 | + "type": "github", | |
| 2402 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2403 | + }, | |
| 2404 | + "peerDependencies": { | |
| 2405 | + "@tiptap/core": "^3.20.0" | |
| 2406 | + } | |
| 2407 | + }, | |
| 2408 | + "node_modules/@tiptap/extension-bold": { | |
| 2409 | + "version": "3.20.0", | |
| 2410 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.20.0.tgz", | |
| 2411 | + "integrity": "sha512-sQklEWiyf58yDjiHtm5vmkVjfIc/cBuSusmCsQ0q9vGYnEF1iOHKhGpvnCeEXNeqF3fiJQRlquzt/6ymle3Iwg==", | |
| 2412 | + "license": "MIT", | |
| 2413 | + "funding": { | |
| 2414 | + "type": "github", | |
| 2415 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2416 | + }, | |
| 2417 | + "peerDependencies": { | |
| 2418 | + "@tiptap/core": "^3.20.0" | |
| 2419 | + } | |
| 2420 | + }, | |
| 2421 | + "node_modules/@tiptap/extension-bubble-menu": { | |
| 2422 | + "version": "3.20.0", | |
| 2423 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.20.0.tgz", | |
| 2424 | + "integrity": "sha512-MDosUfs8Tj+nwg8RC+wTMWGkLJORXmbR6YZgbiX4hrc7G90Gopdd6kj6ht5/T8t7dLLaX7N0+DEHdUEPGED7dw==", | |
| 2425 | + "license": "MIT", | |
| 2426 | + "optional": true, | |
| 2427 | + "dependencies": { | |
| 2428 | + "@floating-ui/dom": "^1.0.0" | |
| 2429 | + }, | |
| 2430 | + "funding": { | |
| 2431 | + "type": "github", | |
| 2432 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2433 | + }, | |
| 2434 | + "peerDependencies": { | |
| 2435 | + "@tiptap/core": "^3.20.0", | |
| 2436 | + "@tiptap/pm": "^3.20.0" | |
| 2437 | + } | |
| 2438 | + }, | |
| 2439 | + "node_modules/@tiptap/extension-bullet-list": { | |
| 2440 | + "version": "3.20.0", | |
| 2441 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.20.0.tgz", | |
| 2442 | + "integrity": "sha512-OcKMeopBbqWzhSi6o8nNz0aayogg1sfOAhto3NxJu3Ya32dwBFqmHXSYM6uW4jOphNvVPyjiq9aNRh3qTdd1dw==", | |
| 2443 | + "license": "MIT", | |
| 2444 | + "funding": { | |
| 2445 | + "type": "github", | |
| 2446 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2447 | + }, | |
| 2448 | + "peerDependencies": { | |
| 2449 | + "@tiptap/extension-list": "^3.20.0" | |
| 2450 | + } | |
| 2451 | + }, | |
| 2452 | + "node_modules/@tiptap/extension-code": { | |
| 2453 | + "version": "3.20.0", | |
| 2454 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.20.0.tgz", | |
| 2455 | + "integrity": "sha512-TYDWFeSQ9umiyrqsT6VecbuhL8XIHkUhO+gEk0sVvH67ZLwjFDhAIIgWIr1/dbIGPcvMZM19E7xUUhAdIaXaOQ==", | |
| 2456 | + "license": "MIT", | |
| 2457 | + "funding": { | |
| 2458 | + "type": "github", | |
| 2459 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2460 | + }, | |
| 2461 | + "peerDependencies": { | |
| 2462 | + "@tiptap/core": "^3.20.0" | |
| 2463 | + } | |
| 2464 | + }, | |
| 2465 | + "node_modules/@tiptap/extension-code-block": { | |
| 2466 | + "version": "3.20.0", | |
| 2467 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.20.0.tgz", | |
| 2468 | + "integrity": "sha512-lBbmNek14aCjrHcBcq3PRqWfNLvC6bcRa2Osc6e/LtmXlcpype4f6n+Yx+WZ+f2uUh0UmDRCz7BEyUETEsDmlQ==", | |
| 2469 | + "license": "MIT", | |
| 2470 | + "funding": { | |
| 2471 | + "type": "github", | |
| 2472 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2473 | + }, | |
| 2474 | + "peerDependencies": { | |
| 2475 | + "@tiptap/core": "^3.20.0", | |
| 2476 | + "@tiptap/pm": "^3.20.0" | |
| 2477 | + } | |
| 2478 | + }, | |
| 2479 | + "node_modules/@tiptap/extension-document": { | |
| 2480 | + "version": "3.20.0", | |
| 2481 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.20.0.tgz", | |
| 2482 | + "integrity": "sha512-oJfLIG3vAtZo/wg29WiBcyWt22KUgddpP8wqtCE+kY5Dw8znLR9ehNmVWlSWJA5OJUMO0ntAHx4bBT+I2MBd5w==", | |
| 2483 | + "license": "MIT", | |
| 2484 | + "funding": { | |
| 2485 | + "type": "github", | |
| 2486 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2487 | + }, | |
| 2488 | + "peerDependencies": { | |
| 2489 | + "@tiptap/core": "^3.20.0" | |
| 2490 | + } | |
| 2491 | + }, | |
| 2492 | + "node_modules/@tiptap/extension-dropcursor": { | |
| 2493 | + "version": "3.20.0", | |
| 2494 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.20.0.tgz", | |
| 2495 | + "integrity": "sha512-d+cxplRlktVgZPwatnc34IArlppM0IFKS1J5wLk+ba1jidizsbMVh45tP/BTK2flhyfRqcNoB5R0TArhUpbkNQ==", | |
| 2496 | + "license": "MIT", | |
| 2497 | + "funding": { | |
| 2498 | + "type": "github", | |
| 2499 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2500 | + }, | |
| 2501 | + "peerDependencies": { | |
| 2502 | + "@tiptap/extensions": "^3.20.0" | |
| 2503 | + } | |
| 2504 | + }, | |
| 2505 | + "node_modules/@tiptap/extension-floating-menu": { | |
| 2506 | + "version": "3.20.0", | |
| 2507 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.20.0.tgz", | |
| 2508 | + "integrity": "sha512-rYs4Bv5pVjqZ/2vvR6oe7ammZapkAwN51As/WDbemvYDjfOGRqK58qGauUjYZiDzPOEIzI2mxGwsZ4eJhPW4Ig==", | |
| 2509 | + "license": "MIT", | |
| 2510 | + "optional": true, | |
| 2511 | + "funding": { | |
| 2512 | + "type": "github", | |
| 2513 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2514 | + }, | |
| 2515 | + "peerDependencies": { | |
| 2516 | + "@floating-ui/dom": "^1.0.0", | |
| 2517 | + "@tiptap/core": "^3.20.0", | |
| 2518 | + "@tiptap/pm": "^3.20.0" | |
| 2519 | + } | |
| 2520 | + }, | |
| 2521 | + "node_modules/@tiptap/extension-gapcursor": { | |
| 2522 | + "version": "3.20.0", | |
| 2523 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.20.0.tgz", | |
| 2524 | + "integrity": "sha512-P/LasfvG9/qFq43ZAlNbAnPnXC+/RJf49buTrhtFvI9Zg0+Lbpjx1oh6oMHB19T88Y28KtrckfFZ8aTSUWDq6w==", | |
| 2525 | + "license": "MIT", | |
| 2526 | + "funding": { | |
| 2527 | + "type": "github", | |
| 2528 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2529 | + }, | |
| 2530 | + "peerDependencies": { | |
| 2531 | + "@tiptap/extensions": "^3.20.0" | |
| 2532 | + } | |
| 2533 | + }, | |
| 2534 | + "node_modules/@tiptap/extension-hard-break": { | |
| 2535 | + "version": "3.20.0", | |
| 2536 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.20.0.tgz", | |
| 2537 | + "integrity": "sha512-rqvhMOw4f+XQmEthncbvDjgLH6fz8L9splnKZC7OeS0eX8b0qd7+xI1u5kyxF3KA2Z0BnigES++jjWuecqV6mA==", | |
| 2538 | + "license": "MIT", | |
| 2539 | + "funding": { | |
| 2540 | + "type": "github", | |
| 2541 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2542 | + }, | |
| 2543 | + "peerDependencies": { | |
| 2544 | + "@tiptap/core": "^3.20.0" | |
| 2545 | + } | |
| 2546 | + }, | |
| 2547 | + "node_modules/@tiptap/extension-heading": { | |
| 2548 | + "version": "3.20.0", | |
| 2549 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.20.0.tgz", | |
| 2550 | + "integrity": "sha512-JgJhurnCe3eN6a0lEsNQM/46R1bcwzwWWZEFDSb1P9dR8+t1/5v7cMZWsSInpD7R4/74iJn0+M5hcXLwCmBmYA==", | |
| 2551 | + "license": "MIT", | |
| 2552 | + "funding": { | |
| 2553 | + "type": "github", | |
| 2554 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2555 | + }, | |
| 2556 | + "peerDependencies": { | |
| 2557 | + "@tiptap/core": "^3.20.0" | |
| 2558 | + } | |
| 2559 | + }, | |
| 2560 | + "node_modules/@tiptap/extension-highlight": { | |
| 2561 | + "version": "3.20.0", | |
| 2562 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.20.0.tgz", | |
| 2563 | + "integrity": "sha512-ANL1wFz0s1ScNR4uBfO0s6Sz+qqGp2u6ynrCVk6TCT3d10CQ+gD1gSDTrVRC3CtlMKtHHH4fYrFAq284+J0gKA==", | |
| 2564 | + "license": "MIT", | |
| 2565 | + "funding": { | |
| 2566 | + "type": "github", | |
| 2567 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2568 | + }, | |
| 2569 | + "peerDependencies": { | |
| 2570 | + "@tiptap/core": "^3.20.0" | |
| 2571 | + } | |
| 2572 | + }, | |
| 2573 | + "node_modules/@tiptap/extension-horizontal-rule": { | |
| 2574 | + "version": "3.20.0", | |
| 2575 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.20.0.tgz", | |
| 2576 | + "integrity": "sha512-6uvcutFMv+9wPZgptDkbRDjAm3YVxlibmkhWD5GuaWwS9L/yUtobpI3GycujRSUZ8D3q6Q9J7LqpmQtQRTalWA==", | |
| 2577 | + "license": "MIT", | |
| 2578 | + "funding": { | |
| 2579 | + "type": "github", | |
| 2580 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2581 | + }, | |
| 2582 | + "peerDependencies": { | |
| 2583 | + "@tiptap/core": "^3.20.0", | |
| 2584 | + "@tiptap/pm": "^3.20.0" | |
| 2585 | + } | |
| 2586 | + }, | |
| 2587 | + "node_modules/@tiptap/extension-italic": { | |
| 2588 | + "version": "3.20.0", | |
| 2589 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.20.0.tgz", | |
| 2590 | + "integrity": "sha512-/DhnKQF8yN8RxtuL8abZ28wd5281EaGoE2Oha35zXSOF1vNYnbyt8Ymkv/7u1BcWEWTvRPgaju0YCGXisPRLYw==", | |
| 2591 | + "license": "MIT", | |
| 2592 | + "funding": { | |
| 2593 | + "type": "github", | |
| 2594 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2595 | + }, | |
| 2596 | + "peerDependencies": { | |
| 2597 | + "@tiptap/core": "^3.20.0" | |
| 2598 | + } | |
| 2599 | + }, | |
| 2600 | + "node_modules/@tiptap/extension-link": { | |
| 2601 | + "version": "3.20.0", | |
| 2602 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.20.0.tgz", | |
| 2603 | + "integrity": "sha512-qI/5A+R0ZWBxo/8HxSn1uOyr7odr3xHBZ/gzOR1GUJaZqjlJxkWFX0RtXMbLKEGEvT25o345cF7b0wFznEh8qA==", | |
| 2604 | + "license": "MIT", | |
| 2605 | + "dependencies": { | |
| 2606 | + "linkifyjs": "^4.3.2" | |
| 2607 | + }, | |
| 2608 | + "funding": { | |
| 2609 | + "type": "github", | |
| 2610 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2611 | + }, | |
| 2612 | + "peerDependencies": { | |
| 2613 | + "@tiptap/core": "^3.20.0", | |
| 2614 | + "@tiptap/pm": "^3.20.0" | |
| 2615 | + } | |
| 2616 | + }, | |
| 2617 | + "node_modules/@tiptap/extension-list": { | |
| 2618 | + "version": "3.20.0", | |
| 2619 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.20.0.tgz", | |
| 2620 | + "integrity": "sha512-+V0/gsVWAv+7vcY0MAe6D52LYTIicMSHw00wz3ISZgprSb2yQhJ4+4gurOnUrQ4Du3AnRQvxPROaofwxIQ66WQ==", | |
| 2621 | + "license": "MIT", | |
| 2622 | + "funding": { | |
| 2623 | + "type": "github", | |
| 2624 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2625 | + }, | |
| 2626 | + "peerDependencies": { | |
| 2627 | + "@tiptap/core": "^3.20.0", | |
| 2628 | + "@tiptap/pm": "^3.20.0" | |
| 2629 | + } | |
| 2630 | + }, | |
| 2631 | + "node_modules/@tiptap/extension-list-item": { | |
| 2632 | + "version": "3.20.0", | |
| 2633 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.20.0.tgz", | |
| 2634 | + "integrity": "sha512-qEtjaaGPuqaFB4VpLrGDoIe9RHnckxPfu6d3rc22ap6TAHCDyRv05CEyJogqccnFceG/v5WN4znUBER8RWnWHA==", | |
| 2635 | + "license": "MIT", | |
| 2636 | + "funding": { | |
| 2637 | + "type": "github", | |
| 2638 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2639 | + }, | |
| 2640 | + "peerDependencies": { | |
| 2641 | + "@tiptap/extension-list": "^3.20.0" | |
| 2642 | + } | |
| 2643 | + }, | |
| 2644 | + "node_modules/@tiptap/extension-list-keymap": { | |
| 2645 | + "version": "3.20.0", | |
| 2646 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.20.0.tgz", | |
| 2647 | + "integrity": "sha512-Z4GvKy04Ms4cLFN+CY6wXswd36xYsT2p/YL0V89LYFMZTerOeTjFYlndzn6svqL8NV1PRT5Diw4WTTxJSmcJPA==", | |
| 2648 | + "license": "MIT", | |
| 2649 | + "funding": { | |
| 2650 | + "type": "github", | |
| 2651 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2652 | + }, | |
| 2653 | + "peerDependencies": { | |
| 2654 | + "@tiptap/extension-list": "^3.20.0" | |
| 2655 | + } | |
| 2656 | + }, | |
| 2657 | + "node_modules/@tiptap/extension-ordered-list": { | |
| 2658 | + "version": "3.20.0", | |
| 2659 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.20.0.tgz", | |
| 2660 | + "integrity": "sha512-jVKnJvrizLk7etwBMfyoj6H2GE4M+PD4k7Bwp6Bh1ohBWtfIA1TlngdS842Mx5i1VB2e3UWIwr8ZH46gl6cwMA==", | |
| 2661 | + "license": "MIT", | |
| 2662 | + "funding": { | |
| 2663 | + "type": "github", | |
| 2664 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2665 | + }, | |
| 2666 | + "peerDependencies": { | |
| 2667 | + "@tiptap/extension-list": "^3.20.0" | |
| 2668 | + } | |
| 2669 | + }, | |
| 2670 | + "node_modules/@tiptap/extension-paragraph": { | |
| 2671 | + "version": "3.20.0", | |
| 2672 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.20.0.tgz", | |
| 2673 | + "integrity": "sha512-mM99zK4+RnEXIMCv6akfNATAs0Iija6FgyFA9J9NZ6N4o8y9QiNLLa6HjLpAC+W+VoCgQIekyoF/Q9ftxmAYDQ==", | |
| 2674 | + "license": "MIT", | |
| 2675 | + "funding": { | |
| 2676 | + "type": "github", | |
| 2677 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2678 | + }, | |
| 2679 | + "peerDependencies": { | |
| 2680 | + "@tiptap/core": "^3.20.0" | |
| 2681 | + } | |
| 2682 | + }, | |
| 2683 | + "node_modules/@tiptap/extension-strike": { | |
| 2684 | + "version": "3.20.0", | |
| 2685 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.20.0.tgz", | |
| 2686 | + "integrity": "sha512-0vcTZRRAiDfon3VM1mHBr9EFmTkkUXMhm0Xtdtn0bGe+sIqufyi+hUYTEw93EQOD9XNsPkrud6jzQNYpX2H3AQ==", | |
| 2687 | + "license": "MIT", | |
| 2688 | + "funding": { | |
| 2689 | + "type": "github", | |
| 2690 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2691 | + }, | |
| 2692 | + "peerDependencies": { | |
| 2693 | + "@tiptap/core": "^3.20.0" | |
| 2694 | + } | |
| 2695 | + }, | |
| 2696 | + "node_modules/@tiptap/extension-text": { | |
| 2697 | + "version": "3.20.0", | |
| 2698 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.20.0.tgz", | |
| 2699 | + "integrity": "sha512-tf8bE8tSaOEWabCzPm71xwiUhyMFKqY9jkP5af3Kr1/F45jzZFIQAYZooHI/+zCHRrgJ99MQHKHe1ZNvODrKHQ==", | |
| 2700 | + "license": "MIT", | |
| 2701 | + "funding": { | |
| 2702 | + "type": "github", | |
| 2703 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2704 | + }, | |
| 2705 | + "peerDependencies": { | |
| 2706 | + "@tiptap/core": "^3.20.0" | |
| 2707 | + } | |
| 2708 | + }, | |
| 2709 | + "node_modules/@tiptap/extension-text-align": { | |
| 2710 | + "version": "3.20.0", | |
| 2711 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.20.0.tgz", | |
| 2712 | + "integrity": "sha512-4s0r+bovtH6yeGDUD+Ui8j5WOV5koB5P6AuzOMqoLwaFGRSkKf64ly6DXjjmjIgnYCLZN/XO6llaQKVVdvad2g==", | |
| 2713 | + "license": "MIT", | |
| 2714 | + "funding": { | |
| 2715 | + "type": "github", | |
| 2716 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2717 | + }, | |
| 2718 | + "peerDependencies": { | |
| 2719 | + "@tiptap/core": "^3.20.0" | |
| 2720 | + } | |
| 2721 | + }, | |
| 2722 | + "node_modules/@tiptap/extension-underline": { | |
| 2723 | + "version": "3.20.0", | |
| 2724 | + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.20.0.tgz", | |
| 2725 | + "integrity": "sha512-LzNXuy2jwR/y+ymoUqC72TiGzbOCjioIjsDu0MNYpHuHqTWPK5aV9Mh0nbZcYFy/7fPlV1q0W139EbJeYBZEAQ==", | |
| 2726 | + "license": "MIT", | |
| 2727 | + "funding": { | |
| 2728 | + "type": "github", | |
| 2729 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2730 | + }, | |
| 2731 | + "peerDependencies": { | |
| 2732 | + "@tiptap/core": "^3.20.0" | |
| 2733 | + } | |
| 2734 | + }, | |
| 2735 | + "node_modules/@tiptap/extensions": { | |
| 2736 | + "version": "3.20.0", | |
| 2737 | + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.20.0.tgz", | |
| 2738 | + "integrity": "sha512-HIsXX942w3nbxEQBlMAAR/aa6qiMBEP7CsSMxaxmTIVAmW35p6yUASw6GdV1u0o3lCZjXq2OSRMTskzIqi5uLg==", | |
| 2739 | + "license": "MIT", | |
| 2740 | + "funding": { | |
| 2741 | + "type": "github", | |
| 2742 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2743 | + }, | |
| 2744 | + "peerDependencies": { | |
| 2745 | + "@tiptap/core": "^3.20.0", | |
| 2746 | + "@tiptap/pm": "^3.20.0" | |
| 2747 | + } | |
| 2748 | + }, | |
| 2749 | + "node_modules/@tiptap/pm": { | |
| 2750 | + "version": "3.20.0", | |
| 2751 | + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.0.tgz", | |
| 2752 | + "integrity": "sha512-jn+2KnQZn+b+VXr8EFOJKsnjVNaA4diAEr6FOazupMt8W8ro1hfpYtZ25JL87Kao/WbMze55sd8M8BDXLUKu1A==", | |
| 2753 | + "license": "MIT", | |
| 2754 | + "dependencies": { | |
| 2755 | + "prosemirror-changeset": "^2.3.0", | |
| 2756 | + "prosemirror-collab": "^1.3.1", | |
| 2757 | + "prosemirror-commands": "^1.6.2", | |
| 2758 | + "prosemirror-dropcursor": "^1.8.1", | |
| 2759 | + "prosemirror-gapcursor": "^1.3.2", | |
| 2760 | + "prosemirror-history": "^1.4.1", | |
| 2761 | + "prosemirror-inputrules": "^1.4.0", | |
| 2762 | + "prosemirror-keymap": "^1.2.2", | |
| 2763 | + "prosemirror-markdown": "^1.13.1", | |
| 2764 | + "prosemirror-menu": "^1.2.4", | |
| 2765 | + "prosemirror-model": "^1.24.1", | |
| 2766 | + "prosemirror-schema-basic": "^1.2.3", | |
| 2767 | + "prosemirror-schema-list": "^1.5.0", | |
| 2768 | + "prosemirror-state": "^1.4.3", | |
| 2769 | + "prosemirror-tables": "^1.6.4", | |
| 2770 | + "prosemirror-trailing-node": "^3.0.0", | |
| 2771 | + "prosemirror-transform": "^1.10.2", | |
| 2772 | + "prosemirror-view": "^1.38.1" | |
| 2773 | + }, | |
| 2774 | + "funding": { | |
| 2775 | + "type": "github", | |
| 2776 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2777 | + } | |
| 2778 | + }, | |
| 2779 | + "node_modules/@tiptap/react": { | |
| 2780 | + "version": "3.20.0", | |
| 2781 | + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.20.0.tgz", | |
| 2782 | + "integrity": "sha512-jFLNzkmn18zqefJwPje0PPd9VhZ7Oy28YHiSvSc7YpBnQIbuN/HIxZ2lrOsKyEHta0WjRZjfU5X1pGxlbcGwOA==", | |
| 2783 | + "license": "MIT", | |
| 2784 | + "dependencies": { | |
| 2785 | + "@types/use-sync-external-store": "^0.0.6", | |
| 2786 | + "fast-equals": "^5.3.3", | |
| 2787 | + "use-sync-external-store": "^1.4.0" | |
| 2788 | + }, | |
| 2789 | + "funding": { | |
| 2790 | + "type": "github", | |
| 2791 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2792 | + }, | |
| 2793 | + "optionalDependencies": { | |
| 2794 | + "@tiptap/extension-bubble-menu": "^3.20.0", | |
| 2795 | + "@tiptap/extension-floating-menu": "^3.20.0" | |
| 2796 | + }, | |
| 2797 | + "peerDependencies": { | |
| 2798 | + "@tiptap/core": "^3.20.0", | |
| 2799 | + "@tiptap/pm": "^3.20.0", | |
| 2800 | + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", | |
| 2801 | + "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", | |
| 2802 | + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", | |
| 2803 | + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" | |
| 2804 | + } | |
| 2805 | + }, | |
| 2806 | + "node_modules/@tiptap/starter-kit": { | |
| 2807 | + "version": "3.20.0", | |
| 2808 | + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.20.0.tgz", | |
| 2809 | + "integrity": "sha512-W4+1re35pDNY/7rpXVg+OKo/Fa4Gfrn08Bq3E3fzlJw6gjE3tYU8dY9x9vC2rK9pd9NOp7Af11qCFDaWpohXkw==", | |
| 2810 | + "license": "MIT", | |
| 2811 | + "dependencies": { | |
| 2812 | + "@tiptap/core": "^3.20.0", | |
| 2813 | + "@tiptap/extension-blockquote": "^3.20.0", | |
| 2814 | + "@tiptap/extension-bold": "^3.20.0", | |
| 2815 | + "@tiptap/extension-bullet-list": "^3.20.0", | |
| 2816 | + "@tiptap/extension-code": "^3.20.0", | |
| 2817 | + "@tiptap/extension-code-block": "^3.20.0", | |
| 2818 | + "@tiptap/extension-document": "^3.20.0", | |
| 2819 | + "@tiptap/extension-dropcursor": "^3.20.0", | |
| 2820 | + "@tiptap/extension-gapcursor": "^3.20.0", | |
| 2821 | + "@tiptap/extension-hard-break": "^3.20.0", | |
| 2822 | + "@tiptap/extension-heading": "^3.20.0", | |
| 2823 | + "@tiptap/extension-horizontal-rule": "^3.20.0", | |
| 2824 | + "@tiptap/extension-italic": "^3.20.0", | |
| 2825 | + "@tiptap/extension-link": "^3.20.0", | |
| 2826 | + "@tiptap/extension-list": "^3.20.0", | |
| 2827 | + "@tiptap/extension-list-item": "^3.20.0", | |
| 2828 | + "@tiptap/extension-list-keymap": "^3.20.0", | |
| 2829 | + "@tiptap/extension-ordered-list": "^3.20.0", | |
| 2830 | + "@tiptap/extension-paragraph": "^3.20.0", | |
| 2831 | + "@tiptap/extension-strike": "^3.20.0", | |
| 2832 | + "@tiptap/extension-text": "^3.20.0", | |
| 2833 | + "@tiptap/extension-underline": "^3.20.0", | |
| 2834 | + "@tiptap/extensions": "^3.20.0", | |
| 2835 | + "@tiptap/pm": "^3.20.0" | |
| 2836 | + }, | |
| 2837 | + "funding": { | |
| 2838 | + "type": "github", | |
| 2839 | + "url": "https://github.com/sponsors/ueberdosis" | |
| 2840 | + } | |
| 2841 | + }, | |
| 2338 | 2842 | "node_modules/@tybys/wasm-util": { |
| 2339 | 2843 | "version": "0.10.1", |
| 2340 | 2844 | "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", |
@@ -2403,6 +2907,16 @@ | ||
| 2403 | 2907 | "@types/unist": "*" |
| 2404 | 2908 | } |
| 2405 | 2909 | }, |
| 2910 | + "node_modules/@types/html-docx-js": { | |
| 2911 | + "version": "0.3.4", | |
| 2912 | + "resolved": "https://registry.npmjs.org/@types/html-docx-js/-/html-docx-js-0.3.4.tgz", | |
| 2913 | + "integrity": "sha512-+t/4SxNnEq+0xbSUG7LrOOfG/y12aTXBjYKtOYtigVRIIOCEmK7jh0pSUeW5ATvI934274hkzdBg57t5RDrZow==", | |
| 2914 | + "dev": true, | |
| 2915 | + "license": "MIT", | |
| 2916 | + "dependencies": { | |
| 2917 | + "@types/node": "*" | |
| 2918 | + } | |
| 2919 | + }, | |
| 2406 | 2920 | "node_modules/@types/json5": { |
| 2407 | 2921 | "version": "0.0.29", |
| 2408 | 2922 | "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", |
@@ -2410,6 +2924,22 @@ | ||
| 2410 | 2924 | "dev": true, |
| 2411 | 2925 | "license": "MIT" |
| 2412 | 2926 | }, |
| 2927 | + "node_modules/@types/linkify-it": { | |
| 2928 | + "version": "5.0.0", | |
| 2929 | + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", | |
| 2930 | + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", | |
| 2931 | + "license": "MIT" | |
| 2932 | + }, | |
| 2933 | + "node_modules/@types/markdown-it": { | |
| 2934 | + "version": "14.1.2", | |
| 2935 | + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", | |
| 2936 | + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", | |
| 2937 | + "license": "MIT", | |
| 2938 | + "dependencies": { | |
| 2939 | + "@types/linkify-it": "^5", | |
| 2940 | + "@types/mdurl": "^2" | |
| 2941 | + } | |
| 2942 | + }, | |
| 2413 | 2943 | "node_modules/@types/mdast": { |
| 2414 | 2944 | "version": "4.0.4", |
| 2415 | 2945 | "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", |
@@ -2419,6 +2949,12 @@ | ||
| 2419 | 2949 | "@types/unist": "*" |
| 2420 | 2950 | } |
| 2421 | 2951 | }, |
| 2952 | + "node_modules/@types/mdurl": { | |
| 2953 | + "version": "2.0.0", | |
| 2954 | + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", | |
| 2955 | + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", | |
| 2956 | + "license": "MIT" | |
| 2957 | + }, | |
| 2422 | 2958 | "node_modules/@types/mime-types": { |
| 2423 | 2959 | "version": "3.0.1", |
| 2424 | 2960 | "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz", |
@@ -2488,7 +3024,6 @@ | ||
| 2488 | 3024 | "version": "18.3.7", |
| 2489 | 3025 | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", |
| 2490 | 3026 | "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", |
| 2491 | − "dev": true, | |
| 2492 | 3027 | "license": "MIT", |
| 2493 | 3028 | "peerDependencies": { |
| 2494 | 3029 | "@types/react": "^18.0.0" |
@@ -2530,6 +3065,12 @@ | ||
| 2530 | 3065 | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| 2531 | 3066 | "license": "MIT" |
| 2532 | 3067 | }, |
| 3068 | + "node_modules/@types/use-sync-external-store": { | |
| 3069 | + "version": "0.0.6", | |
| 3070 | + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", | |
| 3071 | + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", | |
| 3072 | + "license": "MIT" | |
| 3073 | + }, | |
| 2533 | 3074 | "node_modules/@types/uuid": { |
| 2534 | 3075 | "version": "10.0.0", |
| 2535 | 3076 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", |
@@ -3125,6 +3666,15 @@ | ||
| 3125 | 3666 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" |
| 3126 | 3667 | } |
| 3127 | 3668 | }, |
| 3669 | + "node_modules/adler-32": { | |
| 3670 | + "version": "1.3.1", | |
| 3671 | + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", | |
| 3672 | + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", | |
| 3673 | + "license": "Apache-2.0", | |
| 3674 | + "engines": { | |
| 3675 | + "node": ">=0.8" | |
| 3676 | + } | |
| 3677 | + }, | |
| 3128 | 3678 | "node_modules/ajv": { |
| 3129 | 3679 | "version": "6.12.6", |
| 3130 | 3680 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", |
@@ -3324,7 +3874,6 @@ | ||
| 3324 | 3874 | "version": "2.0.1", |
| 3325 | 3875 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", |
| 3326 | 3876 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", |
| 3327 | − "dev": true, | |
| 3328 | 3877 | "license": "Python-2.0" |
| 3329 | 3878 | }, |
| 3330 | 3879 | "node_modules/aria-query": { |
@@ -3820,6 +4369,19 @@ | ||
| 3820 | 4369 | "url": "https://github.com/sponsors/wooorm" |
| 3821 | 4370 | } |
| 3822 | 4371 | }, |
| 4372 | + "node_modules/cfb": { | |
| 4373 | + "version": "1.2.2", | |
| 4374 | + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", | |
| 4375 | + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", | |
| 4376 | + "license": "Apache-2.0", | |
| 4377 | + "dependencies": { | |
| 4378 | + "adler-32": "~1.3.0", | |
| 4379 | + "crc-32": "~1.2.0" | |
| 4380 | + }, | |
| 4381 | + "engines": { | |
| 4382 | + "node": ">=0.8" | |
| 4383 | + } | |
| 4384 | + }, | |
| 3823 | 4385 | "node_modules/chalk": { |
| 3824 | 4386 | "version": "4.1.2", |
| 3825 | 4387 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", |
@@ -3966,6 +4528,15 @@ | ||
| 3966 | 4528 | "node": ">=8" |
| 3967 | 4529 | } |
| 3968 | 4530 | }, |
| 4531 | + "node_modules/codepage": { | |
| 4532 | + "version": "1.15.0", | |
| 4533 | + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", | |
| 4534 | + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", | |
| 4535 | + "license": "Apache-2.0", | |
| 4536 | + "engines": { | |
| 4537 | + "node": ">=0.8" | |
| 4538 | + } | |
| 4539 | + }, | |
| 3969 | 4540 | "node_modules/color-convert": { |
| 3970 | 4541 | "version": "2.0.1", |
| 3971 | 4542 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", |
@@ -4454,6 +5025,18 @@ | ||
| 4454 | 5025 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", |
| 4455 | 5026 | "license": "MIT" |
| 4456 | 5027 | }, |
| 5028 | + "node_modules/entities": { | |
| 5029 | + "version": "4.5.0", | |
| 5030 | + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", | |
| 5031 | + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", | |
| 5032 | + "license": "BSD-2-Clause", | |
| 5033 | + "engines": { | |
| 5034 | + "node": ">=0.12" | |
| 5035 | + }, | |
| 5036 | + "funding": { | |
| 5037 | + "url": "https://github.com/fb55/entities?sponsor=1" | |
| 5038 | + } | |
| 5039 | + }, | |
| 4457 | 5040 | "node_modules/es-abstract": { |
| 4458 | 5041 | "version": "1.24.1", |
| 4459 | 5042 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", |
@@ -4677,7 +5260,6 @@ | ||
| 4677 | 5260 | "version": "4.0.0", |
| 4678 | 5261 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", |
| 4679 | 5262 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", |
| 4680 | − "dev": true, | |
| 4681 | 5263 | "license": "MIT", |
| 4682 | 5264 | "engines": { |
| 4683 | 5265 | "node": ">=10" |
@@ -5184,6 +5766,15 @@ | ||
| 5184 | 5766 | "dev": true, |
| 5185 | 5767 | "license": "MIT" |
| 5186 | 5768 | }, |
| 5769 | + "node_modules/fast-equals": { | |
| 5770 | + "version": "5.4.0", | |
| 5771 | + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", | |
| 5772 | + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", | |
| 5773 | + "license": "MIT", | |
| 5774 | + "engines": { | |
| 5775 | + "node": ">=6.0.0" | |
| 5776 | + } | |
| 5777 | + }, | |
| 5187 | 5778 | "node_modules/fast-fifo": { |
| 5188 | 5779 | "version": "1.3.2", |
| 5189 | 5780 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", |
@@ -5374,6 +5965,15 @@ | ||
| 5374 | 5965 | "node": ">=0.4.x" |
| 5375 | 5966 | } |
| 5376 | 5967 | }, |
| 5968 | + "node_modules/frac": { | |
| 5969 | + "version": "1.1.2", | |
| 5970 | + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", | |
| 5971 | + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", | |
| 5972 | + "license": "Apache-2.0", | |
| 5973 | + "engines": { | |
| 5974 | + "node": ">=0.8" | |
| 5975 | + } | |
| 5976 | + }, | |
| 5377 | 5977 | "node_modules/framer-motion": { |
| 5378 | 5978 | "version": "12.34.0", |
| 5379 | 5979 | "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.0.tgz", |
@@ -5859,6 +6459,45 @@ | ||
| 5859 | 6459 | "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", |
| 5860 | 6460 | "license": "CC0-1.0" |
| 5861 | 6461 | }, |
| 6462 | + "node_modules/html-docx-js": { | |
| 6463 | + "version": "0.3.1", | |
| 6464 | + "resolved": "https://registry.npmjs.org/html-docx-js/-/html-docx-js-0.3.1.tgz", | |
| 6465 | + "integrity": "sha512-QSrMiRhxesqxYCa3f+2Z3ttIHPzSjDOL1tCOmIDIEET7HdabxXND6tAbsFMXAgRG4RADQ3wbl74ydMmjidaDPA==", | |
| 6466 | + "license": "MIT", | |
| 6467 | + "dependencies": { | |
| 6468 | + "jszip": "^2.3.0", | |
| 6469 | + "lodash.escape": "^3.0.0", | |
| 6470 | + "lodash.merge": "^3.2.0" | |
| 6471 | + } | |
| 6472 | + }, | |
| 6473 | + "node_modules/html-docx-js/node_modules/jszip": { | |
| 6474 | + "version": "2.7.0", | |
| 6475 | + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.7.0.tgz", | |
| 6476 | + "integrity": "sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==", | |
| 6477 | + "license": "(MIT OR GPL-3.0)", | |
| 6478 | + "dependencies": { | |
| 6479 | + "pako": "~1.0.2" | |
| 6480 | + } | |
| 6481 | + }, | |
| 6482 | + "node_modules/html-docx-js/node_modules/lodash.merge": { | |
| 6483 | + "version": "3.3.2", | |
| 6484 | + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-3.3.2.tgz", | |
| 6485 | + "integrity": "sha512-ZgGZpRhWLjivGUbjtApZR4HyLv/UAyoYqESVYkK4aLBJVHRrbFpG+GNnE9JPijliME4LkKM0SFI/WyOiBiv1+w==", | |
| 6486 | + "license": "MIT", | |
| 6487 | + "dependencies": { | |
| 6488 | + "lodash._arraycopy": "^3.0.0", | |
| 6489 | + "lodash._arrayeach": "^3.0.0", | |
| 6490 | + "lodash._createassigner": "^3.0.0", | |
| 6491 | + "lodash._getnative": "^3.0.0", | |
| 6492 | + "lodash.isarguments": "^3.0.0", | |
| 6493 | + "lodash.isarray": "^3.0.0", | |
| 6494 | + "lodash.isplainobject": "^3.0.0", | |
| 6495 | + "lodash.istypedarray": "^3.0.0", | |
| 6496 | + "lodash.keys": "^3.0.0", | |
| 6497 | + "lodash.keysin": "^3.0.0", | |
| 6498 | + "lodash.toplainobject": "^3.0.0" | |
| 6499 | + } | |
| 6500 | + }, | |
| 5862 | 6501 | "node_modules/html-url-attributes": { |
| 5863 | 6502 | "version": "3.0.1", |
| 5864 | 6503 | "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", |
@@ -6735,6 +7374,21 @@ | ||
| 6735 | 7374 | "dev": true, |
| 6736 | 7375 | "license": "MIT" |
| 6737 | 7376 | }, |
| 7377 | + "node_modules/linkify-it": { | |
| 7378 | + "version": "5.0.0", | |
| 7379 | + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", | |
| 7380 | + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", | |
| 7381 | + "license": "MIT", | |
| 7382 | + "dependencies": { | |
| 7383 | + "uc.micro": "^2.0.0" | |
| 7384 | + } | |
| 7385 | + }, | |
| 7386 | + "node_modules/linkifyjs": { | |
| 7387 | + "version": "4.3.2", | |
| 7388 | + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", | |
| 7389 | + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", | |
| 7390 | + "license": "MIT" | |
| 7391 | + }, | |
| 6738 | 7392 | "node_modules/locate-path": { |
| 6739 | 7393 | "version": "6.0.0", |
| 6740 | 7394 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", |
@@ -6757,6 +7411,124 @@ | ||
| 6757 | 7411 | "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", |
| 6758 | 7412 | "license": "MIT" |
| 6759 | 7413 | }, |
| 7414 | + "node_modules/lodash._arraycopy": { | |
| 7415 | + "version": "3.0.0", | |
| 7416 | + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", | |
| 7417 | + "integrity": "sha512-RHShTDnPKP7aWxlvXKiDT6IX2jCs6YZLCtNhOru/OX2Q/tzX295vVBK5oX1ECtN+2r86S0Ogy8ykP1sgCZAN0A==", | |
| 7418 | + "license": "MIT" | |
| 7419 | + }, | |
| 7420 | + "node_modules/lodash._arrayeach": { | |
| 7421 | + "version": "3.0.0", | |
| 7422 | + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", | |
| 7423 | + "integrity": "sha512-Mn7HidOVcl3mkQtbPsuKR0Fj0N6Q6DQB77CtYncZcJc0bx5qv2q4Gl6a0LC1AN+GSxpnBDNnK3CKEm9XNA4zqQ==", | |
| 7424 | + "license": "MIT" | |
| 7425 | + }, | |
| 7426 | + "node_modules/lodash._basecopy": { | |
| 7427 | + "version": "3.0.1", | |
| 7428 | + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", | |
| 7429 | + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==", | |
| 7430 | + "license": "MIT" | |
| 7431 | + }, | |
| 7432 | + "node_modules/lodash._basefor": { | |
| 7433 | + "version": "3.0.3", | |
| 7434 | + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", | |
| 7435 | + "integrity": "sha512-6bc3b8grkpMgDcVJv9JYZAk/mHgcqMljzm7OsbmcE2FGUMmmLQTPHlh/dFqR8LA0GQ7z4K67JSotVKu5058v1A==", | |
| 7436 | + "license": "MIT" | |
| 7437 | + }, | |
| 7438 | + "node_modules/lodash._bindcallback": { | |
| 7439 | + "version": "3.0.1", | |
| 7440 | + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", | |
| 7441 | + "integrity": "sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ==", | |
| 7442 | + "license": "MIT" | |
| 7443 | + }, | |
| 7444 | + "node_modules/lodash._createassigner": { | |
| 7445 | + "version": "3.1.1", | |
| 7446 | + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", | |
| 7447 | + "integrity": "sha512-LziVL7IDnJjQeeV95Wvhw6G28Z8Q6da87LWKOPWmzBLv4u6FAT/x5v00pyGW0u38UoogNF2JnD3bGgZZDaNEBw==", | |
| 7448 | + "license": "MIT", | |
| 7449 | + "dependencies": { | |
| 7450 | + "lodash._bindcallback": "^3.0.0", | |
| 7451 | + "lodash._isiterateecall": "^3.0.0", | |
| 7452 | + "lodash.restparam": "^3.0.0" | |
| 7453 | + } | |
| 7454 | + }, | |
| 7455 | + "node_modules/lodash._getnative": { | |
| 7456 | + "version": "3.9.1", | |
| 7457 | + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", | |
| 7458 | + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==", | |
| 7459 | + "license": "MIT" | |
| 7460 | + }, | |
| 7461 | + "node_modules/lodash._isiterateecall": { | |
| 7462 | + "version": "3.0.9", | |
| 7463 | + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", | |
| 7464 | + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==", | |
| 7465 | + "license": "MIT" | |
| 7466 | + }, | |
| 7467 | + "node_modules/lodash._root": { | |
| 7468 | + "version": "3.0.1", | |
| 7469 | + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", | |
| 7470 | + "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==", | |
| 7471 | + "license": "MIT" | |
| 7472 | + }, | |
| 7473 | + "node_modules/lodash.escape": { | |
| 7474 | + "version": "3.2.0", | |
| 7475 | + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", | |
| 7476 | + "integrity": "sha512-n1PZMXgaaDWZDSvuNZ/8XOcYO2hOKDqZel5adtR30VKQAtoWs/5AOeFA0vPV8moiPzlqe7F4cP2tzpFewQyelQ==", | |
| 7477 | + "license": "MIT", | |
| 7478 | + "dependencies": { | |
| 7479 | + "lodash._root": "^3.0.0" | |
| 7480 | + } | |
| 7481 | + }, | |
| 7482 | + "node_modules/lodash.isarguments": { | |
| 7483 | + "version": "3.1.0", | |
| 7484 | + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", | |
| 7485 | + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", | |
| 7486 | + "license": "MIT" | |
| 7487 | + }, | |
| 7488 | + "node_modules/lodash.isarray": { | |
| 7489 | + "version": "3.0.4", | |
| 7490 | + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", | |
| 7491 | + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", | |
| 7492 | + "license": "MIT" | |
| 7493 | + }, | |
| 7494 | + "node_modules/lodash.isplainobject": { | |
| 7495 | + "version": "3.2.0", | |
| 7496 | + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz", | |
| 7497 | + "integrity": "sha512-P4wZnho5curNqeEq/x292Pb57e1v+woR7DJ84DURelKB46lby8aDEGVobSaYtzHdQBWQrJSdxcCwjlGOvvdIyg==", | |
| 7498 | + "license": "MIT", | |
| 7499 | + "dependencies": { | |
| 7500 | + "lodash._basefor": "^3.0.0", | |
| 7501 | + "lodash.isarguments": "^3.0.0", | |
| 7502 | + "lodash.keysin": "^3.0.0" | |
| 7503 | + } | |
| 7504 | + }, | |
| 7505 | + "node_modules/lodash.istypedarray": { | |
| 7506 | + "version": "3.0.6", | |
| 7507 | + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", | |
| 7508 | + "integrity": "sha512-lGWJ6N8AA3KSv+ZZxlTdn4f6A7kMfpJboeyvbFdE7IU9YAgweODqmOgdUHOA+c6lVWeVLysdaxciFXi+foVsWw==", | |
| 7509 | + "license": "MIT" | |
| 7510 | + }, | |
| 7511 | + "node_modules/lodash.keys": { | |
| 7512 | + "version": "3.1.2", | |
| 7513 | + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", | |
| 7514 | + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", | |
| 7515 | + "license": "MIT", | |
| 7516 | + "dependencies": { | |
| 7517 | + "lodash._getnative": "^3.0.0", | |
| 7518 | + "lodash.isarguments": "^3.0.0", | |
| 7519 | + "lodash.isarray": "^3.0.0" | |
| 7520 | + } | |
| 7521 | + }, | |
| 7522 | + "node_modules/lodash.keysin": { | |
| 7523 | + "version": "3.0.8", | |
| 7524 | + "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz", | |
| 7525 | + "integrity": "sha512-YDB/5xkL3fBKFMDaC+cfGV00pbiJ6XoJIfRmBhv7aR6wWtbCW6IzkiWnTfkiHTF6ALD7ff83dAtB3OEaSoyQPg==", | |
| 7526 | + "license": "MIT", | |
| 7527 | + "dependencies": { | |
| 7528 | + "lodash.isarguments": "^3.0.0", | |
| 7529 | + "lodash.isarray": "^3.0.0" | |
| 7530 | + } | |
| 7531 | + }, | |
| 6760 | 7532 | "node_modules/lodash.merge": { |
| 6761 | 7533 | "version": "4.6.2", |
| 6762 | 7534 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", |
@@ -6764,6 +7536,22 @@ | ||
| 6764 | 7536 | "dev": true, |
| 6765 | 7537 | "license": "MIT" |
| 6766 | 7538 | }, |
| 7539 | + "node_modules/lodash.restparam": { | |
| 7540 | + "version": "3.6.1", | |
| 7541 | + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", | |
| 7542 | + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==", | |
| 7543 | + "license": "MIT" | |
| 7544 | + }, | |
| 7545 | + "node_modules/lodash.toplainobject": { | |
| 7546 | + "version": "3.0.0", | |
| 7547 | + "resolved": "https://registry.npmjs.org/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz", | |
| 7548 | + "integrity": "sha512-wMI0Ju1bvSmnBS3EcRRH/3zDnZOPpDtMtNDzbbNMKuTrEpALsf+sPyMeogmv63Y11qZQO7H1xFzohIEGRMjPYA==", | |
| 7549 | + "license": "MIT", | |
| 7550 | + "dependencies": { | |
| 7551 | + "lodash._basecopy": "^3.0.0", | |
| 7552 | + "lodash.keysin": "^3.0.0" | |
| 7553 | + } | |
| 7554 | + }, | |
| 6767 | 7555 | "node_modules/longest-streak": { |
| 6768 | 7556 | "version": "3.1.0", |
| 6769 | 7557 | "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", |
@@ -6859,6 +7647,33 @@ | ||
| 6859 | 7647 | "sprintf-js": "~1.0.2" |
| 6860 | 7648 | } |
| 6861 | 7649 | }, |
| 7650 | + "node_modules/markdown-it": { | |
| 7651 | + "version": "14.1.1", | |
| 7652 | + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", | |
| 7653 | + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", | |
| 7654 | + "license": "MIT", | |
| 7655 | + "dependencies": { | |
| 7656 | + "argparse": "^2.0.1", | |
| 7657 | + "entities": "^4.4.0", | |
| 7658 | + "linkify-it": "^5.0.0", | |
| 7659 | + "mdurl": "^2.0.0", | |
| 7660 | + "punycode.js": "^2.3.1", | |
| 7661 | + "uc.micro": "^2.1.0" | |
| 7662 | + }, | |
| 7663 | + "bin": { | |
| 7664 | + "markdown-it": "bin/markdown-it.mjs" | |
| 7665 | + } | |
| 7666 | + }, | |
| 7667 | + "node_modules/markdown-table": { | |
| 7668 | + "version": "3.0.4", | |
| 7669 | + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", | |
| 7670 | + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", | |
| 7671 | + "license": "MIT", | |
| 7672 | + "funding": { | |
| 7673 | + "type": "github", | |
| 7674 | + "url": "https://github.com/sponsors/wooorm" | |
| 7675 | + } | |
| 7676 | + }, | |
| 6862 | 7677 | "node_modules/math-intrinsics": { |
| 6863 | 7678 | "version": "1.1.0", |
| 6864 | 7679 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", |
@@ -6869,6 +7684,34 @@ | ||
| 6869 | 7684 | "node": ">= 0.4" |
| 6870 | 7685 | } |
| 6871 | 7686 | }, |
| 7687 | + "node_modules/mdast-util-find-and-replace": { | |
| 7688 | + "version": "3.0.2", | |
| 7689 | + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", | |
| 7690 | + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", | |
| 7691 | + "license": "MIT", | |
| 7692 | + "dependencies": { | |
| 7693 | + "@types/mdast": "^4.0.0", | |
| 7694 | + "escape-string-regexp": "^5.0.0", | |
| 7695 | + "unist-util-is": "^6.0.0", | |
| 7696 | + "unist-util-visit-parents": "^6.0.0" | |
| 7697 | + }, | |
| 7698 | + "funding": { | |
| 7699 | + "type": "opencollective", | |
| 7700 | + "url": "https://opencollective.com/unified" | |
| 7701 | + } | |
| 7702 | + }, | |
| 7703 | + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { | |
| 7704 | + "version": "5.0.0", | |
| 7705 | + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", | |
| 7706 | + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", | |
| 7707 | + "license": "MIT", | |
| 7708 | + "engines": { | |
| 7709 | + "node": ">=12" | |
| 7710 | + }, | |
| 7711 | + "funding": { | |
| 7712 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 7713 | + } | |
| 7714 | + }, | |
| 6872 | 7715 | "node_modules/mdast-util-from-markdown": { |
| 6873 | 7716 | "version": "2.0.2", |
| 6874 | 7717 | "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", |
@@ -6893,6 +7736,107 @@ | ||
| 6893 | 7736 | "url": "https://opencollective.com/unified" |
| 6894 | 7737 | } |
| 6895 | 7738 | }, |
| 7739 | + "node_modules/mdast-util-gfm": { | |
| 7740 | + "version": "3.1.0", | |
| 7741 | + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", | |
| 7742 | + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", | |
| 7743 | + "license": "MIT", | |
| 7744 | + "dependencies": { | |
| 7745 | + "mdast-util-from-markdown": "^2.0.0", | |
| 7746 | + "mdast-util-gfm-autolink-literal": "^2.0.0", | |
| 7747 | + "mdast-util-gfm-footnote": "^2.0.0", | |
| 7748 | + "mdast-util-gfm-strikethrough": "^2.0.0", | |
| 7749 | + "mdast-util-gfm-table": "^2.0.0", | |
| 7750 | + "mdast-util-gfm-task-list-item": "^2.0.0", | |
| 7751 | + "mdast-util-to-markdown": "^2.0.0" | |
| 7752 | + }, | |
| 7753 | + "funding": { | |
| 7754 | + "type": "opencollective", | |
| 7755 | + "url": "https://opencollective.com/unified" | |
| 7756 | + } | |
| 7757 | + }, | |
| 7758 | + "node_modules/mdast-util-gfm-autolink-literal": { | |
| 7759 | + "version": "2.0.1", | |
| 7760 | + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", | |
| 7761 | + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", | |
| 7762 | + "license": "MIT", | |
| 7763 | + "dependencies": { | |
| 7764 | + "@types/mdast": "^4.0.0", | |
| 7765 | + "ccount": "^2.0.0", | |
| 7766 | + "devlop": "^1.0.0", | |
| 7767 | + "mdast-util-find-and-replace": "^3.0.0", | |
| 7768 | + "micromark-util-character": "^2.0.0" | |
| 7769 | + }, | |
| 7770 | + "funding": { | |
| 7771 | + "type": "opencollective", | |
| 7772 | + "url": "https://opencollective.com/unified" | |
| 7773 | + } | |
| 7774 | + }, | |
| 7775 | + "node_modules/mdast-util-gfm-footnote": { | |
| 7776 | + "version": "2.1.0", | |
| 7777 | + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", | |
| 7778 | + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", | |
| 7779 | + "license": "MIT", | |
| 7780 | + "dependencies": { | |
| 7781 | + "@types/mdast": "^4.0.0", | |
| 7782 | + "devlop": "^1.1.0", | |
| 7783 | + "mdast-util-from-markdown": "^2.0.0", | |
| 7784 | + "mdast-util-to-markdown": "^2.0.0", | |
| 7785 | + "micromark-util-normalize-identifier": "^2.0.0" | |
| 7786 | + }, | |
| 7787 | + "funding": { | |
| 7788 | + "type": "opencollective", | |
| 7789 | + "url": "https://opencollective.com/unified" | |
| 7790 | + } | |
| 7791 | + }, | |
| 7792 | + "node_modules/mdast-util-gfm-strikethrough": { | |
| 7793 | + "version": "2.0.0", | |
| 7794 | + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", | |
| 7795 | + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", | |
| 7796 | + "license": "MIT", | |
| 7797 | + "dependencies": { | |
| 7798 | + "@types/mdast": "^4.0.0", | |
| 7799 | + "mdast-util-from-markdown": "^2.0.0", | |
| 7800 | + "mdast-util-to-markdown": "^2.0.0" | |
| 7801 | + }, | |
| 7802 | + "funding": { | |
| 7803 | + "type": "opencollective", | |
| 7804 | + "url": "https://opencollective.com/unified" | |
| 7805 | + } | |
| 7806 | + }, | |
| 7807 | + "node_modules/mdast-util-gfm-table": { | |
| 7808 | + "version": "2.0.0", | |
| 7809 | + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", | |
| 7810 | + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", | |
| 7811 | + "license": "MIT", | |
| 7812 | + "dependencies": { | |
| 7813 | + "@types/mdast": "^4.0.0", | |
| 7814 | + "devlop": "^1.0.0", | |
| 7815 | + "markdown-table": "^3.0.0", | |
| 7816 | + "mdast-util-from-markdown": "^2.0.0", | |
| 7817 | + "mdast-util-to-markdown": "^2.0.0" | |
| 7818 | + }, | |
| 7819 | + "funding": { | |
| 7820 | + "type": "opencollective", | |
| 7821 | + "url": "https://opencollective.com/unified" | |
| 7822 | + } | |
| 7823 | + }, | |
| 7824 | + "node_modules/mdast-util-gfm-task-list-item": { | |
| 7825 | + "version": "2.0.0", | |
| 7826 | + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", | |
| 7827 | + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", | |
| 7828 | + "license": "MIT", | |
| 7829 | + "dependencies": { | |
| 7830 | + "@types/mdast": "^4.0.0", | |
| 7831 | + "devlop": "^1.0.0", | |
| 7832 | + "mdast-util-from-markdown": "^2.0.0", | |
| 7833 | + "mdast-util-to-markdown": "^2.0.0" | |
| 7834 | + }, | |
| 7835 | + "funding": { | |
| 7836 | + "type": "opencollective", | |
| 7837 | + "url": "https://opencollective.com/unified" | |
| 7838 | + } | |
| 7839 | + }, | |
| 6896 | 7840 | "node_modules/mdast-util-mdx-expression": { |
| 6897 | 7841 | "version": "2.0.1", |
| 6898 | 7842 | "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", |
@@ -7022,6 +7966,12 @@ | ||
| 7022 | 7966 | "url": "https://opencollective.com/unified" |
| 7023 | 7967 | } |
| 7024 | 7968 | }, |
| 7969 | + "node_modules/mdurl": { | |
| 7970 | + "version": "2.0.0", | |
| 7971 | + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", | |
| 7972 | + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", | |
| 7973 | + "license": "MIT" | |
| 7974 | + }, | |
| 7025 | 7975 | "node_modules/merge2": { |
| 7026 | 7976 | "version": "1.4.1", |
| 7027 | 7977 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", |
@@ -7101,6 +8051,127 @@ | ||
| 7101 | 8051 | "micromark-util-types": "^2.0.0" |
| 7102 | 8052 | } |
| 7103 | 8053 | }, |
| 8054 | + "node_modules/micromark-extension-gfm": { | |
| 8055 | + "version": "3.0.0", | |
| 8056 | + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", | |
| 8057 | + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", | |
| 8058 | + "license": "MIT", | |
| 8059 | + "dependencies": { | |
| 8060 | + "micromark-extension-gfm-autolink-literal": "^2.0.0", | |
| 8061 | + "micromark-extension-gfm-footnote": "^2.0.0", | |
| 8062 | + "micromark-extension-gfm-strikethrough": "^2.0.0", | |
| 8063 | + "micromark-extension-gfm-table": "^2.0.0", | |
| 8064 | + "micromark-extension-gfm-tagfilter": "^2.0.0", | |
| 8065 | + "micromark-extension-gfm-task-list-item": "^2.0.0", | |
| 8066 | + "micromark-util-combine-extensions": "^2.0.0", | |
| 8067 | + "micromark-util-types": "^2.0.0" | |
| 8068 | + }, | |
| 8069 | + "funding": { | |
| 8070 | + "type": "opencollective", | |
| 8071 | + "url": "https://opencollective.com/unified" | |
| 8072 | + } | |
| 8073 | + }, | |
| 8074 | + "node_modules/micromark-extension-gfm-autolink-literal": { | |
| 8075 | + "version": "2.1.0", | |
| 8076 | + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", | |
| 8077 | + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", | |
| 8078 | + "license": "MIT", | |
| 8079 | + "dependencies": { | |
| 8080 | + "micromark-util-character": "^2.0.0", | |
| 8081 | + "micromark-util-sanitize-uri": "^2.0.0", | |
| 8082 | + "micromark-util-symbol": "^2.0.0", | |
| 8083 | + "micromark-util-types": "^2.0.0" | |
| 8084 | + }, | |
| 8085 | + "funding": { | |
| 8086 | + "type": "opencollective", | |
| 8087 | + "url": "https://opencollective.com/unified" | |
| 8088 | + } | |
| 8089 | + }, | |
| 8090 | + "node_modules/micromark-extension-gfm-footnote": { | |
| 8091 | + "version": "2.1.0", | |
| 8092 | + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", | |
| 8093 | + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", | |
| 8094 | + "license": "MIT", | |
| 8095 | + "dependencies": { | |
| 8096 | + "devlop": "^1.0.0", | |
| 8097 | + "micromark-core-commonmark": "^2.0.0", | |
| 8098 | + "micromark-factory-space": "^2.0.0", | |
| 8099 | + "micromark-util-character": "^2.0.0", | |
| 8100 | + "micromark-util-normalize-identifier": "^2.0.0", | |
| 8101 | + "micromark-util-sanitize-uri": "^2.0.0", | |
| 8102 | + "micromark-util-symbol": "^2.0.0", | |
| 8103 | + "micromark-util-types": "^2.0.0" | |
| 8104 | + }, | |
| 8105 | + "funding": { | |
| 8106 | + "type": "opencollective", | |
| 8107 | + "url": "https://opencollective.com/unified" | |
| 8108 | + } | |
| 8109 | + }, | |
| 8110 | + "node_modules/micromark-extension-gfm-strikethrough": { | |
| 8111 | + "version": "2.1.0", | |
| 8112 | + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", | |
| 8113 | + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", | |
| 8114 | + "license": "MIT", | |
| 8115 | + "dependencies": { | |
| 8116 | + "devlop": "^1.0.0", | |
| 8117 | + "micromark-util-chunked": "^2.0.0", | |
| 8118 | + "micromark-util-classify-character": "^2.0.0", | |
| 8119 | + "micromark-util-resolve-all": "^2.0.0", | |
| 8120 | + "micromark-util-symbol": "^2.0.0", | |
| 8121 | + "micromark-util-types": "^2.0.0" | |
| 8122 | + }, | |
| 8123 | + "funding": { | |
| 8124 | + "type": "opencollective", | |
| 8125 | + "url": "https://opencollective.com/unified" | |
| 8126 | + } | |
| 8127 | + }, | |
| 8128 | + "node_modules/micromark-extension-gfm-table": { | |
| 8129 | + "version": "2.1.1", | |
| 8130 | + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", | |
| 8131 | + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", | |
| 8132 | + "license": "MIT", | |
| 8133 | + "dependencies": { | |
| 8134 | + "devlop": "^1.0.0", | |
| 8135 | + "micromark-factory-space": "^2.0.0", | |
| 8136 | + "micromark-util-character": "^2.0.0", | |
| 8137 | + "micromark-util-symbol": "^2.0.0", | |
| 8138 | + "micromark-util-types": "^2.0.0" | |
| 8139 | + }, | |
| 8140 | + "funding": { | |
| 8141 | + "type": "opencollective", | |
| 8142 | + "url": "https://opencollective.com/unified" | |
| 8143 | + } | |
| 8144 | + }, | |
| 8145 | + "node_modules/micromark-extension-gfm-tagfilter": { | |
| 8146 | + "version": "2.0.0", | |
| 8147 | + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", | |
| 8148 | + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", | |
| 8149 | + "license": "MIT", | |
| 8150 | + "dependencies": { | |
| 8151 | + "micromark-util-types": "^2.0.0" | |
| 8152 | + }, | |
| 8153 | + "funding": { | |
| 8154 | + "type": "opencollective", | |
| 8155 | + "url": "https://opencollective.com/unified" | |
| 8156 | + } | |
| 8157 | + }, | |
| 8158 | + "node_modules/micromark-extension-gfm-task-list-item": { | |
| 8159 | + "version": "2.1.0", | |
| 8160 | + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", | |
| 8161 | + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", | |
| 8162 | + "license": "MIT", | |
| 8163 | + "dependencies": { | |
| 8164 | + "devlop": "^1.0.0", | |
| 8165 | + "micromark-factory-space": "^2.0.0", | |
| 8166 | + "micromark-util-character": "^2.0.0", | |
| 8167 | + "micromark-util-symbol": "^2.0.0", | |
| 8168 | + "micromark-util-types": "^2.0.0" | |
| 8169 | + }, | |
| 8170 | + "funding": { | |
| 8171 | + "type": "opencollective", | |
| 8172 | + "url": "https://opencollective.com/unified" | |
| 8173 | + } | |
| 8174 | + }, | |
| 7104 | 8175 | "node_modules/micromark-factory-destination": { |
| 7105 | 8176 | "version": "2.0.1", |
| 7106 | 8177 | "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", |
@@ -7879,6 +8950,12 @@ | ||
| 7879 | 8950 | "node": ">= 0.8.0" |
| 7880 | 8951 | } |
| 7881 | 8952 | }, |
| 8953 | + "node_modules/orderedmap": { | |
| 8954 | + "version": "2.1.1", | |
| 8955 | + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", | |
| 8956 | + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", | |
| 8957 | + "license": "MIT" | |
| 8958 | + }, | |
| 7882 | 8959 | "node_modules/otplib": { |
| 7883 | 8960 | "version": "13.3.0", |
| 7884 | 8961 | "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.3.0.tgz", |
@@ -8378,6 +9455,201 @@ | ||
| 8378 | 9455 | "url": "https://github.com/sponsors/wooorm" |
| 8379 | 9456 | } |
| 8380 | 9457 | }, |
| 9458 | + "node_modules/prosemirror-changeset": { | |
| 9459 | + "version": "2.4.0", | |
| 9460 | + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", | |
| 9461 | + "integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==", | |
| 9462 | + "license": "MIT", | |
| 9463 | + "dependencies": { | |
| 9464 | + "prosemirror-transform": "^1.0.0" | |
| 9465 | + } | |
| 9466 | + }, | |
| 9467 | + "node_modules/prosemirror-collab": { | |
| 9468 | + "version": "1.3.1", | |
| 9469 | + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", | |
| 9470 | + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", | |
| 9471 | + "license": "MIT", | |
| 9472 | + "dependencies": { | |
| 9473 | + "prosemirror-state": "^1.0.0" | |
| 9474 | + } | |
| 9475 | + }, | |
| 9476 | + "node_modules/prosemirror-commands": { | |
| 9477 | + "version": "1.7.1", | |
| 9478 | + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", | |
| 9479 | + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", | |
| 9480 | + "license": "MIT", | |
| 9481 | + "dependencies": { | |
| 9482 | + "prosemirror-model": "^1.0.0", | |
| 9483 | + "prosemirror-state": "^1.0.0", | |
| 9484 | + "prosemirror-transform": "^1.10.2" | |
| 9485 | + } | |
| 9486 | + }, | |
| 9487 | + "node_modules/prosemirror-dropcursor": { | |
| 9488 | + "version": "1.8.2", | |
| 9489 | + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", | |
| 9490 | + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", | |
| 9491 | + "license": "MIT", | |
| 9492 | + "dependencies": { | |
| 9493 | + "prosemirror-state": "^1.0.0", | |
| 9494 | + "prosemirror-transform": "^1.1.0", | |
| 9495 | + "prosemirror-view": "^1.1.0" | |
| 9496 | + } | |
| 9497 | + }, | |
| 9498 | + "node_modules/prosemirror-gapcursor": { | |
| 9499 | + "version": "1.4.0", | |
| 9500 | + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz", | |
| 9501 | + "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==", | |
| 9502 | + "license": "MIT", | |
| 9503 | + "dependencies": { | |
| 9504 | + "prosemirror-keymap": "^1.0.0", | |
| 9505 | + "prosemirror-model": "^1.0.0", | |
| 9506 | + "prosemirror-state": "^1.0.0", | |
| 9507 | + "prosemirror-view": "^1.0.0" | |
| 9508 | + } | |
| 9509 | + }, | |
| 9510 | + "node_modules/prosemirror-history": { | |
| 9511 | + "version": "1.5.0", | |
| 9512 | + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", | |
| 9513 | + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", | |
| 9514 | + "license": "MIT", | |
| 9515 | + "dependencies": { | |
| 9516 | + "prosemirror-state": "^1.2.2", | |
| 9517 | + "prosemirror-transform": "^1.0.0", | |
| 9518 | + "prosemirror-view": "^1.31.0", | |
| 9519 | + "rope-sequence": "^1.3.0" | |
| 9520 | + } | |
| 9521 | + }, | |
| 9522 | + "node_modules/prosemirror-inputrules": { | |
| 9523 | + "version": "1.5.1", | |
| 9524 | + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", | |
| 9525 | + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", | |
| 9526 | + "license": "MIT", | |
| 9527 | + "dependencies": { | |
| 9528 | + "prosemirror-state": "^1.0.0", | |
| 9529 | + "prosemirror-transform": "^1.0.0" | |
| 9530 | + } | |
| 9531 | + }, | |
| 9532 | + "node_modules/prosemirror-keymap": { | |
| 9533 | + "version": "1.2.3", | |
| 9534 | + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", | |
| 9535 | + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", | |
| 9536 | + "license": "MIT", | |
| 9537 | + "dependencies": { | |
| 9538 | + "prosemirror-state": "^1.0.0", | |
| 9539 | + "w3c-keyname": "^2.2.0" | |
| 9540 | + } | |
| 9541 | + }, | |
| 9542 | + "node_modules/prosemirror-markdown": { | |
| 9543 | + "version": "1.13.4", | |
| 9544 | + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", | |
| 9545 | + "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", | |
| 9546 | + "license": "MIT", | |
| 9547 | + "dependencies": { | |
| 9548 | + "@types/markdown-it": "^14.0.0", | |
| 9549 | + "markdown-it": "^14.0.0", | |
| 9550 | + "prosemirror-model": "^1.25.0" | |
| 9551 | + } | |
| 9552 | + }, | |
| 9553 | + "node_modules/prosemirror-menu": { | |
| 9554 | + "version": "1.3.0", | |
| 9555 | + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz", | |
| 9556 | + "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==", | |
| 9557 | + "license": "MIT", | |
| 9558 | + "dependencies": { | |
| 9559 | + "crelt": "^1.0.0", | |
| 9560 | + "prosemirror-commands": "^1.0.0", | |
| 9561 | + "prosemirror-history": "^1.0.0", | |
| 9562 | + "prosemirror-state": "^1.0.0" | |
| 9563 | + } | |
| 9564 | + }, | |
| 9565 | + "node_modules/prosemirror-model": { | |
| 9566 | + "version": "1.25.4", | |
| 9567 | + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", | |
| 9568 | + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", | |
| 9569 | + "license": "MIT", | |
| 9570 | + "dependencies": { | |
| 9571 | + "orderedmap": "^2.0.0" | |
| 9572 | + } | |
| 9573 | + }, | |
| 9574 | + "node_modules/prosemirror-schema-basic": { | |
| 9575 | + "version": "1.2.4", | |
| 9576 | + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", | |
| 9577 | + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", | |
| 9578 | + "license": "MIT", | |
| 9579 | + "dependencies": { | |
| 9580 | + "prosemirror-model": "^1.25.0" | |
| 9581 | + } | |
| 9582 | + }, | |
| 9583 | + "node_modules/prosemirror-schema-list": { | |
| 9584 | + "version": "1.5.1", | |
| 9585 | + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", | |
| 9586 | + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", | |
| 9587 | + "license": "MIT", | |
| 9588 | + "dependencies": { | |
| 9589 | + "prosemirror-model": "^1.0.0", | |
| 9590 | + "prosemirror-state": "^1.0.0", | |
| 9591 | + "prosemirror-transform": "^1.7.3" | |
| 9592 | + } | |
| 9593 | + }, | |
| 9594 | + "node_modules/prosemirror-state": { | |
| 9595 | + "version": "1.4.4", | |
| 9596 | + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", | |
| 9597 | + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", | |
| 9598 | + "license": "MIT", | |
| 9599 | + "dependencies": { | |
| 9600 | + "prosemirror-model": "^1.0.0", | |
| 9601 | + "prosemirror-transform": "^1.0.0", | |
| 9602 | + "prosemirror-view": "^1.27.0" | |
| 9603 | + } | |
| 9604 | + }, | |
| 9605 | + "node_modules/prosemirror-tables": { | |
| 9606 | + "version": "1.8.5", | |
| 9607 | + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", | |
| 9608 | + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", | |
| 9609 | + "license": "MIT", | |
| 9610 | + "dependencies": { | |
| 9611 | + "prosemirror-keymap": "^1.2.3", | |
| 9612 | + "prosemirror-model": "^1.25.4", | |
| 9613 | + "prosemirror-state": "^1.4.4", | |
| 9614 | + "prosemirror-transform": "^1.10.5", | |
| 9615 | + "prosemirror-view": "^1.41.4" | |
| 9616 | + } | |
| 9617 | + }, | |
| 9618 | + "node_modules/prosemirror-trailing-node": { | |
| 9619 | + "version": "3.0.0", | |
| 9620 | + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", | |
| 9621 | + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", | |
| 9622 | + "license": "MIT", | |
| 9623 | + "dependencies": { | |
| 9624 | + "@remirror/core-constants": "3.0.0", | |
| 9625 | + "escape-string-regexp": "^4.0.0" | |
| 9626 | + }, | |
| 9627 | + "peerDependencies": { | |
| 9628 | + "prosemirror-model": "^1.22.1", | |
| 9629 | + "prosemirror-state": "^1.4.2", | |
| 9630 | + "prosemirror-view": "^1.33.8" | |
| 9631 | + } | |
| 9632 | + }, | |
| 9633 | + "node_modules/prosemirror-transform": { | |
| 9634 | + "version": "1.11.0", | |
| 9635 | + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz", | |
| 9636 | + "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==", | |
| 9637 | + "license": "MIT", | |
| 9638 | + "dependencies": { | |
| 9639 | + "prosemirror-model": "^1.21.0" | |
| 9640 | + } | |
| 9641 | + }, | |
| 9642 | + "node_modules/prosemirror-view": { | |
| 9643 | + "version": "1.41.6", | |
| 9644 | + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz", | |
| 9645 | + "integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==", | |
| 9646 | + "license": "MIT", | |
| 9647 | + "dependencies": { | |
| 9648 | + "prosemirror-model": "^1.20.0", | |
| 9649 | + "prosemirror-state": "^1.0.0", | |
| 9650 | + "prosemirror-transform": "^1.1.0" | |
| 9651 | + } | |
| 9652 | + }, | |
| 8381 | 9653 | "node_modules/punycode": { |
| 8382 | 9654 | "version": "2.3.1", |
| 8383 | 9655 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", |
@@ -8388,6 +9660,15 @@ | ||
| 8388 | 9660 | "node": ">=6" |
| 8389 | 9661 | } |
| 8390 | 9662 | }, |
| 9663 | + "node_modules/punycode.js": { | |
| 9664 | + "version": "2.3.1", | |
| 9665 | + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", | |
| 9666 | + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", | |
| 9667 | + "license": "MIT", | |
| 9668 | + "engines": { | |
| 9669 | + "node": ">=6" | |
| 9670 | + } | |
| 9671 | + }, | |
| 8391 | 9672 | "node_modules/qrcode": { |
| 8392 | 9673 | "version": "1.5.4", |
| 8393 | 9674 | "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", |
@@ -8678,6 +9959,24 @@ | ||
| 8678 | 9959 | "url": "https://github.com/sponsors/ljharb" |
| 8679 | 9960 | } |
| 8680 | 9961 | }, |
| 9962 | + "node_modules/remark-gfm": { | |
| 9963 | + "version": "4.0.1", | |
| 9964 | + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", | |
| 9965 | + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", | |
| 9966 | + "license": "MIT", | |
| 9967 | + "dependencies": { | |
| 9968 | + "@types/mdast": "^4.0.0", | |
| 9969 | + "mdast-util-gfm": "^3.0.0", | |
| 9970 | + "micromark-extension-gfm": "^3.0.0", | |
| 9971 | + "remark-parse": "^11.0.0", | |
| 9972 | + "remark-stringify": "^11.0.0", | |
| 9973 | + "unified": "^11.0.0" | |
| 9974 | + }, | |
| 9975 | + "funding": { | |
| 9976 | + "type": "opencollective", | |
| 9977 | + "url": "https://opencollective.com/unified" | |
| 9978 | + } | |
| 9979 | + }, | |
| 8681 | 9980 | "node_modules/remark-parse": { |
| 8682 | 9981 | "version": "11.0.0", |
| 8683 | 9982 | "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", |
@@ -8711,6 +10010,21 @@ | ||
| 8711 | 10010 | "url": "https://opencollective.com/unified" |
| 8712 | 10011 | } |
| 8713 | 10012 | }, |
| 10013 | + "node_modules/remark-stringify": { | |
| 10014 | + "version": "11.0.0", | |
| 10015 | + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", | |
| 10016 | + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", | |
| 10017 | + "license": "MIT", | |
| 10018 | + "dependencies": { | |
| 10019 | + "@types/mdast": "^4.0.0", | |
| 10020 | + "mdast-util-to-markdown": "^2.0.0", | |
| 10021 | + "unified": "^11.0.0" | |
| 10022 | + }, | |
| 10023 | + "funding": { | |
| 10024 | + "type": "opencollective", | |
| 10025 | + "url": "https://opencollective.com/unified" | |
| 10026 | + } | |
| 10027 | + }, | |
| 8714 | 10028 | "node_modules/require-directory": { |
| 8715 | 10029 | "version": "2.1.1", |
| 8716 | 10030 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", |
@@ -8817,6 +10131,12 @@ | ||
| 8817 | 10131 | "url": "https://github.com/sponsors/isaacs" |
| 8818 | 10132 | } |
| 8819 | 10133 | }, |
| 10134 | + "node_modules/rope-sequence": { | |
| 10135 | + "version": "1.3.4", | |
| 10136 | + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", | |
| 10137 | + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", | |
| 10138 | + "license": "MIT" | |
| 10139 | + }, | |
| 8820 | 10140 | "node_modules/run-parallel": { |
| 8821 | 10141 | "version": "1.2.0", |
| 8822 | 10142 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", |
@@ -9162,6 +10482,18 @@ | ||
| 9162 | 10482 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", |
| 9163 | 10483 | "license": "BSD-3-Clause" |
| 9164 | 10484 | }, |
| 10485 | + "node_modules/ssf": { | |
| 10486 | + "version": "0.11.2", | |
| 10487 | + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", | |
| 10488 | + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", | |
| 10489 | + "license": "Apache-2.0", | |
| 10490 | + "dependencies": { | |
| 10491 | + "frac": "~1.1.2" | |
| 10492 | + }, | |
| 10493 | + "engines": { | |
| 10494 | + "node": ">=0.8" | |
| 10495 | + } | |
| 10496 | + }, | |
| 9165 | 10497 | "node_modules/stable-hash": { |
| 9166 | 10498 | "version": "0.0.5", |
| 9167 | 10499 | "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", |
@@ -9927,6 +11259,12 @@ | ||
| 9927 | 11259 | "node": ">=14.17" |
| 9928 | 11260 | } |
| 9929 | 11261 | }, |
| 11262 | + "node_modules/uc.micro": { | |
| 11263 | + "version": "2.1.0", | |
| 11264 | + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", | |
| 11265 | + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", | |
| 11266 | + "license": "MIT" | |
| 11267 | + }, | |
| 9930 | 11268 | "node_modules/unbox-primitive": { |
| 9931 | 11269 | "version": "1.1.0", |
| 9932 | 11270 | "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", |
@@ -10097,6 +11435,15 @@ | ||
| 10097 | 11435 | "punycode": "^2.1.0" |
| 10098 | 11436 | } |
| 10099 | 11437 | }, |
| 11438 | + "node_modules/use-sync-external-store": { | |
| 11439 | + "version": "1.6.0", | |
| 11440 | + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", | |
| 11441 | + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", | |
| 11442 | + "license": "MIT", | |
| 11443 | + "peerDependencies": { | |
| 11444 | + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" | |
| 11445 | + } | |
| 11446 | + }, | |
| 10100 | 11447 | "node_modules/util-deprecate": { |
| 10101 | 11448 | "version": "1.0.2", |
| 10102 | 11449 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", |
@@ -10266,6 +11613,24 @@ | ||
| 10266 | 11613 | "url": "https://github.com/sponsors/ljharb" |
| 10267 | 11614 | } |
| 10268 | 11615 | }, |
| 11616 | + "node_modules/wmf": { | |
| 11617 | + "version": "1.0.2", | |
| 11618 | + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", | |
| 11619 | + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", | |
| 11620 | + "license": "Apache-2.0", | |
| 11621 | + "engines": { | |
| 11622 | + "node": ">=0.8" | |
| 11623 | + } | |
| 11624 | + }, | |
| 11625 | + "node_modules/word": { | |
| 11626 | + "version": "0.3.0", | |
| 11627 | + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", | |
| 11628 | + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", | |
| 11629 | + "license": "Apache-2.0", | |
| 11630 | + "engines": { | |
| 11631 | + "node": ">=0.8" | |
| 11632 | + } | |
| 11633 | + }, | |
| 10269 | 11634 | "node_modules/word-wrap": { |
| 10270 | 11635 | "version": "1.2.5", |
| 10271 | 11636 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", |
@@ -10377,6 +11742,27 @@ | ||
| 10377 | 11742 | "dev": true, |
| 10378 | 11743 | "license": "ISC" |
| 10379 | 11744 | }, |
| 11745 | + "node_modules/xlsx": { | |
| 11746 | + "version": "0.18.5", | |
| 11747 | + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", | |
| 11748 | + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", | |
| 11749 | + "license": "Apache-2.0", | |
| 11750 | + "dependencies": { | |
| 11751 | + "adler-32": "~1.3.0", | |
| 11752 | + "cfb": "~1.2.1", | |
| 11753 | + "codepage": "~1.15.0", | |
| 11754 | + "crc-32": "~1.2.1", | |
| 11755 | + "ssf": "~0.11.2", | |
| 11756 | + "wmf": "~1.0.1", | |
| 11757 | + "word": "~0.3.0" | |
| 11758 | + }, | |
| 11759 | + "bin": { | |
| 11760 | + "xlsx": "bin/xlsx.njs" | |
| 11761 | + }, | |
| 11762 | + "engines": { | |
| 11763 | + "node": ">=0.8" | |
| 11764 | + } | |
| 11765 | + }, | |
| 10380 | 11766 | "node_modules/xmlbuilder": { |
| 10381 | 11767 | "version": "10.1.1", |
| 10382 | 11768 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", |
modified
package.json
+13 −3
@@ -3,9 +3,9 @@ | ||
| 3 | 3 | "version": "1.0.0", |
| 4 | 4 | "private": true, |
| 5 | 5 | "scripts": { |
| 6 | − "dev": "next dev", | |
| 6 | + "dev": "tsx server.ts", | |
| 7 | 7 | "build": "next build", |
| 8 | − "start": "next start", | |
| 8 | + "start": "NODE_ENV=production tsx server.ts", | |
| 9 | 9 | "lint": "next lint", |
| 10 | 10 | "db:generate": "prisma generate", |
| 11 | 11 | "db:push": "prisma db push", |
@@ -40,10 +40,17 @@ | ||
| 40 | 40 | "@codemirror/theme-one-dark": "^6.1.3", |
| 41 | 41 | "@codemirror/view": "^6.39.14", |
| 42 | 42 | "@prisma/client": "^5.22.0", |
| 43 | + "@tiptap/extension-highlight": "^3.20.0", | |
| 44 | + "@tiptap/extension-text-align": "^3.20.0", | |
| 45 | + "@tiptap/extension-underline": "^3.20.0", | |
| 46 | + "@tiptap/pm": "^3.20.0", | |
| 47 | + "@tiptap/react": "^3.20.0", | |
| 48 | + "@tiptap/starter-kit": "^3.20.0", | |
| 43 | 49 | "archiver": "^7.0.1", |
| 44 | 50 | "bcryptjs": "^3.0.3", |
| 45 | 51 | "diff": "^8.0.3", |
| 46 | 52 | "framer-motion": "^12.34.0", |
| 53 | + "html-docx-js": "^0.3.1", | |
| 47 | 54 | "iron-session": "^8.0.4", |
| 48 | 55 | "jszip": "^3.10.1", |
| 49 | 56 | "lucide-react": "^0.564.0", |
@@ -63,14 +70,17 @@ | ||
| 63 | 70 | "react-hot-toast": "^2.6.0", |
| 64 | 71 | "react-markdown": "^10.1.0", |
| 65 | 72 | "react-syntax-highlighter": "^16.1.0", |
| 73 | + "remark-gfm": "^4.0.1", | |
| 66 | 74 | "sharp": "^0.34.5", |
| 67 | 75 | "uuid": "^13.0.0", |
| 68 | − "wavesurfer.js": "^7.12.1" | |
| 76 | + "wavesurfer.js": "^7.12.1", | |
| 77 | + "xlsx": "^0.18.5" | |
| 69 | 78 | }, |
| 70 | 79 | "devDependencies": { |
| 71 | 80 | "@types/archiver": "^7.0.0", |
| 72 | 81 | "@types/bcryptjs": "^2.4.6", |
| 73 | 82 | "@types/diff": "^7.0.2", |
| 83 | + "@types/html-docx-js": "^0.3.4", | |
| 74 | 84 | "@types/mime-types": "^3.0.1", |
| 75 | 85 | "@types/node": "^20", |
| 76 | 86 | "@types/papaparse": "^5.5.2", |
added
types/html-docx-js.d.ts
+15 −0
@@ -0,0 +1,15 @@ | ||
| 1 | +declare module "html-docx-js/dist/html-docx" { | |
| 2 | + interface HtmlDocxOptions { | |
| 3 | + orientation?: "landscape" | "portrait"; | |
| 4 | + margins?: { | |
| 5 | + top?: number; | |
| 6 | + bottom?: number; | |
| 7 | + left?: number; | |
| 8 | + right?: number; | |
| 9 | + header?: number; | |
| 10 | + footer?: number; | |
| 11 | + gutter?: number; | |
| 12 | + }; | |
| 13 | + } | |
| 14 | + export function asBlob(html: string, options?: HtmlDocxOptions): Blob; | |
| 15 | +} | |
| 16 | ||