Reubencf commited on
Commit
56ce56a
·
1 Parent(s): ef3fe15

changed latex

Browse files
app/api/data/route.ts CHANGED
@@ -120,6 +120,50 @@ export async function POST(request: NextRequest) {
120
 
121
  await writeFile(filePath, content, 'utf-8')
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  return NextResponse.json({ success: true })
124
  }
125
 
 
120
 
121
  await writeFile(filePath, content, 'utf-8')
122
 
123
+ // Auto-convert .tex files to PDF
124
+ if (fileName.endsWith('.tex')) {
125
+ console.log('Detected .tex file, auto-converting to PDF...')
126
+ try {
127
+ // Compile LaTeX to PDF using LaTeX.Online
128
+ const compileResponse = await fetch('https://latexonline.cc/compile', {
129
+ method: 'POST',
130
+ headers: {
131
+ 'Content-Type': 'text/plain',
132
+ },
133
+ body: content
134
+ })
135
+
136
+ if (compileResponse.ok) {
137
+ const pdfBuffer = await compileResponse.arrayBuffer()
138
+ const pdfFileName = fileName.replace('.tex', '.pdf')
139
+ const pdfFilePath = path.join(targetDir, pdfFileName)
140
+
141
+ await writeFile(pdfFilePath, Buffer.from(pdfBuffer))
142
+ console.log(`PDF generated successfully: ${pdfFilePath}`)
143
+
144
+ return NextResponse.json({
145
+ success: true,
146
+ pdfGenerated: true,
147
+ pdfFileName: pdfFileName
148
+ })
149
+ } else {
150
+ console.error('PDF compilation failed:', await compileResponse.text())
151
+ return NextResponse.json({
152
+ success: true,
153
+ pdfGenerated: false,
154
+ error: 'LaTeX file saved but PDF generation failed'
155
+ })
156
+ }
157
+ } catch (pdfError) {
158
+ console.error('Error generating PDF:', pdfError)
159
+ return NextResponse.json({
160
+ success: true,
161
+ pdfGenerated: false,
162
+ error: 'LaTeX file saved but PDF generation failed'
163
+ })
164
+ }
165
+ }
166
+
167
  return NextResponse.json({ success: true })
168
  }
169
 
app/api/latex/compile/route.ts CHANGED
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
2
 
