diff --git a/package.json b/package.json index 11e1908..bce8ceb 100644 --- a/package.json +++ b/package.json @@ -91,44 +91,37 @@ ] }, "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@monaco-editor/react": "^4.7.0", "@unified-latex/unified-latex-util-parse": "^1.8.3", "@unified-latex/unified-latex-util-to-string": "^1.8.3", "adm-zip": "^0.5.16", "axios": "^1.12.2", "cors": "^2.8.5", "dotenv": "^17.2.3", - "driver.js": "^1.4.0", "express": "^5.1.0", "fs-extra": "^11.3.2", "multer": "^2.0.2", "nodemon": "^3.1.10", - "pdf-parse": "^2.4.5", - "pdfjs-dist": "^5.4.296", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-icons": "^5.5.0", - "react-pdf": "^10.3.0", - "react-quill-new": "^3.6.0", - "react-router-dom": "^7.9.2", "sharp": "^0.34.4", "slate": "^0.124.0", "slate-react": "^0.124.0", "tar": "^7.5.11", "uuid": "^13.0.0", - "y-indexeddb": "^9.0.12", - "y-monaco": "^0.1.6", - "y-websocket": "^3.0.0", - "yjs": "^13.6.29" + "pdf-parse": "^2.4.5" }, "devDependencies": { "@eslint/js": "^9.25.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.4.1", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^7.9.2", + "y-indexeddb": "^9.0.12", + "y-websocket": "^3.0.0", + "yjs": "^13.6.29", "autoprefixer": "^10.4.16", "concurrently": "^9.2.1", "cross-env": "^10.1.0", @@ -142,6 +135,13 @@ "tailwindcss": "^3.4.3", "vite": "^6.3.5", "vite-plugin-svgr": "^4.5.0", - "wait-on": "^9.0.3" + "wait-on": "^9.0.3", + "react-icons": "^5.5.0", + "@monaco-editor/react": "^4.7.0", + "pdfjs-dist": "^5.4.296", + "react-pdf": "^10.3.0", + "driver.js": "^1.4.0", + "react-quill-new": "^3.6.0", + "y-monaco": "^0.1.6" } } diff --git a/server.js b/server.js index d3f3a3d..a098c6a 100644 --- a/server.js +++ b/server.js @@ -51,7 +51,7 @@ function decrypt(text) { const execAsync = util.promisify(exec); const app = express(); -const PORT = process.env.PORT || 5000; +const PORT = 5000; const AI_SERVICE_URL = "http://localhost:5025"; // Middleware @@ -1499,7 +1499,9 @@ app.get("/api/projects/:id/export-zip", async (req, res) => { .json({ success: false, error: "Project not found" }); } - const projectData = await fs.readJSON(path.join(projectDir, "project.json")); + const projectData = await fs.readJSON( + path.join(projectDir, "project.json"), + ); const zip = new AdmZip(); // Add files to ZIP from project.json list diff --git a/src/components/pdfViewer.jsx b/src/components/pdfViewer.jsx index b931e43..80955bf 100644 --- a/src/components/pdfViewer.jsx +++ b/src/components/pdfViewer.jsx @@ -1,649 +1,3 @@ -// import React, { useState, useRef, useEffect, useCallback } from "react"; -// import { Document, Page, pdfjs } from "react-pdf"; -// import SyncIcon from "../assets/icons/syncIcon.svg?react"; -// import DownloadIcon from "../assets/icons/download.svg?react"; -// import axios from "axios"; -// import "react-pdf/dist/Page/TextLayer.css"; -// import "react-pdf/dist/Page/AnnotationLayer.css"; -// import "../assets/styles/pdfViewer.css"; -// import { useSettings } from "../context/useSettings"; - -// // pdfjs.GlobalWorkerOptions.workerSrc = "/pdf.worker.min.js"; -// pdfjs.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.js"; - -// const PdfViewer = ({ -// pdfUrl, -// pdfFileName, -// onLineJump, -// onCompile, -// fileTitle, -// onShowLogs, -// projectDetails, -// loading, -// }) => { -// const [numPages, setNumPages] = useState(null); -// const [scale, setScale] = useState(1); -// const [pageNumber, setPageNumber] = useState(1); -// const [scrollTop, setScrollTop] = useState(0); -// const [viewportHeight, setViewportHeight] = useState(800); -// const [zoomDropdownOpen, setZoomDropdownOpen] = useState(false); -// const [isEditingPage, setIsEditingPage] = useState(false); -// const [pageInputValue, setPageInputValue] = useState(""); -// const [isFitMode, setIsFitMode] = useState(false); -// const [pdfPageWidth, setPdfPageWidth] = useState(595); // A4 default in PDF points -// const viewportRef = useRef(null); -// const pageRefs = useRef([]); -// const zoomDropdownRef = useRef(null); -// const pageInputRef = useRef(null); -// const savedPageRef = useRef(1); // Persist page across recompilations -// const { settings } = useSettings(); - -// // Keep ref in sync with pageNumber state -// useEffect(() => { -// savedPageRef.current = pageNumber; -// }, [pageNumber]); - -// // Standard A4 aspect ratio height at 1x scale (96dpi approximate) -// // We'll adjust this once the first page loads if possible, but 842 is a safe LaTeX default. -// const ESTIMATED_PAGE_HEIGHT = 842; -// const GAP = 16; // gap-4 in tailwind is 16px -// const itemHeight = ESTIMATED_PAGE_HEIGHT * scale + GAP; - -// const getPdfFileName = () => { -// // Use the pdfFileName prop if available (from server compile response) -// // This is the correct filename like "{projectId}.pdf" -// if (pdfFileName) return pdfFileName; -// if (!pdfUrl) return null; -// return pdfUrl.split("/").pop(); -// }; - -// const zoomIn = () => { -// setIsFitMode(false); -// setScale((s) => Math.min(4, s + 0.2)); -// }; -// const zoomOut = () => { -// setIsFitMode(false); -// setScale((s) => Math.max(0.5, s - 0.2)); -// }; -// const setZoomPreset = (pct) => { -// setIsFitMode(false); -// setScale(pct / 100); -// setZoomDropdownOpen(false); -// }; -// const nextPage = () => setPageNumber((p) => Math.min(numPages, p + 1)); -// const prevPage = () => setPageNumber((p) => Math.max(1, p - 1)); - -// const zoomToFit = useCallback(() => { -// if (!viewportRef.current) return; -// const containerWidth = viewportRef.current.clientWidth - 32; // 16px padding each side -// const fitScale = Math.round((containerWidth / pdfPageWidth) * 100) / 100; -// const clamped = Math.max(0.5, Math.min(4, fitScale)); -// setIsFitMode(true); -// setZoomDropdownOpen(false); -// // Use rAF to batch the scale update and avoid layout thrashing -// requestAnimationFrame(() => setScale(clamped)); -// }, [pdfPageWidth]); - -// // Zoom-to-fit label for the dropdown button -// const zoomLabel = isFitMode ? "Zoom to fit" : `${Math.round(scale * 100)}%`; - -// const ZOOM_PRESETS = [50, 75, 100, 150, 200, 300, 400]; - -// const handlePageClick = async (event, pageIndex) => { -// if (!pdfUrl || !onLineJump) return; - -// // Use pdfFileName prop (from server compile response) which contains the correct -// // project ID-based filename. Fallback to extracting from pdfUrl for backwards compatibility. -// const fileName = pdfFileName || pdfUrl.split("/").pop(); - -// if (!fileName) { -// console.error("SyncTeX: No filename available"); -// return; -// } - -// const pageNum = pageIndex + 1; -// const bounds = event.currentTarget.getBoundingClientRect(); -// const x = (event.clientX - bounds.left) / scale; -// const y = (event.clientY - bounds.top) / scale; - -// try { -// console.log(`🔎 SyncTeX lookup for: ${fileName}`); -// const response = await axios.post("http://localhost:5000/api/synctex", { -// pdfFile: fileName, -// page: pageNum, -// x: x, -// y: y, -// }); - -// if (response.data.success) { -// console.log("📍 SyncTeX Jump to Line:", response.data.line); -// onLineJump(response.data.line); -// } -// } catch (error) { -// console.error("SyncTeX failed:", error); -// } -// }; - -// function onDocumentLoadSuccess({ numPages: loadedNumPages }) { -// setNumPages(loadedNumPages); -// // Restore page position after recompilation -// const targetPage = Math.min(savedPageRef.current, loadedNumPages); -// if (targetPage > 1) { -// // Delay slightly to let the document render first -// setTimeout(() => setPageNumber(targetPage), 100); -// } -// } - -// // Track intrinsic page width for zoom-to-fit calculation -// const handlePageLoadSuccess = (page) => { -// if (page.width && page.width !== pdfPageWidth) { -// setPdfPageWidth(page.width); -// } -// }; - -// // --- Page number input handlers --- -// const startEditingPage = () => { -// setIsEditingPage(true); -// setPageInputValue(String(pageNumber)); -// setTimeout(() => pageInputRef.current?.select(), 0); -// }; - -// const commitPageInput = () => { -// const val = parseInt(pageInputValue, 10); -// if (isNaN(val) || val < 1) { -// setPageNumber(1); -// } else if (val > numPages) { -// setPageNumber(numPages); -// } else { -// setPageNumber(val); -// } -// setIsEditingPage(false); -// }; - -// const handlePageInputKeyDown = (e) => { -// if (e.key === "Enter") { -// commitPageInput(); -// } else if (e.key === "Escape") { -// setIsEditingPage(false); -// } -// }; - -// const handleExportPDF = async () => { -// if (!pdfUrl) { -// alert("Please compile your document first to generate a PDF"); -// return; -// } - -// try { -// const response = await fetch(pdfUrl); -// const blob = await response.blob(); -// const defaultName = `${fileTitle || "document"}.pdf`; - -// // Check if we are running inside the Electron Desktop App -// const isElectron = -// window.electronAPI && typeof window.electronAPI.savePDF === "function"; - -// if (isElectron) { -// const arrayBuffer = await blob.arrayBuffer(); -// const result = await window.electronAPI.savePDF( -// arrayBuffer, -// defaultName, -// ); - -// if (result.success) { -// console.log("PDF saved successfully to:", result.filePath); -// } else if (!result.canceled) { -// throw new Error(result.error || "Unknown Electron save error"); -// } -// } else { -// // For Web Browser - Create a local URL for the Blob -// const downloadUrl = window.URL.createObjectURL(blob); -// const link = document.createElement("a"); - -// link.href = downloadUrl; -// link.download = defaultName; - -// // Append to body to ensure compatibility with all browsers -// document.body.appendChild(link); -// link.click(); - -// // Cleanup: Remove element and revoke URL to prevent memory leaks -// document.body.removeChild(link); -// window.URL.revokeObjectURL(downloadUrl); - -// console.log("PDF download triggered via Browser"); -// } -// } catch (error) { -// console.error("Error during PDF export:", error); -// alert(`An error occurred during PDF export: ${error.message}`); -// } -// }; - -// useEffect(() => { -// let rafId = null; -// const updateHeight = () => { -// if (rafId) cancelAnimationFrame(rafId); -// rafId = requestAnimationFrame(() => { -// if (viewportRef.current) { -// setViewportHeight(viewportRef.current.clientHeight); -// // Re-fit on window resize if in fit mode -// if (isFitMode) { -// const containerWidth = viewportRef.current.clientWidth - 32; -// const fitScale = -// Math.round((containerWidth / pdfPageWidth) * 100) / 100; -// setScale(Math.max(0.5, Math.min(4, fitScale))); -// } -// } -// }); -// }; -// updateHeight(); -// window.addEventListener("resize", updateHeight); -// return () => { -// window.removeEventListener("resize", updateHeight); -// if (rafId) cancelAnimationFrame(rafId); -// }; -// }, [isFitMode, pdfPageWidth]); - -// // Close dropdown on outside click -// useEffect(() => { -// const handleClickOutside = (e) => { -// if ( -// zoomDropdownRef.current && -// !zoomDropdownRef.current.contains(e.target) -// ) { -// setZoomDropdownOpen(false); -// } -// }; -// if (zoomDropdownOpen) { -// document.addEventListener("mousedown", handleClickOutside); -// } -// return () => document.removeEventListener("mousedown", handleClickOutside); -// }, [zoomDropdownOpen]); - -// // Ctrl+0 shortcut for Zoom to fit -// useEffect(() => { -// const handleKeyDown = (e) => { -// if ((e.ctrlKey || e.metaKey) && e.key === "0") { -// e.preventDefault(); -// zoomToFit(); -// } -// }; -// window.addEventListener("keydown", handleKeyDown); -// return () => window.removeEventListener("keydown", handleKeyDown); -// }, [zoomToFit]); - -// const handleScroll = (e) => { -// const newScrollTop = e.target.scrollTop; -// setScrollTop(newScrollTop); - -// // Update pageNumber based on current scroll position -// const currentPage = Math.round(newScrollTop / itemHeight) + 1; -// if ( -// currentPage !== pageNumber && -// currentPage >= 1 && -// currentPage <= numPages -// ) { -// setPageNumber(currentPage); -// } -// }; - -// useEffect(() => { -// if (numPages && viewportRef.current) { -// const targetScrollTop = (pageNumber - 1) * itemHeight; -// // Only scroll if we are significantly off (prevents feedback loops) -// if (Math.abs(viewportRef.current.scrollTop - targetScrollTop) > 10) { -// viewportRef.current.scrollTo({ -// top: targetScrollTop, -// behavior: "smooth", -// }); -// } -// } -// }, [pageNumber, numPages, itemHeight]); - -// // Calculate sliding window -// const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - 2); -// const endIndex = Math.min( -// (numPages || 0) - 1, -// Math.floor((scrollTop + viewportHeight) / itemHeight) + 2, -// ); -// const visiblePages = numPages -// ? Array.from( -// { length: endIndex - startIndex + 1 }, -// (_, i) => startIndex + i, -// ) -// : []; - -// return ( -//
-// {/* Toolbar - Compile button always visible, rest only when PDF exists */} -//
-//
-// -// {projectDetails.compilationStatus === "error" ? ( -//

-// Logs -//

-// ) : ( -//

-// Logs -//

-// )} -//
- -// {/* Page navigation - only visible when PDF exists */} -// {pdfUrl && ( -//
-// -// -// {isEditingPage ? ( -// -// setPageInputValue(e.target.value.replace(/[^0-9]/g, "")) -// } -// onKeyDown={handlePageInputKeyDown} -// onBlur={commitPageInput} -// className="w-8 text-center text-xs font-poppins font-light underline outline-none bg-transparent border-b" -// style={{ -// color: -// settings.appearance.customThemes[ -// settings.appearance.theme -// ].text1, -// borderColor: -// settings.appearance.customThemes[ -// settings.appearance.theme -// ].text1, -// }} -// /> -// ) : ( -// -// {String(pageNumber).padStart(2, "0")} -// -// )}{" "} -// of {numPages} -// -// -//
-// )} - -// {/* Zoom dropdown and download - only visible when PDF exists */} -// {pdfUrl && ( -//
-// {/* Zoom Dropdown */} -//
-// - -// {zoomDropdownOpen && ( -//
-// {/* Zoom to fit */} -// - -// {/* Zoom in */} -// - -// {/* Zoom out */} -// - -// {/* Divider */} -//
- -// {/* Preset percentages */} -// {ZOOM_PRESETS.map((pct) => ( -// -// ))} -//
-// )} -//
- -// {/* Download */} -// -//
-// )} -//
-// {/* PDF Viewer */} -//
-// {pdfUrl ? ( -//
-// Loading PDF...
} -// error={ -//
Failed to load PDF.
-// } -// > -// {visiblePages.map((index) => ( -//
(pageRefs.current[index] = el)} -// onClick={(e) => handlePageClick(e, index)} -// className="cursor-text absolute left-1/2 -translate-x-1/2" -// style={{ -// top: `${index * itemHeight}px`, -// height: `${ESTIMATED_PAGE_HEIGHT * scale}px`, -// }} -// > -// -//
-// ))} -// -//
-// ) : ( -//
-// Compile the project to view the PDF file -//
-// )} -//
-//
-// ); -// }; - -// export default PdfViewer; import React, { useState, useRef, @@ -1024,7 +378,7 @@ const PdfViewer = ({ Compile