3
  export async function POST(request: NextRequest) {
4
  try {
5
- const { latex, sessionId } = await request.json()
6
 
7
  if (!latex) {
8
  return NextResponse.json(
@@ -11,25 +11,48 @@ export async function POST(request: NextRequest) {
11
  )
12
  }
13
 
14
- // Note: In a production environment, you would:
15
- // 1. Use a LaTeX compiler service (like LaTeX online compiler API)
16
- // 2. Or install and use pdflatex locally with child_process
17
- // 3. Or use a Docker container with LaTeX installed
18
 
19
- // For now, we'll provide the LaTeX source as a downloadable .tex file
20
- // since PDF compilation requires external LaTeX installation
 
 
 
 
 
 
21
 
22
- // Return the LaTeX content as a downloadable file
23
- const response = new NextResponse(latex)
24
- response.headers.set('Content-Type', 'text/plain')
25
- response.headers.set('Content-Disposition', 'attachment; filename="document.tex"')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  return response
28
 
29
  } catch (error) {
30
  console.error('Error compiling LaTeX:', error)
31
  return NextResponse.json(
32
- { error: 'Failed to compile LaTeX' },
 
 
 
33
  { status: 500 }
34
  )
35
  }
 
2
 
3
  export async function POST(request: NextRequest) {
4
  try {
5
+ const { latex, filename } = await request.json()
6
 
7
  if (!latex) {
8
  return NextResponse.json(
 
11
  )
12
  }
13
 
14
+ console.log('Compiling LaTeX to PDF using LaTeX.Online...')
 
 
 
15
 
16
+ // Use LaTeX.Online free API to compile LaTeX to PDF
17
+ const compileResponse = await fetch('https://latexonline.cc/compile', {
18
+ method: 'POST',
19
+ headers: {
20
+ 'Content-Type': 'text/plain',
21
+ },
22
+ body: latex
23
+ })
24
 
25
+ if (!compileResponse.ok) {
26
+ const errorText = await compileResponse.text()
27
+ console.error('LaTeX compilation failed:', errorText)
28
+ return NextResponse.json(
29
+ {
30
+ error: 'LaTeX compilation failed',
31
+ details: errorText
32
+ },
33
+ { status: 500 }
34
+ )
35
+ }
36
+
37
+ // Get the PDF buffer
38
+ const pdfBuffer = await compileResponse.arrayBuffer()
39
+
40
+ console.log('LaTeX compiled successfully, PDF size:', pdfBuffer.byteLength, 'bytes')
41
+
42
+ // Return the PDF
43
+ const response = new NextResponse(Buffer.from(pdfBuffer))
44
+ response.headers.set('Content-Type', 'application/pdf')
45
+ response.headers.set('Content-Disposition', `attachment; filename="${filename || 'document.pdf'}"`)
46
 
47
  return response
48
 
49
  } catch (error) {
50
  console.error('Error compiling LaTeX:', error)
51
  return NextResponse.json(
52
+ {
53
+ error: 'Failed to compile LaTeX',
54
+ details: error instanceof Error ? error.message : String(error)
55
+ },
56
  { status: 500 }
57
  )
58
  }
app/components/Desktop.tsx CHANGED
@@ -20,9 +20,6 @@ import { ContextMenu } from './ContextMenu'
20
  import { AboutModal } from './AboutModal'
21
 
22
  import { FlutterRunner } from './FlutterRunner'
23
-
24
-
25
- import { LaTeXEditor } from './LaTeXEditor'
26
  import { QuizApp } from './QuizApp'
27
  import { motion, AnimatePresence } from 'framer-motion'
28
  import { SystemPowerOverlay } from './SystemPowerOverlay'
@@ -68,8 +65,6 @@ export function Desktop() {
68
  const [activeFlutterApp, setActiveFlutterApp] = useState<any>(null)
69
 
70
  const [flutterCodeEditorOpen, setFlutterCodeEditorOpen] = useState(false)
71
- const [latexEditorOpen, setLaTeXEditorOpen] = useState(false)
72
- const [activeLaTeXApp, setActiveLaTeXApp] = useState<any>(null)
73
  const [quizAppOpen, setQuizAppOpen] = useState(false)
74
 
75
  // Minimized states
@@ -84,7 +79,6 @@ export function Desktop() {
84
  const [flutterRunnerMinimized, setFlutterRunnerMinimized] = useState(false)
85
 
86
  const [flutterCodeEditorMinimized, setFlutterCodeEditorMinimized] = useState(false)
87
- const [latexEditorMinimized, setLaTeXEditorMinimized] = useState(false)
88
  const [quizAppMinimized, setQuizAppMinimized] = useState(false)
89
 
90
  const [powerState, setPowerState] = useState<'active' | 'sleep' | 'restart' | 'shutdown'>('active')
@@ -111,7 +105,6 @@ export function Desktop() {
111
  setFlutterRunnerOpen(false)
112
 
113
  setFlutterCodeEditorOpen(false)
114
- setLaTeXEditorOpen(false)
115
  setQuizAppOpen(false)
116
 
117
  // Reset all minimized states
@@ -123,7 +116,6 @@ export function Desktop() {
123
  setFlutterRunnerMinimized(false)
124
 
125
  setFlutterCodeEditorMinimized(false)
126
- setLaTeXEditorMinimized(false)
127
  setQuizAppMinimized(false)
128
 
129
  // Reset window z-indices
@@ -228,25 +220,6 @@ export function Desktop() {
228
  setFlutterCodeEditorMinimized(false)
229
  }
230
 
231
- const openLaTeXEditor = () => {
232
- // Check if there's file content stored from File Manager
233
- const storedContent = sessionStorage.getItem('latexFileContent')
234
- if (storedContent) {
235
- setActiveLaTeXApp({ content: storedContent })
236
- sessionStorage.removeItem('latexFileContent') // Clean up
237
- } else {
238
- setActiveLaTeXApp(null)
239
- }
240
- setLaTeXEditorOpen(true)
241
- setLaTeXEditorMinimized(false)
242
- bringWindowToFront('latexEditor')
243
- }
244
-
245
- const closeLaTeXEditor = () => {
246
- setLaTeXEditorOpen(false)
247
- setLaTeXEditorMinimized(false)
248
- }
249
-
250
  const openQuizApp = () => {
251
  setQuizAppOpen(true)
252
  setQuizAppMinimized(false)
@@ -281,9 +254,6 @@ export function Desktop() {
281
  case 'flutter-editor':
282
  openFlutterCodeEditor()
283
  break
284
- case 'latex-editor':
285
- openLaTeXEditor()
286
- break
287
  case 'quiz':
288
  openQuizApp()
289
  break
@@ -518,26 +488,6 @@ export function Desktop() {
518
  })
519
  }
520
 
521
- if (latexEditorMinimized && latexEditorOpen) {
522
- minimizedApps.push({
523
- id: 'latex-editor',
524
- label: 'LaTeX Studio',
525
- icon: (
526
- <div className="bg-gradient-to-b from-slate-700 to-slate-900 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden">
527
- <div className="absolute inset-0 bg-gradient-to-b from-white/10 to-transparent opacity-30" />
528
- <div className="relative z-10 flex flex-col items-center justify-center">
529
- <Function size={18} weight="bold" className="text-green-400 drop-shadow-md" />
530
- <span className="text-[5px] font-black text-gray-300 tracking-widest mt-0.5">TEX</span>
531
- </div>
532
- </div>
533
- ),
534
- onRestore: () => {
535
- setLaTeXEditorMinimized(false)
536
- bringWindowToFront('latexEditor')
537
- }
538
- })
539
- }
540
-
541
  if (quizAppMinimized && quizAppOpen) {
542
  minimizedApps.push({
543
  id: 'quiz',
@@ -699,17 +649,6 @@ export function Desktop() {
699
  />
700
  </div>
701
 
702
- <div className="pointer-events-auto w-24 h-24">
703
- <DraggableDesktopIcon
704
- id="latex-editor"
705
- label="LaTeX Studio"
706
- iconType="latex"
707
- initialPosition={{ x: 0, y: 0 }}
708
- onClick={() => { }}
709
- onDoubleClick={openLaTeXEditor}
710
- />
711
- </div>
712
-
713
  <div className="pointer-events-auto w-24 h-24">
714
  <DraggableDesktopIcon
715
  id="quiz"
@@ -909,35 +848,6 @@ export function Desktop() {
909
  </motion.div>
910
  )}
911
 
912
- {latexEditorOpen && (
913
- <motion.div
914
- key="latex-editor"
915
- initial={{ opacity: 0, scale: 0.95 }}
916
- animate={{
917
- opacity: latexEditorMinimized ? 0 : 1,
918
- scale: latexEditorMinimized ? 0.9 : 1,
919
- y: latexEditorMinimized ? 100 : 0,
920
- }}
921
- exit={{ opacity: 0, scale: 0.95 }}
922
- transition={{ duration: 0.2 }}
923
- style={{
924
- pointerEvents: latexEditorMinimized ? 'none' : 'auto',
925
- display: latexEditorMinimized ? 'none' : 'block'
926
- }}
927
- >
928
- <LaTeXEditor
929
- initialContent={activeLaTeXApp?.content}
930
- onClose={() => {
931
- setLaTeXEditorOpen(false)
932
- setActiveLaTeXApp(null)
933
- }}
934
- onMinimize={() => setLaTeXEditorMinimized(true)}
935
- onFocus={() => bringWindowToFront('latexEditor')}
936
- zIndex={windowZIndices.latexEditor || 1000}
937
- />
938
- </motion.div>
939
- )}
940
-
941
  {quizAppOpen && (
942
  <motion.div
943
  key="quiz-app"
 
20
  import { AboutModal } from './AboutModal'
21
 
22
  import { FlutterRunner } from './FlutterRunner'
 
 
 
23
  import { QuizApp } from './QuizApp'
24
  import { motion, AnimatePresence } from 'framer-motion'
25
  import { SystemPowerOverlay } from './SystemPowerOverlay'
 
65
  const [activeFlutterApp, setActiveFlutterApp] = useState<any>(null)
66
 
67
  const [flutterCodeEditorOpen, setFlutterCodeEditorOpen] = useState(false)
 
 
68
  const [quizAppOpen, setQuizAppOpen] = useState(false)
69
 
70
  // Minimized states
 
79
  const [flutterRunnerMinimized, setFlutterRunnerMinimized] = useState(false)
80
 
81
  const [flutterCodeEditorMinimized, setFlutterCodeEditorMinimized] = useState(false)
 
82
  const [quizAppMinimized, setQuizAppMinimized] = useState(false)
83
 
84
  const [powerState, setPowerState] = useState<'active' | 'sleep' | 'restart' | 'shutdown'>('active')
 
105
  setFlutterRunnerOpen(false)
106
 
107
  setFlutterCodeEditorOpen(false)
 
108
  setQuizAppOpen(false)
109
 
110
  // Reset all minimized states
 
116
  setFlutterRunnerMinimized(false)
117
 
118
  setFlutterCodeEditorMinimized(false)
 
119
  setQuizAppMinimized(false)
120
 
121
  // Reset window z-indices
 
220
  setFlutterCodeEditorMinimized(false)
221
  }
222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  const openQuizApp = () => {
224
  setQuizAppOpen(true)
225
  setQuizAppMinimized(false)
 
254
  case 'flutter-editor':
255
  openFlutterCodeEditor()
256
  break
 
 
 
257
  case 'quiz':
258
  openQuizApp()
259
  break
 
488
  })
489
  }
490
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  if (quizAppMinimized && quizAppOpen) {
492
  minimizedApps.push({
493
  id: 'quiz',
 
649
  />
650
  </div>
651
 
 
 
 
 
 
 
 
 
 
 
 
652
  <div className="pointer-events-auto w-24 h-24">
653
  <DraggableDesktopIcon
654
  id="quiz"
 
848
  </motion.div>
849
  )}
850
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
851
  {quizAppOpen && (
852
  <motion.div
853
  key="quiz-app"
app/components/FlutterRunner.tsx CHANGED
@@ -28,74 +28,7 @@ interface FileNode {
28
  isOpen?: boolean
29
  }
30
 
31
- const DEFAULT_FLUTTER_CODE = `import 'package:flutter/material.dart';
32
-
33
- void main() {
34
- runApp(const MyApp());
35
- }
36
-
37
- class MyApp extends StatelessWidget {
38
- const MyApp({super.key});
39
-
40
- @override
41
- Widget build(BuildContext context) {
42
- return MaterialApp(
43
- title: 'Flutter Demo',
44
- theme: ThemeData(
45
- primarySwatch: Colors.blue,
46
- useMaterial3: true,
47
- ),
48
- home: const MyHomePage(title: 'Flutter Demo Home Page'),
49
- );
50
- }
51
- }
52
-
53
- class MyHomePage extends StatefulWidget {
54
- const MyHomePage({super.key, required this.title});
55
-
56
- final String title;
57
-
58
- @override
59
- State<MyHomePage> createState() => _MyHomePageState();
60
- }
61
-
62
- class _MyHomePageState extends State<MyHomePage> {
63
- int _counter = 0;
64
-
65
- void _incrementCounter() {
66
- setState(() {
67
- _counter++;
68
- });
69
- }
70
-
71
- @override
72
- Widget build(BuildContext context) {
73
- return Scaffold(
74
- appBar: AppBar(
75
- title: Text(widget.title),
76
- ),
77
- body: Center(
78
- child: Column(
79
- mainAxisAlignment: MainAxisAlignment.center,
80
- children: <Widget>[
81
- const Text(
82
- 'You have pushed the button this many times:',
83
- ),
84
- Text(
85
- '$_counter',
86
- style: Theme.of(context).textTheme.headlineMedium,
87
- ),
88
- ],
89
- ),
90
- ),
91
- floatingActionButton: FloatingActionButton(
92
- onPressed: _incrementCounter,
93
- tooltip: 'Increment',
94
- child: const Icon(Icons.add),
95
- ),
96
- );
97
- }
98
- }`
99
 
100
  export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex, initialCode }: FlutterRunnerProps) {
101
  const [code, setCode] = useState(initialCode || DEFAULT_FLUTTER_CODE)
@@ -371,9 +304,6 @@ export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex
371
  <div className="w-24" /> {/* Spacer for centering */}
372
  </div>
373
  <div className="flex-1 bg-[#1e1e1e] relative overflow-hidden">
374
- <div className="absolute top-0 left-0 right-0 bg-gray-900/95 backdrop-blur-sm px-4 py-2 z-10 border-b border-gray-700">
375
- <p className="text-red-500 text-sm font-bold">Copy the code from editor to DartPad</p>
376
- </div>
377
  <iframe
378
 
379
  src="https://dartpad.dev/embed-flutter.html?theme=dark&split=50"
 
28
  isOpen?: boolean
29
  }
30
 
31
+ const DEFAULT_FLUTTER_CODE = ``
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex, initialCode }: FlutterRunnerProps) {
34
  const [code, setCode] = useState(initialCode || DEFAULT_FLUTTER_CODE)
 
304
  <div className="w-24" /> {/* Spacer for centering */}
305
  </div>
306
  <div className="flex-1 bg-[#1e1e1e] relative overflow-hidden">
 
 
 
307
  <iframe
308
 
309
  src="https://dartpad.dev/embed-flutter.html?theme=dark&split=50"
app/components/LaTeXEditor.tsx DELETED
@@ -1,515 +0,0 @@
1
- 'use client'
2
-
3
- import React, { useState, useEffect, useRef } from 'react'
4
- import Editor from '@monaco-editor/react'
5
- import {
6
- Play,
7
- Download,
8
- X,
9
- Minus,
10
- Square,
11
- SidebarSimple,
12
- FileText,
13
- ArrowsClockwise,
14
- CaretDown,
15
- FilePdf,
16
- FloppyDisk,
17
- Check
18
- } from '@phosphor-icons/react'
19
- import Window from './Window'
20
- import katex from 'katex'
21
- import 'katex/dist/katex.min.css'
22
-
23
- interface LaTeXEditorProps {
24
- onClose: () => void
25
- onMinimize?: () => void
26
- onMaximize?: () => void
27
- onFocus?: () => void
28
- zIndex?: number
29
- initialContent?: string
30
- }
31
-
32
- interface FileNode {
33
- id: string
34
- name: string
35
- type: 'file' | 'folder'
36
- content?: string
37
- children?: FileNode[]
38
- isOpen?: boolean
39
- }
40
-
41
- const DEFAULT_LATEX = `\\documentclass{article}
42
- \\usepackage{amsmath}
43
- \\title{My LaTeX Document}
44
- \\author{Reuben OS}
45
- \\date{\\today}
46
-
47
- \\begin{document}
48
-
49
- \\maketitle
50
-
51
- \\section{Introduction}
52
- Welcome to LaTeX Studio on Reuben OS.
53
-
54
- \\section{Mathematics}
55
- Here is an equation:
56
- \\[ E = mc^2 \\]
57
-
58
- \\end{document}`
59
-
60
- export function LaTeXEditor({ onClose, onMinimize, onMaximize, onFocus, zIndex, initialContent }: LaTeXEditorProps) {
61
- const [code, setCode] = useState(initialContent || DEFAULT_LATEX)
62
- const [showSidebar, setShowSidebar] = useState(true)
63
- const [files, setFiles] = useState<FileNode[]>([])
64
- const [activeFileId, setActiveFileId] = useState('main')
65
- const [activeFileName, setActiveFileName] = useState('main.tex')
66
- const previewRef = useRef<HTMLDivElement>(null)
67
- const [lastSaved, setLastSaved] = useState<Date | null>(null)
68
- const [isSaving, setIsSaving] = useState(false)
69
- const [passkey, setPasskey] = useState('')
70
- const [isUnlocked, setIsUnlocked] = useState(false)
71
- const [tempPasskey, setTempPasskey] = useState('')
72
- const [loading, setLoading] = useState(false)
73
-
74
- // Load files from secure storage
75
- const loadFiles = async (key: string) => {
76
- setLoading(true)
77
- try {
78
- const response = await fetch(`/api/data?key=${encodeURIComponent(key)}&folder=`)
79
- const data = await response.json()
80
-
81
- if (data.error) {
82
- throw new Error(data.error)
83
- }
84
-
85
- // Filter for .tex files
86
- const texFiles = data.files?.filter((f: any) => f.name.endsWith('.tex')) || []
87
-
88
- if (texFiles.length > 0) {
89
- const fileNodes: FileNode[] = texFiles.map((file: any, index: number) => ({
90
- id: `file_${index}`,
91
- name: file.name,
92
- type: 'file' as const,
93
- content: file.content
94
- }))
95
-
96
- setFiles([{
97
- id: 'root',
98
- name: 'LaTeX Files',
99
- type: 'folder',
100
- isOpen: true,
101
- children: fileNodes
102
- }])
103
-
104
- // Load first file
105
- if (fileNodes.length > 0) {
106
- setActiveFileId(fileNodes[0].id)
107
- setActiveFileName(fileNodes[0].name)
108
- setCode(fileNodes[0].content || DEFAULT_LATEX)
109
- }
110
- } else {
111
- // No files found, create default
112
- setFiles([{
113
- id: 'root',
114
- name: 'LaTeX Files',
115
- type: 'folder',
116
- isOpen: true,
117
- children: [
118
- { id: 'main', name: 'main.tex', type: 'file', content: DEFAULT_LATEX }
119
- ]
120
- }])
121
- setCode(DEFAULT_LATEX)
122
- setActiveFileName('main.tex')
123
- }
124
- } catch (err) {
125
- console.error('Error loading files:', err)
126
- // Create default on error
127
- setFiles([{
128
- id: 'root',
129
- name: 'LaTeX Files',
130
- type: 'folder',
131
- isOpen: true,
132
- children: [
133
- { id: 'main', name: 'main.tex', type: 'file', content: DEFAULT_LATEX }
134
- ]
135
- }])
136
- setCode(DEFAULT_LATEX)
137
- setActiveFileName('main.tex')
138
- } finally {
139
- setLoading(false)
140
- }
141
- }
142
-
143
- const handleUnlock = async () => {
144
- if (tempPasskey.trim().length >= 4) {
145
- setPasskey(tempPasskey.trim())
146
- setIsUnlocked(true)
147
- await loadFiles(tempPasskey.trim())
148
- } else {
149
- alert('Passkey must be at least 4 characters')
150
- }
151
- }
152
-
153
- const handleFileClick = (file: FileNode) => {
154
- if (file.type === 'file') {
155
- setActiveFileId(file.id)
156
- setActiveFileName(file.name)
157
- setCode(file.content || '')
158
- }
159
- }
160
-
161
- // Update code if initialContent changes (e.g. opening a new file)
162
- useEffect(() => {
163
- if (initialContent) {
164
- setCode(initialContent)
165
- // Check if we have a passkey from session
166
- const sessionPasskey = sessionStorage.getItem('currentPasskey')
167
- if (sessionPasskey) {
168
- setPasskey(sessionPasskey)
169
- setIsUnlocked(true)
170
- loadFiles(sessionPasskey)
171
- }
172
- }
173
- }, [initialContent])
174
-
175
- // Auto-save functionality
176
- useEffect(() => {
177
- if (!passkey || !isUnlocked || !activeFileName) return
178
-
179
- const saveTimer = setTimeout(async () => {
180
- setIsSaving(true)
181
- try {
182
- await fetch('/api/data', {
183
- method: 'POST',
184
- headers: { 'Content-Type': 'application/json' },
185
- body: JSON.stringify({
186
- key: passkey,
187
- passkey: passkey,
188
- action: 'save_file',
189
- fileName: activeFileName,
190
- content: code
191
- })
192
- })
193
- setLastSaved(new Date())
194
- } catch (err) {
195
- console.error('Auto-save failed:', err)
196
- } finally {
197
- setIsSaving(false)
198
- }
199
- }, 2000) // Debounce 2s
200
-
201
- return () => clearTimeout(saveTimer)
202
- }, [code, passkey, activeFileName, isUnlocked])
203
-
204
- // Render LaTeX preview
205
- useEffect(() => {
206
- if (previewRef.current) {
207
- try {
208
- // Extract title, author, and date from LaTeX preamble
209
- let title = 'Untitled Document'
210
- let author = 'Anonymous'
211
- let date = new Date().toLocaleDateString()
212
-
213
- const titleMatch = code.match(/\\title\{([^}]+)\}/)
214
- const authorMatch = code.match(/\\author\{([^}]+)\}/)
215
- const dateMatch = code.match(/\\date\{([^}]+)\}/)
216
-
217
- if (titleMatch) title = titleMatch[1]
218
- if (authorMatch) author = authorMatch[1]
219
- if (dateMatch) {
220
- date = dateMatch[1].replace(/\\today/g, new Date().toLocaleDateString())
221
- }
222
-
223
- // Process the document content
224
- let processedText = code
225
- // Remove preamble
226
- .replace(/\\documentclass\{[^}]+\}/g, '')
227
- .replace(/\\usepackage\{[^}]+\}/g, '')
228
- .replace(/\\title\{[^}]+\}/g, '')
229
- .replace(/\\author\{[^}]+\}/g, '')
230
- .replace(/\\date\{[^}]+\}/g, '')
231
- .replace(/\\begin\{document\}/g, '')
232
- .replace(/\\end\{document\}/g, '')
233
-
234
- // Handle title
235
- .replace(/\\maketitle/g, `
236
- <div style="text-align: center; margin-bottom: 2em; padding-bottom: 1em; border-bottom: 1px solid #ccc;">
237
- <h1 style="font-size: 2em; margin: 0.5em 0;">${title}</h1>
238
- <p style="margin: 0.3em 0; font-size: 1.1em;">${author}</p>
239
- <p style="margin: 0.3em 0; color: #666;">${date}</p>
240
- </div>
241
- `)
242
-
243
- // Sections and structure
244
- .replace(/\\section\{([^}]+)\}/g, '<h2 style="font-size: 1.5em; margin-top: 1.5em; margin-bottom: 0.5em; font-weight: bold;">$1</h2>')
245
- .replace(/\\subsection\{([^}]+)\}/g, '<h3 style="font-size: 1.2em; margin-top: 1em; margin-bottom: 0.5em; font-weight: bold;">$1</h3>')
246
- .replace(/\\subsubsection\{([^}]+)\}/g, '<h4 style="font-size: 1em; margin-top: 0.8em; margin-bottom: 0.3em; font-weight: bold;">$1</h4>')
247
- .replace(/\\paragraph\{([^}]+)\}/g, '<p style="font-weight: bold; margin-top: 0.5em;">$1</p>')
248
-
249
- // Text formatting
250
- .replace(/\\textbf\{([^}]+)\}/g, '<strong>$1</strong>')
251
- .replace(/\\textit\{([^}]+)\}/g, '<em>$1</em>')
252
- .replace(/\\underline\{([^}]+)\}/g, '<u>$1</u>')
253
- .replace(/\\emph\{([^}]+)\}/g, '<em>$1</em>')
254
- .replace(/\\texttt\{([^}]+)\}/g, '<code style="font-family: monospace; background: #f5f5f5; padding: 2px 4px; border-radius: 3px;">$1</code>')
255
-
256
- // Lists
257
- .replace(/\\begin\{itemize\}/g, '<ul style="margin: 0.5em 0; padding-left: 1.5em;">')
258
- .replace(/\\end\{itemize\}/g, '</ul>')
259
- .replace(/\\begin\{enumerate\}/g, '<ol style="margin: 0.5em 0; padding-left: 1.5em;">')
260
- .replace(/\\end\{enumerate\}/g, '</ol>')
261
- .replace(/\\item\s+/g, '<li style="margin: 0.3em 0;">')
262
-
263
- // Paragraphs and line breaks
264
- .replace(/\\\\/g, '<br />')
265
- .replace(/\\newline/g, '<br />')
266
- .replace(/\\par\b/g, '</p><p>')
267
-
268
- // Clean up extra whitespace
269
- .replace(/\n\n+/g, '</p><p style="margin: 0.5em 0;">')
270
-
271
- // Split by math delimiters to render KaTeX (improved regex for display and inline math)
272
- const parts = processedText.split(/(\\\[[\s\S]*?\\\]|\$\$[\s\S]*?\$\$|\$[^$]+\$|\\\([\s\S]*?\\\))/)
273
- previewRef.current.innerHTML = ''
274
-
275
- parts.forEach(part => {
276
- if (part.startsWith('\\[') || part.startsWith('$$') || part.startsWith('$') || part.startsWith('\\(')) {
277
- // Determine if it's display math or inline math
278
- const isDisplayMath = part.startsWith('\\[') || part.startsWith('$$')
279
-
280
- // Extract the math content
281
- let math = part
282
- if (part.startsWith('\\[') && part.endsWith('\\]')) {
283
- math = part.slice(2, -2)
284
- } else if (part.startsWith('$$') && part.endsWith('$$')) {
285
- math = part.slice(2, -2)
286
- } else if (part.startsWith('$') && part.endsWith('$')) {
287
- math = part.slice(1, -1)
288
- } else if (part.startsWith('\\(') && part.endsWith('\\)')) {
289
- math = part.slice(2, -2)
290
- }
291
-
292
- const span = document.createElement(isDisplayMath ? 'div' : 'span')
293
- if (isDisplayMath) {
294
- span.style.textAlign = 'center'
295
- span.style.margin = '1em 0'
296
- }
297
-
298
- try {
299
- katex.render(math, span, {
300
- throwOnError: false,
301
- displayMode: isDisplayMath,
302
- strict: false
303
- })
304
- previewRef.current?.appendChild(span)
305
- } catch (e) {
306
- // If KaTeX fails, show the raw math
307
- const errorSpan = document.createElement('span')
308
- errorSpan.style.color = '#cc0000'
309
- errorSpan.textContent = part
310
- previewRef.current?.appendChild(errorSpan)
311
- }
312
- } else if (part.trim()) {
313
- // Only add non-empty parts
314
- const div = document.createElement('div')
315
- div.innerHTML = part
316
- div.style.margin = '0.5em 0'
317
- previewRef.current?.appendChild(div)
318
- }
319
- })
320
- } catch (e) {
321
- console.error('Render error:', e)
322
- }
323
- }
324
- }, [code])
325
-
326
- const handleDownload = () => {
327
- const blob = new Blob([code], { type: 'text/plain' })
328
- const url = URL.createObjectURL(blob)
329
- const a = document.createElement('a')
330
- a.href = url
331
- a.download = activeFileName || 'main.tex'
332
- a.click()
333
- URL.revokeObjectURL(url)
334
- }
335
-
336
- return (
337
- <Window
338
- id="latex-editor"
339
- title="LaTeX Studio"
340
- isOpen={true}
341
- onClose={onClose}
342
- onMinimize={onMinimize}
343
- onMaximize={onMaximize}
344
- onFocus={onFocus}
345
- zIndex={zIndex}
346
- width={1200}
347
- height={800}
348
- x={60}
349
- y={60}
350
- className="latex-studio-window"
351
- >
352
- {!isUnlocked ? (
353
- <div className="flex h-full bg-[#1e1e1e] items-center justify-center">
354
- <div className="bg-[#252526] p-8 rounded-lg shadow-xl border border-[#333] max-w-md w-full">
355
- <h2 className="text-xl font-bold text-white mb-4">Enter Passkey</h2>
356
- <p className="text-gray-400 mb-6">Enter your passkey to access LaTeX files</p>
357
- <input
358
- type="password"
359
- value={tempPasskey}
360
- onChange={(e) => setTempPasskey(e.target.value)}
361
- onKeyPress={(e) => e.key === 'Enter' && handleUnlock()}
362
- placeholder="Your passkey"
363
- className="w-full px-4 py-2 bg-[#1e1e1e] border border-[#333] rounded text-white focus:outline-none focus:border-blue-500"
364
- autoFocus
365
- />
366
- <button
367
- onClick={handleUnlock}
368
- disabled={loading}
369
- className="w-full mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-medium disabled:opacity-50"
370
- >
371
- {loading ? 'Loading...' : 'Unlock'}
372
- </button>
373
- </div>
374
- </div>
375
- ) : (
376
- <div className="flex h-full bg-[#1e1e1e] text-gray-300 font-sans">
377
- {/* Sidebar */}
378
- {showSidebar && (
379
- <div className="w-64 bg-[#252526] border-r border-[#333] flex flex-col">
380
- <div className="h-9 px-4 flex items-center text-xs font-bold text-gray-500 uppercase tracking-wider">
381
- Explorer
382
- </div>
383
- <div className="flex-1 overflow-y-auto py-2">
384
- <div className="px-2">
385
- <div className="flex items-center gap-1 py-1 px-2 text-sm text-gray-300 hover:bg-[#2a2d2e] rounded cursor-pointer">
386
- <CaretDown size={12} weight="bold" />
387
- <span className="font-bold">PROJECT</span>
388
- </div>
389
- <div className="pl-4">
390
- {files.map(file => (
391
- <div key={file.id}>
392
- <div className="flex items-center gap-2 py-1 px-2 text-sm hover:bg-[#2a2d2e] rounded cursor-pointer text-blue-400">
393
- <CaretDown size={12} weight="bold" />
394
- {file.name}
395
- </div>
396
- {file.children?.map(child => (
397
- <div
398
- key={child.id}
399
- onClick={() => handleFileClick(child)}
400
- className={`flex items-center gap-2 py-1 px-2 ml-4 text-sm rounded cursor-pointer ${activeFileId === child.id ? 'bg-[#37373d] text-white' : 'text-gray-400 hover:bg-[#2a2d2e]'
401
- }`}
402
- >
403
- <FileText size={14} />
404
- {child.name}
405
- </div>
406
- ))}
407
- </div>
408
- ))}
409
- </div>
410
- </div>
411
- </div>
412
- </div>
413
- )}
414
-
415
- {/* Main Content */}
416
- <div className="flex-1 flex flex-col min-w-0">
417
- {/* Toolbar */}
418
- <div className="h-10 bg-[#2d2d2d] border-b border-[#1e1e1e] flex items-center justify-between px-4">
419
- <div className="flex items-center gap-2">
420
- <button
421
- onClick={() => setShowSidebar(!showSidebar)}
422
- className={`p-1.5 rounded hover:bg-[#3e3e42] ${showSidebar ? 'text-white' : 'text-gray-500'}`}
423
- title="Toggle Sidebar"
424
- >
425
- <SidebarSimple size={16} />
426
- </button>
427
- <div className="h-4 w-[1px] bg-gray-600 mx-2" />
428
- <div className="flex items-center gap-2 px-3 py-1 bg-[#1e1e1e] rounded text-xs text-gray-400 border border-[#333]">
429
- <FileText size={14} className="text-green-400" />
430
- {activeFileName}
431
- </div>
432
- {lastSaved && (
433
- <div className="flex items-center gap-1 text-xs text-gray-500 ml-2">
434
- {isSaving ? (
435
- <span className="text-yellow-500">Saving...</span>
436
- ) : (
437
- <>
438
- <Check size={12} className="text-green-500" />
439
- <span>Saved {lastSaved.toLocaleTimeString()}</span>
440
- </>
441
- )}
442
- </div>
443
- )}
444
- </div>
445
-
446
- <div className="flex items-center gap-2">
447
- <button
448
- className="flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded text-xs font-medium transition-colors"
449
- >
450
- <Play size={14} weight="fill" />
451
- Compile
452
- </button>
453
- <button
454
- onClick={handleDownload}
455
- className="p-1.5 text-gray-400 hover:text-white hover:bg-[#3e3e42] rounded transition-colors"
456
- title="Download PDF"
457
- >
458
- <Download size={16} />
459
- </button>
460
- </div>
461
- </div>
462
-
463
- {/* Split View: Editor & Preview */}
464
- <div className="flex-1 flex overflow-hidden">
465
- {/* Editor */}
466
- <div className="flex-1 border-r border-[#333]">
467
- <Editor
468
- height="100%"
469
- defaultLanguage="latex"
470
- theme="vs-dark"
471
- value={code}
472
- onChange={(value) => setCode(value || '')}
473
- options={{
474
- minimap: { enabled: false },
475
- fontSize: 14,
476
- fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
477
- lineNumbers: 'on',
478
- scrollBeyondLastLine: false,
479
- automaticLayout: true,
480
- padding: { top: 16, bottom: 16 },
481
- renderLineHighlight: 'all',
482
- smoothScrolling: true,
483
- cursorBlinking: 'smooth',
484
- cursorSmoothCaretAnimation: 'on'
485
- }}
486
- />
487
- </div>
488
-
489
- {/* Preview */}
490
- <div className="w-[500px] bg-[#525659] flex flex-col border-l border-[#333]">
491
- <div className="h-8 bg-[#323639] border-b border-[#2b2b2b] flex items-center justify-between px-3 shadow-md z-10">
492
- <span className="text-xs font-bold text-gray-400 uppercase">PDF Preview</span>
493
- <div className="flex items-center gap-2">
494
- <span className="text-xs text-gray-400">100%</span>
495
- </div>
496
- </div>
497
- <div className="flex-1 overflow-y-auto p-8 flex justify-center">
498
- <div
499
- className="bg-white w-full max-w-[800px] min-h-[1000px] shadow-2xl p-12 text-black font-serif"
500
- ref={previewRef}
501
- style={{
502
- boxShadow: '0 0 20px rgba(0,0,0,0.5)'
503
- }}
504
- >
505
- {/* Content rendered via useEffect */}
506
- </div>
507
- </div>
508
- </div>
509
- </div>
510
- </div>
511
- </div>
512
- )}
513
- </Window>
514
- )
515
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
package-lock.json CHANGED
@@ -20,8 +20,8 @@
20
  "framer-motion": "^12.23.24",
21
  "highlight.js": "^11.11.1",
22
  "html-pdf-node": "^1.0.8",
23
- "katex": "^0.16.25",
24
  "latex": "^0.0.1",
 
25
  "lucide-react": "^0.553.0",
26
  "mammoth": "^1.11.0",
27
  "monaco-editor": "^0.54.0",
@@ -2504,6 +2504,12 @@
2504
  "node": ">= 20"
2505
  }
2506
  },
 
 
 
 
 
 
2507
  "node_modules/@phosphor-icons/react": {
2508
  "version": "2.1.10",
2509
  "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
@@ -3926,6 +3932,15 @@
3926
  "node": ">=10.0.0"
3927
  }
3928
  },
 
 
 
 
 
 
 
 
 
3929
  "node_modules/accepts": {
3930
  "version": "2.0.0",
3931
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -5146,6 +5161,16 @@
5146
  "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
5147
  "license": "MIT"
5148
  },
 
 
 
 
 
 
 
 
 
 
5149
  "node_modules/content-disposition": {
5150
  "version": "1.0.0",
5151
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
@@ -5712,6 +5737,69 @@
5712
  "safe-buffer": "^5.0.1"
5713
  }
5714
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5715
  "node_modules/ee-first": {
5716
  "version": "1.1.1",
5717
  "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -7867,6 +7955,28 @@
7867
  "node": ">= 6"
7868
  }