- {projectDetails.compilationStatus === "error" ? ( + {projectDetails?.compilationStatus === "error" ? (

{ + const handleClickOutside = (event) => { + const menuElement = menuRefs.current.file; + + if (menuElement && document.body.contains(menuElement)) { + if (!menuElement.contains(event.target)) { + setIsMathModalOpen(false); + setIsCitationModalOpen(false); + setIsTableModalOpen(false); + setIsImageModalOpen(false); + setIsShareModalOpen(false); + setIsVersionModalOpen(false); + setIsProfileActive(false); + setShowAuthModal(false); + } + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + const handleMathIconClick = (e) => { e.preventDefault(); setIsMathModalOpen(true); @@ -434,7 +457,10 @@ const DynamicSideBar = ({ ) : ( -

+
(menuRefs.current.file = el)} + >
diff --git a/src/components/statusBar.jsx b/src/components/statusBar.jsx index 3547a2a..7ec516f 100644 --- a/src/components/statusBar.jsx +++ b/src/components/statusBar.jsx @@ -11,7 +11,7 @@ import { projectContext } from "../context/useProject"; const StatusBar = () => { const { isServerConnected } = useAuth(); - const { projectDetails } = useContext(projectContext); + const { projectDetails, updateProjectDetails } = useContext(projectContext); const { settings } = useSettings(); const [progress, setProgress] = useState(""); React.useEffect(() => { @@ -73,6 +73,15 @@ const StatusBar = () => { ].text3, }} className="w-4 h-4" + onClick={() => { + // Fire Ctrl+P as a key event so editorPage picks it up + const evt = new KeyboardEvent("keydown", { + key: "p", + ctrlKey: true, + bubbles: true, + }); + window.dispatchEvent(evt); + }} /> )} { {/* Card Visual */}
{ const location = useLocation(); const { projectDetails, updateProjectDetails } = useContext(projectContext); const isSectionSpaceOpen = projectDetails.isSectionSpaceOpen; - + const setIsSectionSpaceOpen = (val) => { updateProjectDetails({ - isSectionSpaceOpen: typeof val === "function" ? val(isSectionSpaceOpen) : val + isSectionSpaceOpen: + typeof val === "function" ? val(isSectionSpaceOpen) : val, }); }; @@ -55,35 +56,45 @@ const PageLayout = () => { return () => window.removeEventListener("keydown", handleKey); }, []); - const handleStartSectionDrag = useCallback((e) => { - e.preventDefault(); - isDraggingSection.current = true; - sectionDragStartX.current = e.clientX; - sectionDragStartWidth.current = sectionSpaceWidth; - document.body.style.cursor = "col-resize"; - document.body.style.userSelect = "none"; - - const onMouseMove = (moveEvent) => { - if (!isDraggingSection.current) return; - const delta = moveEvent.clientX - sectionDragStartX.current; - const newWidth = Math.min(480, Math.max(160, sectionDragStartWidth.current + delta)); - setSectionSpaceWidth(newWidth); - }; - - const onMouseUp = () => { - isDraggingSection.current = false; - document.body.style.cursor = ""; - document.body.style.userSelect = ""; - window.removeEventListener("mousemove", onMouseMove); - window.removeEventListener("mouseup", onMouseUp); - }; - - window.addEventListener("mousemove", onMouseMove); - window.addEventListener("mouseup", onMouseUp); - }, [sectionSpaceWidth]); + const handleStartSectionDrag = useCallback( + (e) => { + e.preventDefault(); + isDraggingSection.current = true; + sectionDragStartX.current = e.clientX; + sectionDragStartWidth.current = sectionSpaceWidth; + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + + const onMouseMove = (moveEvent) => { + if (!isDraggingSection.current) return; + const delta = moveEvent.clientX - sectionDragStartX.current; + const newWidth = Math.min( + 480, + Math.max(160, sectionDragStartWidth.current + delta), + ); + setSectionSpaceWidth(newWidth); + }; + + const onMouseUp = () => { + isDraggingSection.current = false; + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + window.removeEventListener("mousemove", onMouseMove); + window.removeEventListener("mouseup", onMouseUp); + }; + + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + }, + [sectionSpaceWidth], + ); - const handleStartTour = () => { startTour(); }; - const handleOpenAIChat = () => { setIsAIChatOpen(true); }; + const handleStartTour = () => { + startTour(); + }; + const handleOpenAIChat = () => { + setIsAIChatOpen(true); + }; return (
{ )} {/* Section Space panel with resize handle */} - {!isDistractionFree && isSectionSpaceOpen && ( - - )} + {!isDistractionFree && + isSectionSpaceOpen && + projectDetails?.currentProject?.id && ( + + )}
-
-
-

Docière Pro

-
-
@@ -312,9 +307,9 @@ function Login() {
{/* Fixed footer at bottom with minimal spacing */} -
+ {/*

© 2025 Dociere. All rights reserved.

-
+
*/}
); diff --git a/src/pages/User_Account/signup.jsx b/src/pages/User_Account/signup.jsx index 95745c3..17ca1dc 100644 --- a/src/pages/User_Account/signup.jsx +++ b/src/pages/User_Account/signup.jsx @@ -63,12 +63,12 @@ function DynamicSignup() {
- {/* Branding - smaller */} -
+ {/* Branding */} + {/*

Docière Pro

-
+
*/} {/* Main content container */}
@@ -203,9 +203,9 @@ function DynamicSignup() {
{/* Footer */} -
+ {/*

© 2025 Dociere. All rights reserved.

-
+
*/}
{ type="application/pdf" className="w-full h-full rounded border border-[#CFCFCF]" /> + {/* */}
)} diff --git a/src/pages/settingsPage.jsx b/src/pages/settingsPage.jsx index eafd17e..644c25c 100644 --- a/src/pages/settingsPage.jsx +++ b/src/pages/settingsPage.jsx @@ -211,7 +211,7 @@ const SettingsPage = () => { {/* Back Button */} +

+ Recent Projects +

+ {/* Modal content with scroll */} -
-

- Recent Projects -

- +
{(projectData || [])?.map((project) => (