7869
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7870
  "node_modules/iconv-lite": {
7871
  "version": "0.6.3",
7872
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -7909,6 +8019,21 @@
7909
  "node": ">= 4"
7910
  }
7911
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7912
  "node_modules/immediate": {
7913
  "version": "3.0.6",
7914
  "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
@@ -7959,6 +8084,12 @@
7959
  "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
7960
  "license": "ISC"
7961
  },
 
 
 
 
 
 
7962
  "node_modules/inline-css": {
7963
  "version": "3.0.0",
7964
  "resolved": "https://registry.npmjs.org/inline-css/-/inline-css-3.0.0.tgz",
@@ -8564,6 +8695,70 @@
8564
  "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==",
8565
  "license": "MIT"
8566
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8567
  "node_modules/js-tokens": {
8568
  "version": "4.0.0",
8569
  "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -8693,22 +8888,6 @@
8693
  "safe-buffer": "^5.0.1"
8694
  }
8695
  },
8696
- "node_modules/katex": {
8697
- "version": "0.16.25",
8698
- "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz",
8699
- "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==",
8700
- "funding": [
8701
- "https://opencollective.com/katex",
8702
- "https://github.com/sponsors/katex"
8703
- ],
8704
- "license": "MIT",
8705
- "dependencies": {
8706
- "commander": "^8.3.0"
8707
- },
8708
- "bin": {
8709
- "katex": "cli.js"
8710
- }
8711
- },
8712
  "node_modules/keyv": {
8713
  "version": "4.5.4",
8714
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -8750,6 +8929,53 @@
8750
  "through": "~2.1.0"
8751
  }
8752
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8753
  "node_modules/lazystream": {
8754
  "version": "1.0.1",
8755
  "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
@@ -10627,6 +10853,21 @@
10627
  "dev": true,
10628
  "license": "MIT"
10629
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10630
  "node_modules/normalize-path": {
10631
  "version": "3.0.0",
10632
  "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -11556,6 +11797,12 @@
11556
  "url": "https://github.com/sponsors/wooorm"
11557
  }
11558
  },
 
 
 
 
 
 
11559
  "node_modules/proxy-addr": {
11560
  "version": "2.0.7",
11561
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -11778,6 +12025,15 @@
11778
  "url": "https://github.com/sponsors/ljharb"
11779
  }
11780
  },
 
 
 
 
 
 
 
 
 
11781
  "node_modules/queue-microtask": {
11782
  "version": "1.2.3",
11783
  "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -12883,6 +13139,11 @@
12883
  "node": ">= 0.8"
12884
  }
12885
  },
 
 
 
 
 
12886
  "node_modules/stop-iteration-iterator": {
12887
  "version": "1.1.0",
12888
  "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -13211,6 +13472,21 @@
13211
  "url": "https://github.com/sponsors/ljharb"
13212
  }
13213
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13214
  "node_modules/tailwindcss": {
13215
  "version": "4.1.17",
13216
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
@@ -13883,6 +14159,15 @@
13883
  "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
13884
  "license": "ISC"
13885
  },
 
 
 
 
 
 
 
 
 
13886
  "node_modules/unpipe": {
13887
  "version": "1.0.0",
13888
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
 
20
  "framer-motion": "^12.23.24",
21
  "highlight.js": "^11.11.1",
22
  "html-pdf-node": "^1.0.8",
 
23
  "latex": "^0.0.1",
24
+ "latex.js": "^0.12.6",
25
  "lucide-react": "^0.553.0",
26
  "mammoth": "^1.11.0",
27
  "monaco-editor": "^0.54.0",
 
2504
  "node": ">= 20"
2505
  }
2506
  },
2507
+ "node_modules/@one-ini/wasm": {
2508
+ "version": "0.1.1",
2509
+ "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
2510
+ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
2511
+ "license": "MIT"
2512
+ },
2513
  "node_modules/@phosphor-icons/react": {
2514
  "version": "2.1.10",
2515
  "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
 
3932
  "node": ">=10.0.0"
3933
  }
3934
  },
3935
+ "node_modules/abbrev": {
3936
+ "version": "2.0.0",
3937
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
3938
+ "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
3939
+ "license": "ISC",
3940
+ "engines": {
3941
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
3942
+ }
3943
+ },
3944
  "node_modules/accepts": {
3945
  "version": "2.0.0",
3946
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
 
5161
  "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
5162
  "license": "MIT"
5163
  },
5164
+ "node_modules/config-chain": {
5165
+ "version": "1.1.13",
5166
+ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
5167
+ "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
5168
+ "license": "MIT",
5169
+ "dependencies": {
5170
+ "ini": "^1.3.4",
5171
+ "proto-list": "~1.2.1"
5172
+ }
5173
+ },
5174
  "node_modules/content-disposition": {
5175
  "version": "1.0.0",
5176
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
 
5737
  "safe-buffer": "^5.0.1"
5738
  }
5739
  },
5740
+ "node_modules/editorconfig": {
5741
+ "version": "1.0.4",
5742
+ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
5743
+ "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
5744
+ "license": "MIT",
5745
+ "dependencies": {
5746
+ "@one-ini/wasm": "0.1.1",
5747
+ "commander": "^10.0.0",
5748
+ "minimatch": "9.0.1",
5749
+ "semver": "^7.5.3"
5750
+ },
5751
+ "bin": {
5752
+ "editorconfig": "bin/editorconfig"
5753
+ },
5754
+ "engines": {
5755
+ "node": ">=14"
5756
+ }
5757
+ },
5758
+ "node_modules/editorconfig/node_modules/brace-expansion": {
5759
+ "version": "2.0.2",
5760
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
5761
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
5762
+ "license": "MIT",
5763
+ "dependencies": {
5764
+ "balanced-match": "^1.0.0"
5765
+ }
5766
+ },
5767
+ "node_modules/editorconfig/node_modules/commander": {
5768
+ "version": "10.0.1",
5769
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
5770
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
5771
+ "license": "MIT",
5772
+ "engines": {
5773
+ "node": ">=14"
5774
+ }
5775
+ },
5776
+ "node_modules/editorconfig/node_modules/minimatch": {
5777
+ "version": "9.0.1",
5778
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
5779
+ "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
5780
+ "license": "ISC",
5781
+ "dependencies": {
5782
+ "brace-expansion": "^2.0.1"
5783
+ },
5784
+ "engines": {
5785
+ "node": ">=16 || 14 >=14.17"
5786
+ },
5787
+ "funding": {
5788
+ "url": "https://github.com/sponsors/isaacs"
5789
+ }
5790
+ },
5791
+ "node_modules/editorconfig/node_modules/semver": {
5792
+ "version": "7.7.3",
5793
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
5794
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
5795
+ "license": "ISC",
5796
+ "bin": {
5797
+ "semver": "bin/semver.js"
5798
+ },
5799
+ "engines": {
5800
+ "node": ">=10"
5801
+ }
5802
+ },
5803
  "node_modules/ee-first": {
5804
  "version": "1.1.1",
5805
  "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
 
7955
  "node": ">= 6"
7956
  }
7957
  },
7958
+ "node_modules/hyphenation.de": {
7959
+ "version": "0.2.1",
7960
+ "resolved": "https://registry.npmjs.org/hyphenation.de/-/hyphenation.de-0.2.1.tgz",
7961
+ "integrity": "sha512-s6Y4TFA8xWjRLneOPI6HV/+wzfm2c2yurTvFaXlznmsbeI6waZhMpxu94fSXGNGsrPxrzI1zTtYDEWeEeaANnw==",
7962
+ "dependencies": {
7963
+ "hypher": "*"
7964
+ }
7965
+ },
7966
+ "node_modules/hyphenation.en-us": {
7967
+ "version": "0.2.1",
7968
+ "resolved": "https://registry.npmjs.org/hyphenation.en-us/-/hyphenation.en-us-0.2.1.tgz",
7969
+ "integrity": "sha512-ItXYgvIpfN8rfXl/GTBQC7DsSb5PPsKh9gGzViK/iWzCS5mvjDebFJ6xCcIYo8dal+nSp2rUzvTT7BosrKlL8A==",
7970
+ "dependencies": {
7971
+ "hypher": "*"
7972
+ }
7973
+ },
7974
+ "node_modules/hypher": {
7975
+ "version": "0.2.5",
7976
+ "resolved": "https://registry.npmjs.org/hypher/-/hypher-0.2.5.tgz",
7977
+ "integrity": "sha512-kUTpuyzBWWDO2VakmjHC/cxesg4lKQP+Fdc+7lrK4yvjNjkV9vm5UTZMDAwOyyHTOpbkYrAMlNZHG61NnE9vYQ==",
7978
+ "license": "BSD-3-Clause"
7979
+ },
7980
  "node_modules/iconv-lite": {
7981
  "version": "0.6.3",
7982
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 
8019
  "node": ">= 4"
8020
  }
8021
  },
8022
+ "node_modules/image-size": {
8023
+ "version": "1.2.1",
8024
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
8025
+ "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
8026
+ "license": "MIT",
8027
+ "dependencies": {
8028
+ "queue": "6.0.2"
8029
+ },
8030
+ "bin": {
8031
+ "image-size": "bin/image-size.js"
8032
+ },
8033
+ "engines": {
8034
+ "node": ">=16.x"
8035
+ }
8036
+ },
8037
  "node_modules/immediate": {
8038
  "version": "3.0.6",
8039
  "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
 
8084
  "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
8085
  "license": "ISC"
8086
  },
8087
+ "node_modules/ini": {
8088
+ "version": "1.3.8",
8089
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
8090
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
8091
+ "license": "ISC"
8092
+ },
8093
  "node_modules/inline-css": {
8094
  "version": "3.0.0",
8095
  "resolved": "https://registry.npmjs.org/inline-css/-/inline-css-3.0.0.tgz",
 
8695
  "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==",
8696
  "license": "MIT"
8697
  },
8698
+ "node_modules/js-beautify": {
8699
+ "version": "1.14.11",
8700
+ "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz",
8701
+ "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==",
8702
+ "license": "MIT",
8703
+ "dependencies": {
8704
+ "config-chain": "^1.1.13",
8705
+ "editorconfig": "^1.0.3",
8706
+ "glob": "^10.3.3",
8707
+ "nopt": "^7.2.0"
8708
+ },
8709
+ "bin": {
8710
+ "css-beautify": "js/bin/css-beautify.js",
8711
+ "html-beautify": "js/bin/html-beautify.js",
8712
+ "js-beautify": "js/bin/js-beautify.js"
8713
+ },
8714
+ "engines": {
8715
+ "node": ">=14"
8716
+ }
8717
+ },
8718
+ "node_modules/js-beautify/node_modules/brace-expansion": {
8719
+ "version": "2.0.2",
8720
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
8721
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
8722
+ "license": "MIT",
8723
+ "dependencies": {
8724
+ "balanced-match": "^1.0.0"
8725
+ }
8726
+ },
8727
+ "node_modules/js-beautify/node_modules/glob": {
8728
+ "version": "10.5.0",
8729
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
8730
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
8731
+ "license": "ISC",
8732
+ "dependencies": {
8733
+ "foreground-child": "^3.1.0",
8734
+ "jackspeak": "^3.1.2",
8735
+ "minimatch": "^9.0.4",
8736
+ "minipass": "^7.1.2",
8737
+ "package-json-from-dist": "^1.0.0",
8738
+ "path-scurry": "^1.11.1"
8739
+ },
8740
+ "bin": {
8741
+ "glob": "dist/esm/bin.mjs"
8742
+ },
8743
+ "funding": {
8744
+ "url": "https://github.com/sponsors/isaacs"
8745
+ }
8746
+ },
8747
+ "node_modules/js-beautify/node_modules/minimatch": {
8748
+ "version": "9.0.5",
8749
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
8750
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
8751
+ "license": "ISC",
8752
+ "dependencies": {
8753
+ "brace-expansion": "^2.0.1"
8754
+ },
8755
+ "engines": {
8756
+ "node": ">=16 || 14 >=14.17"
8757
+ },
8758
+ "funding": {
8759
+ "url": "https://github.com/sponsors/isaacs"
8760
+ }
8761
+ },
8762
  "node_modules/js-tokens": {
8763
  "version": "4.0.0",
8764
  "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
 
8888
  "safe-buffer": "^5.0.1"
8889
  }
8890
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8891
  "node_modules/keyv": {
8892
  "version": "4.5.4",
8893
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
 
8929
  "through": "~2.1.0"
8930
  }
8931
  },
8932
+ "node_modules/latex.js": {
8933
+ "version": "0.12.6",
8934
+ "resolved": "https://registry.npmjs.org/latex.js/-/latex.js-0.12.6.tgz",
8935
+ "integrity": "sha512-spMTeSq9cP4vidMQuPgoYGKmsQTMElZhDtNF3NyLIRc03XkuuPvMA0tzb0ShS/otaIJrB7DIHDk0GL3knCisEw==",
8936
+ "license": "MIT",
8937
+ "dependencies": {
8938
+ "commander": "8.x",
8939
+ "fs-extra": "10.x",
8940
+ "hyphenation.de": "*",
8941
+ "hyphenation.en-us": "*",
8942
+ "js-beautify": "1.14.x",
8943
+ "stdin": "*",
8944
+ "svgdom": "^0.1.8"
8945
+ },
8946
+ "bin": {
8947
+ "latex.js": "bin/latex.js"
8948
+ },
8949
+ "engines": {
8950
+ "node": ">= 14.0"
8951
+ }
8952
+ },
8953
+ "node_modules/latex.js/node_modules/fs-extra": {
8954
+ "version": "10.1.0",
8955
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
8956
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
8957
+ "license": "MIT",
8958
+ "dependencies": {
8959
+ "graceful-fs": "^4.2.0",
8960
+ "jsonfile": "^6.0.1",
8961
+ "universalify": "^2.0.0"
8962
+ },
8963
+ "engines": {
8964
+ "node": ">=12"
8965
+ }
8966
+ },
8967
+ "node_modules/latex.js/node_modules/jsonfile": {
8968
+ "version": "6.2.0",
8969
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
8970
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
8971
+ "license": "MIT",
8972
+ "dependencies": {
8973
+ "universalify": "^2.0.0"
8974
+ },
8975
+ "optionalDependencies": {
8976
+ "graceful-fs": "^4.1.6"
8977
+ }
8978
+ },
8979
  "node_modules/lazystream": {
8980
  "version": "1.0.1",
8981
  "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
 
10853
  "dev": true,
10854
  "license": "MIT"
10855
  },
10856
+ "node_modules/nopt": {
10857
+ "version": "7.2.1",
10858
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
10859
+ "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
10860
+ "license": "ISC",
10861
+ "dependencies": {
10862
+ "abbrev": "^2.0.0"
10863
+ },
10864
+ "bin": {
10865
+ "nopt": "bin/nopt.js"
10866
+ },
10867
+ "engines": {
10868
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
10869
+ }
10870
+ },
10871
  "node_modules/normalize-path": {
10872
  "version": "3.0.0",
10873
  "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
 
11797
  "url": "https://github.com/sponsors/wooorm"
11798
  }
11799
  },
11800
+ "node_modules/proto-list": {
11801
+ "version": "1.2.4",
11802
+ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
11803
+ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
11804
+ "license": "ISC"
11805
+ },
11806
  "node_modules/proxy-addr": {
11807
  "version": "2.0.7",
11808
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
 
12025
  "url": "https://github.com/sponsors/ljharb"
12026
  }
12027
  },
12028
+ "node_modules/queue": {
12029
+ "version": "6.0.2",
12030
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
12031
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
12032
+ "license": "MIT",
12033
+ "dependencies": {
12034
+ "inherits": "~2.0.3"
12035
+ }
12036
+ },
12037
  "node_modules/queue-microtask": {
12038
  "version": "1.2.3",
12039
  "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
 
13139
  "node": ">= 0.8"
13140
  }
13141
  },
13142
+ "node_modules/stdin": {
13143
+ "version": "0.0.1",
13144
+ "resolved": "https://registry.npmjs.org/stdin/-/stdin-0.0.1.tgz",
13145
+ "integrity": "sha512-2bacd1TXzqOEsqRa+eEWkRdOSznwptrs4gqFcpMq5tOtmJUGPZd10W5Lam6wQ4YQ/+qjQt4e9u35yXCF6mrlfQ=="
13146
+ },
13147
  "node_modules/stop-iteration-iterator": {
13148
  "version": "1.1.0",
13149
  "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
 
13472
  "url": "https://github.com/sponsors/ljharb"
13473
  }
13474
  },
13475
+ "node_modules/svgdom": {
13476
+ "version": "0.1.22",
13477
+ "resolved": "https://registry.npmjs.org/svgdom/-/svgdom-0.1.22.tgz",
13478
+ "integrity": "sha512-NPf43Dha2ocSjgyVSDWrKuY5XfV6+nngTzeVypRFNj+OjKUNwWr4eWx9IrWQ8wXdaHguOTHvzEji0v46z0iwKQ==",
13479
+ "license": "MIT",
13480
+ "dependencies": {
13481
+ "fontkit": "^2.0.4",
13482
+ "image-size": "^1.2.1",
13483
+ "sax": "^1.4.1"
13484
+ },
13485
+ "funding": {
13486
+ "type": "github",
13487
+ "url": "https://github.com/sponsors/Fuzzyma"
13488
+ }
13489
+ },
13490
  "node_modules/tailwindcss": {
13491
  "version": "4.1.17",
13492
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
 
14159
  "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
14160
  "license": "ISC"
14161
  },
14162
+ "node_modules/universalify": {
14163
+ "version": "2.0.1",
14164
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
14165
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
14166
+ "license": "MIT",
14167
+ "engines": {
14168
+ "node": ">= 10.0.0"
14169
+ }
14170
+ },
14171
  "node_modules/unpipe": {
14172
  "version": "1.0.0",
14173
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
package.json CHANGED
@@ -24,8 +24,8 @@
24
  "framer-motion": "^12.23.24",
25
  "highlight.js": "^11.11.1",
26
  "html-pdf-node": "^1.0.8",
27
- "katex": "^0.16.25",
28
  "latex": "^0.0.1",
 
29
  "lucide-react": "^0.553.0",
30
  "mammoth": "^1.11.0",
31
  "monaco-editor": "^0.54.0",
 
24
  "framer-motion": "^12.23.24",
25
  "highlight.js": "^11.11.1",
26
  "html-pdf-node": "^1.0.8",
 
27
  "latex": "^0.0.1",
28
+ "latex.js": "^0.12.6",
29
  "lucide-react": "^0.553.0",
30
  "mammoth": "^1.11.0",
31
  "monaco-editor": "^0.54.0",
public/data/reuben/main.tex ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \documentclass{article}
2
+ \usepackage{amsmath}
3
+ \title{My LaTeX Document}
4
+ \author{Reuben OS}
5
+ \date{\today}
6
+
7
+ \begin{document}
8
+
9
+ \maketitle
10
+
11
+ \section{Introduction}
12
+ Welcome to LaTeX Studio on Reuben OS.
13
+
14
+ \section{Mathematics}
15
+ Here is an equation:
16
+ \[ E = mc^2 \]
17
+
18
+ \end{document}