Ericsonv12 commited on
Commit
9e7159f
·
verified ·
1 Parent(s): bf070d2

Upload 36 files

Browse files

acabei de adicionar os arquivos para paginação do editor

Files changed (36) hide show
  1. Document-Editor--master/.gitignore +2 -0
  2. Document-Editor--master/AI_USAGE.md +86 -0
  3. Document-Editor--master/ARCHITECTURE.md +272 -0
  4. Document-Editor--master/README.md +246 -0
  5. Document-Editor--master/client/.gitignore +23 -0
  6. Document-Editor--master/client/README.md +46 -0
  7. Document-Editor--master/client/package-lock.json +0 -0
  8. Document-Editor--master/client/package.json +44 -0
  9. Document-Editor--master/client/public/favicon.ico +0 -0
  10. Document-Editor--master/client/public/index.html +43 -0
  11. Document-Editor--master/client/public/logo192.png +0 -0
  12. Document-Editor--master/client/public/logo512.png +0 -0
  13. Document-Editor--master/client/public/manifest.json +25 -0
  14. Document-Editor--master/client/public/robots.txt +3 -0
  15. Document-Editor--master/client/src/App.css +163 -0
  16. Document-Editor--master/client/src/App.test.tsx +9 -0
  17. Document-Editor--master/client/src/App.tsx +611 -0
  18. Document-Editor--master/client/src/index.css +13 -0
  19. Document-Editor--master/client/src/index.tsx +19 -0
  20. Document-Editor--master/client/src/logo.svg +1 -0
  21. Document-Editor--master/client/src/paginationEngine.ts +77 -0
  22. Document-Editor--master/client/src/react-app-env.d.ts +1 -0
  23. Document-Editor--master/client/src/reportWebVitals.ts +15 -0
  24. Document-Editor--master/client/src/setupTests.ts +5 -0
  25. Document-Editor--master/client/src/textMeasurer.ts +68 -0
  26. Document-Editor--master/client/src/types.ts +35 -0
  27. Document-Editor--master/client/tsconfig.json +26 -0
  28. Document-Editor--master/package.json +10 -0
  29. Document-Editor--master/server/data/040e38fc-f5ff-448b-a38e-528b77d62c7c.json +192 -0
  30. Document-Editor--master/server/data/71b8d791-d7b2-44e3-bf5b-bfa4ca3a34de.json +72 -0
  31. Document-Editor--master/server/data/92a85bb2-938a-41b0-9c69-1c36919da23c.json +32 -0
  32. Document-Editor--master/server/data/d8b5e4ae-e7c1-4d7b-a70d-45f7e9d5f2d8.json +20 -0
  33. Document-Editor--master/server/index.js +61 -0
  34. Document-Editor--master/server/package-lock.json +851 -0
  35. Document-Editor--master/server/package.json +18 -0
  36. Document-Editor--master/server/tempCodeRunnerFile.js +3 -0
Document-Editor--master/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /node_modules
2
+ /server/node_modules
Document-Editor--master/AI_USAGE.md ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI Usage Documentation
2
+
3
+ This document details how AI tools (specifically Claude by Anthropic) were used throughout the development of this document editor, the AI tools were relied on for codegen and documentation.
4
+
5
+ ## AI Tool Used
6
+
7
+ **Primary Tool**: Claude (Anthropic AI Assistant)
8
+
9
+ ## Development Phases
10
+
11
+ NOTE: AI was used to generate but much of the refactoring, debugging and testing was mannual and has not been mentioned in this document. Many features and improvements were attempted and discarded due to the time constraint and those have not been mentioned in this file as well. Only the bits used are written here.
12
+
13
+ ### Phase 1: Initial Architecture & Pagination
14
+
15
+ **Task**: Design and implement basic pagination system
16
+
17
+ **AI Contribution**:
18
+ Gave a useable skeleton schema for types.ts adn textMeasurer.ts which was reviewed and updated along the project
19
+
20
+ **Key Prompts**:
21
+ ```
22
+ I am building a document editor with pagination, give me the initial static requirement files which will be used as a base throughout the project and will hold the truth about the content that the user inputs for consistency. This is for a standard A4 size document assume any general case for any missing information that you may need.
23
+ ```
24
+
25
+ **Code Generated**:
26
+ - `paginationEngine.ts` (initial version)
27
+ - `textMeasurer.ts` (initial version)
28
+ - `types.ts` (ContentBlock, Page, Line interfaces)
29
+
30
+ ### Phase 2: Bug Fixes - Pagination Overlapping Issues
31
+ (These errors were faced after the data structure archtecture was changed to achieve a better approach which is also closer to industry standard)
32
+
33
+ **Task**: Fix content overlapping across pages and improve React key handling as there are contenboxes with similar key and they are overlapping when the content over them reflows instead of rearranging themselves.
34
+
35
+ **AI Contribution**:
36
+ - Implemented unique key generation per segment
37
+ - Added block height calculation based on actual line count
38
+ - Improved auto-split logic for blocks spanning pages
39
+
40
+
41
+ **Code Generated**:
42
+ - Unique key generation: `page-${pageNumber}-block-${blockId}-instance-${count}`
43
+ - Auto-split useEffect (lines 25-76 in initial version)
44
+ - Block height calculation logic
45
+ - willBlockOverflowPage() and splitOverflowingBlock() functions
46
+
47
+
48
+ ### Phase 3: Major Refactor - Data Model Separation
49
+
50
+ **Task**: Separate data model from page rendering layer to fix selection issues
51
+
52
+ **AI Contribution**:
53
+ - Designed new segment-based architecture
54
+ - Removed all block-splitting logic
55
+ - Implemented BlockSegment interface
56
+ - Updated PaginationEngine to generate layout metadata
57
+ - Rewrote rendering to use segments
58
+
59
+ **Key Prompts**:
60
+ ```
61
+ "Refactor to separate data model from page rendering layer. Keep text blocks
62
+ intact - only break on user intent (Enter key). Create separate page layout
63
+ layer with metadata."
64
+ ```
65
+
66
+ **Code Generated**:
67
+ - New `BlockSegment` interface with startOffset/endOffset
68
+ - Complete rewrite of PaginationEngine.paginate()
69
+ - Removed willBlockOverflowPage() and splitOverflowingBlock()
70
+ - Simplified handleInput() to not check for overflow
71
+ - New segment-based rendering logic
72
+
73
+
74
+ ### Phase 4: Feature Additions
75
+
76
+ **Task**: Add clear document button and improve UX
77
+
78
+ **AI Contribution**:
79
+ - Implemented handleClear function with confirmation dialog
80
+ - Added red-styled clear button to UI
81
+ - Added user-select CSS for cross-block selection
82
+
83
+ **Code Generated**:
84
+ - handleClear() function with window.confirm
85
+ - Clear button in controls with custom styling
86
+ - userSelect CSS properties for all browsers
Document-Editor--master/ARCHITECTURE.md ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Architecture Documentation
2
+
3
+ ## Editor State Model
4
+
5
+ ### State Hierarchy
6
+
7
+ ```
8
+ App Component (Root)
9
+ ├─ blocks: ContentBlock[] // Source of truth for content
10
+ ├─ pages: Page[] // Derived state from pagination
11
+ ├─ docId: string // Current document ID
12
+ ├─ cursorPositionRef: Map<blockId, absoluteOffset> // Cursor tracking
13
+ └─ isRestoringCursorRef: boolean // Race condition guard
14
+ ```
15
+
16
+ ### State Flow
17
+
18
+ ```
19
+ User Input → handleInput() → Update blocks[] → Trigger Pagination
20
+
21
+ pages[] calculated
22
+
23
+ React re-renders
24
+
25
+ Cursor restoration
26
+ ```
27
+
28
+ ### ContentBlock (Data Model)
29
+
30
+ **Philosophy**: Blocks represent *semantic* content units (paragraphs), not visual layout.
31
+
32
+ ```typescript
33
+ interface ContentBlock {
34
+ id: string; // "block-1701234567890-0.123456"
35
+ text: string; // Full paragraph text (unbounded length)
36
+ }
37
+ ```
38
+
39
+ **Lifecycle**:
40
+ - Created: User presses Enter (splits current block)
41
+ - Modified: User types (text updates)
42
+ - Deleted: User backspaces at block start (merges with previous)
43
+ - **Never split due to pagination**
44
+
45
+ ### BlockSegment (Rendering Model)
46
+
47
+ **Philosophy**: Segments are *ephemeral* or complete views of block data for rendering.
48
+
49
+ ```typescript
50
+ interface BlockSegment {
51
+ blockId: string; // References ContentBlock
52
+ startOffset: number; // Character position in block.text
53
+ endOffset: number; // Character position in block.text
54
+ startLine: number; // Line index within block
55
+ endLine: number; // Line index within block
56
+ y: number; // Absolute Y on page
57
+ height: number; // Pixel height
58
+ lines: string[]; // Actual visible text
59
+ }
60
+ ```
61
+
62
+ **Properties**:
63
+ - Calculated during pagination (not stored)
64
+ - Multiple segments can reference same block
65
+ - Each segment renders only its portion of block text
66
+ - Segments on different pages have different offsets
67
+
68
+ ## Pagination Algorithm
69
+
70
+ The `PaginationEngine` performs pagination by processing content blocks sequentially, breaking each block's text into lines that fit the page width using `TextMeasurer`. It tracks the current vertical position (`currentY`) and places lines on the active page until reaching the bottom margin. When a line won't fit, it finalizes the current page, creates a new one, and continues from the top margin. Each block is split into segments that record character offsets and line boundaries, allowing the engine to track exactly which portion of each block appears on each page.
71
+
72
+ ### Input & Output
73
+
74
+ **Input**: `ContentBlock[]` (array of paragraphs)
75
+ **Output**: `Page[]` (array of pages with segments)
76
+
77
+ ### Algorithm Steps
78
+
79
+ #### Phase 1: Text Measurement
80
+
81
+ 1. Canvas is used for Measuring text with it's inbuilt function ctx.measureText(text),
82
+ 2. This phase has:
83
+ Word-wrapping algorithm
84
+ Preserves whitespace and newlines
85
+ Returns array of line strings
86
+
87
+ #### Phase 2: Line Breaking
88
+
89
+ For each block:
90
+ 1. Split text by explicit newlines (`\n`)
91
+ 2. For each paragraph:
92
+ - Split into words (preserving spaces)
93
+ - Build lines until width exceeds `maxWidth`
94
+ - Track character offsets for each line
95
+
96
+
97
+ #### Phase 3: Page Layout
98
+
99
+ ```javascript
100
+ let currentY = PAGE_CONFIG.marginTop;
101
+ let currentPage = { pageNumber: 1, segments: [] };
102
+
103
+ for (const block of blocks) {
104
+ const lines = measurer.breakIntoLines(block.text, contentWidth);
105
+
106
+ let segmentLines = [];
107
+ let segmentStartLine = 0;
108
+
109
+ for (let i = 0; i < lines.length; i++) {
110
+ if (currentY + lineHeight > pageBottom) {
111
+ // Finalize segment
112
+ currentPage.segments.push({
113
+ blockId: block.id,
114
+ startOffset: lineOffsets[segmentStartLine].start,
115
+ endOffset: lineOffsets[i-1].end,
116
+ lines: segmentLines,
117
+ y: segmentStartY,
118
+ height: (i - segmentStartLine) * lineHeight
119
+ });
120
+
121
+ // New page
122
+ pages.push(currentPage);
123
+ currentPage = { pageNumber: pages.length + 1, segments: [] };
124
+ currentY = PAGE_CONFIG.marginTop;
125
+
126
+ // Reset segment tracking
127
+ segmentLines = [];
128
+ segmentStartLine = i;
129
+ }
130
+
131
+ segmentLines.push(lines[i]);
132
+ currentY += lineHeight;
133
+ }
134
+
135
+ // Finalize last segment of block
136
+ currentPage.segments.push({ /* ... */ });
137
+ }
138
+ ```
139
+
140
+ ### Edge Cases Handled
141
+
142
+ 1. **Empty blocks**: Rendered as single blank line
143
+ 2. **Very long words**: Break at character boundary if no spaces
144
+ 3. **Multi-page blocks**: Single block can span 10+ pages
145
+
146
+ ## Rendering Strategy
147
+
148
+ ### Segment Rendering
149
+
150
+ Each segment is an independent `contentEditable` div:
151
+
152
+ **Critical**: Renders `segment.lines`, NOT full `block.text`
153
+
154
+
155
+ ### Cursor Persistence
156
+
157
+ **Challenge**: React re-renders destroy DOM elements, losing cursor position.
158
+
159
+ **Solution**: Three-phase restoration
160
+
161
+ ```javascript
162
+ // Phase 1: Save (during input)
163
+ const cursorOffset = getCursorOffset(element);
164
+ const absoluteOffset = segmentStart + cursorOffset;
165
+ cursorPositionRef.current.set(blockId, absoluteOffset);
166
+
167
+ // Phase 2: Re-render
168
+ // React updates DOM, cursor position lost
169
+
170
+ // Phase 3: Restore (after render)
171
+
172
+ ```
173
+
174
+ **Race Condition Prevention**:
175
+ - `isRestoringCursorRef`: Guards against concurrent restorations
176
+ - Input blocking during restoration
177
+ - Cleared cursor map after each restoration
178
+
179
+ ## Persistence Flow
180
+
181
+ ### Save Flow
182
+
183
+ ```
184
+ User clicks "Save"
185
+
186
+ POST /documents { title, content: blocks[] }
187
+
188
+ Server generates UUID
189
+
190
+ Server writes JSON file: data/{uuid}.json
191
+
192
+ Server returns { id: uuid }
193
+
194
+ Client displays document ID
195
+ ```
196
+
197
+ ### Load Flow
198
+
199
+ ```
200
+ User enters document ID
201
+
202
+ GET /documents/{id}
203
+
204
+ Server reads data/{id}.json
205
+
206
+ Server returns { id, title, content, created_at, updated_at }
207
+
208
+ Client: setBlocks(response.content)
209
+
210
+ Pagination recalculates
211
+
212
+ Editor displays loaded content
213
+ ```
214
+
215
+ ### Storage Format
216
+
217
+ File: `server/data/71b8d791-d7b2-44e3-bf5b-bfa4ca3a34de.json`
218
+
219
+ ```json
220
+ {
221
+ "id": "71b8d791-d7b2-44e3-bf5b-bfa4ca3a34de",
222
+ "title": "My Document",
223
+ "content": [
224
+ {
225
+ "id": "block-1701234567890-0.123",
226
+ "text": "First paragraph with potentially very long text..."
227
+ },
228
+ {
229
+ "id": "block-1701234567891-0.456",
230
+ "text": "Second paragraph..."
231
+ }
232
+ ],
233
+ "created_at": "2024-11-26T10:30:00.000Z",
234
+ "updated_at": "2024-11-26T10:30:00.000Z"
235
+ }
236
+ ```
237
+
238
+ **Benefits**:
239
+ - Human-readable
240
+ - Version control friendly
241
+ - Easy debugging
242
+ - Simple backup/restore
243
+
244
+ **Trade-offs**:
245
+ - Not suitable for large-scale production
246
+ - No concurrent write protection
247
+ - File I/O overhead
248
+
249
+
250
+ **Current Status**: Optimized for documents up to 50-100 pages. Beyond that, consider implementing above optimizations.
251
+
252
+ ## Design Decisions
253
+
254
+
255
+ ### Why Canvas for Text Measurement?
256
+
257
+
258
+ **Canvas API Benefits**:
259
+ - Pixel-perfect accuracy
260
+ - No DOM manipulation
261
+ - Fast (native code)
262
+ - Matches actual rendering
263
+
264
+ ### Why File-Based Storage?
265
+
266
+ **Decision**: Simplicity for prototype/demo
267
+ - Fast to implement
268
+ - Zero configuration
269
+ - Easy to inspect
270
+ - Human-readable
271
+
272
+ **Production Alternative**: PostgreSQL for block content
Document-Editor--master/README.md ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Document Editor - Multi-Page Text Editor with Real-Time Pagination
2
+
3
+ Try It out Here: https://document-editor-qqg3-r8zfcn2ag.vercel.app/
4
+
5
+ A sophisticated document editor with automatic pagination, multi-block editing, and persistent storage. Text flows naturally across pages with real-time layout calculation.
6
+
7
+ ## Prerequisites
8
+
9
+ - Node.js 16+ and npm
10
+ - Modern browser (Chrome, Firefox, Safari, Edge)
11
+
12
+ ## Quick Start
13
+
14
+ ```bash
15
+ # Install dependencies
16
+ cd server && npm install express
17
+ cd ../client && npm install
18
+
19
+ # Start backend (Terminal 1)
20
+ cd server && node index.js
21
+
22
+ # Start frontend (Terminal 2)
23
+ cd client && npm start
24
+ ```
25
+
26
+ The editor will open at `http://localhost:3000` with the backend API at `http://localhost:3001`.
27
+
28
+ ## Project Structure
29
+
30
+ ```
31
+ Document-Editor-/
32
+ ├── client/ # React TypeScript frontend
33
+ │ ├── src/
34
+ │ │ ├── App.tsx # Main editor component
35
+ │ │ ├── paginationEngine.ts # Pagination algorithm
36
+ │ │ ├── textMeasurer.ts # Text measurement utilities
37
+ │ │ ├── types.ts # TypeScript interfaces
38
+ │ │ └── App.css # Styling
39
+ │ └── package.json
40
+ ├── server/ # Express.js backend
41
+ │ ├── index.js # API server
42
+ │ ├── data/ # Document storage (JSON files)
43
+ │ └── package.json
44
+ ├── README.md
45
+ ├── ARCHITECTURE.md
46
+ └── AI_USAGE.md
47
+ ```
48
+
49
+ ## Features
50
+
51
+ ### Core Functionality
52
+ - ✅ Multi-page document editing with automatic pagination
53
+ - ✅ Real-time text reflow across pages
54
+ - ✅ Cursor persistence across edits and page boundaries
55
+ - ✅ Multi-block selection and deletion
56
+ - ✅ Block-based content model (paragraphs remain intact)
57
+ - ✅ Save/Load documents with unique IDs
58
+ - ✅ Clear document functionality
59
+
60
+ ### Technical Highlights
61
+ - **Segment-Based Rendering**: Blocks can span pages without splitting in data model
62
+ - **Accurate Text Measurement**: Canvas-based character width calculation
63
+ - **Cursor Restoration**: Complex logic to maintain cursor position across re-renders
64
+ - **Race Condition Prevention**: Guards against concurrent cursor restoration
65
+ - **Keyboard Navigation**: Arrow keys, Enter, Backspace work across pages
66
+
67
+ ## API Endpoints
68
+
69
+ ### POST /documents
70
+ Create and save a new document.
71
+
72
+ **Request:**
73
+ ```json
74
+ {
75
+ "title": "My Document",
76
+ "content": [
77
+ { "id": "block-1", "text": "First paragraph..." },
78
+ { "id": "block-2", "text": "Second paragraph..." }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ **Response:**
84
+ ```json
85
+ {
86
+ "id": "71b8d791-d7b2-44e3-bf5b-bfa4ca3a34de"
87
+ }
88
+ ```
89
+
90
+ ### GET /documents/:id
91
+ Retrieve a saved document by ID.
92
+
93
+ **Response:**
94
+ ```json
95
+ {
96
+ "id": "71b8d791-d7b2-44e3-bf5b-bfa4ca3a34de",
97
+ "title": "My Document",
98
+ "content": [
99
+ { "id": "block-1", "text": "First paragraph..." },
100
+ { "id": "block-2", "text": "Second paragraph..." }
101
+ ],
102
+ "created_at": "2024-11-26T10:30:00.000Z",
103
+ "updated_at": "2024-11-26T10:30:00.000Z"
104
+ }
105
+ ```
106
+
107
+ ## Data Model
108
+
109
+ ### ContentBlock
110
+ ```typescript
111
+ interface ContentBlock {
112
+ id: string; // Unique identifier (e.g., "block-1234567890")
113
+ text: string; // Complete paragraph text (can span multiple pages)
114
+ }
115
+ ```
116
+
117
+ **Key Principle**: Blocks represent logical content units (paragraphs), NOT page fragments. A block is only split when the user presses Enter, never due to pagination.
118
+
119
+ ### BlockSegment (Rendering Layer)
120
+ ```typescript
121
+ interface BlockSegment {
122
+ blockId: string; // Reference to ContentBlock
123
+ startOffset: number; // Character offset where segment starts
124
+ endOffset: number; // Character offset where segment ends
125
+ startLine: number; // First line index in this segment
126
+ endLine: number; // Last line index in this segment
127
+ y: number; // Y position on page
128
+ height: number; // Height of segment
129
+ lines: string[]; // Visible text lines for this segment
130
+ }
131
+ ```
132
+
133
+ ### Page
134
+ ```typescript
135
+ interface Page {
136
+ pageNumber: number;
137
+ lines: Line[]; // Legacy compatibility
138
+ segments: BlockSegment[]; // Primary rendering data
139
+ }
140
+ ```
141
+
142
+ ### Document (Persistence)
143
+ ```typescript
144
+ interface Document {
145
+ id: string;
146
+ title: string;
147
+ content: ContentBlock[];
148
+ created_at: string;
149
+ updated_at: string;
150
+ }
151
+ ```
152
+
153
+ ## Pagination Strategy
154
+
155
+ ### Overview
156
+ The editor uses a **two-layer architecture**:
157
+ 1. **Data Layer**: Immutable blocks (paragraphs)
158
+ 2. **Rendering Layer**: Dynamic segments (visual layout)
159
+
160
+ ### How It Works
161
+
162
+ #### 1. Text Measurement
163
+ - Uses HTML5 Canvas API for pixel-perfect text width calculation
164
+ - Accounts for font family, size, and browser rendering
165
+ - Breaks text into lines at word boundaries
166
+
167
+ #### 2. Line Breaking Algorithm
168
+ ```
169
+ For each block:
170
+ 1. Measure text with current font
171
+ 2. Break into lines that fit page width
172
+ 3. Track character offsets for each line
173
+ 4. Preserve whitespace and newlines
174
+ ```
175
+
176
+ #### 3. Page Assignment
177
+ ```
178
+ currentY = marginTop
179
+ For each line:
180
+ If (currentY + lineHeight > pageBottom):
181
+ - Finalize current segment
182
+ - Start new page
183
+ - Reset Y position
184
+ Add line to current segment
185
+ currentY += lineHeight
186
+ ```
187
+
188
+ #### 4. Segment Creation
189
+ When a block spans multiple pages, create separate segments:
190
+ ```javascript
191
+ // Block spans pages 1 and 2
192
+ Segment 1: { blockId: "block-1", startOffset: 0, endOffset: 150, lines: [...] }
193
+ Segment 2: { blockId: "block-1", startOffset: 151, endOffset: 300, lines: [...] }
194
+ ```
195
+
196
+ ### Key Benefits
197
+ - ✅ **Clean Data Model**: Content structure independent of layout
198
+ - ✅ **Seamless Editing**: Text selection works across page boundaries
199
+ - ✅ **Version Control Friendly**: Changes tracked at paragraph level
200
+ - ✅ **Efficient Reflow**: Only pagination recalculates, data unchanged
201
+
202
+ ## Cursor Persistence
203
+
204
+ Critical for user experience. Implementation uses:
205
+ 1. **Absolute Offset Tracking**: Store cursor position relative to full block
206
+ 2. **Segment Mapping**: Find which segment contains cursor position
207
+ 3. **Relative Calculation**: Convert absolute offset to segment-relative position
208
+ 4. **Race Condition Guards**: Prevent overlapping restoration attempts
209
+ 5. **Async Restoration**: Use `requestAnimationFrame` + `setTimeout` for DOM readiness
210
+
211
+
212
+ ### Environment Variables
213
+ No environment variables required. Default configuration:
214
+ - Backend: `http://localhost:3001`
215
+ - Frontend: `http://localhost:3000`
216
+ - Data storage: `server/data/` directory
217
+
218
+ ### Known Limitations
219
+ - No collaborative editing (single user)
220
+ - No rich text formatting (plain text only)
221
+ - File-based storage (no database)
222
+ - No authentication/authorization
223
+
224
+ ## Troubleshooting
225
+
226
+ **Cursor jumps to beginning**
227
+ - Fixed in latest version with concurrent restoration guards
228
+
229
+ **Content overlaps pages**
230
+ - Ensure using segment-based rendering (post-refactor architecture)
231
+
232
+ **Backend not starting**
233
+ - Check port 3001 is available
234
+ - Verify Node.js 16+ installed
235
+
236
+ **Frontend build errors**
237
+ - Clear cache: `rm -rf node_modules && npm install`
238
+ - Check Node version matches prerequisites
239
+
240
+ ## License
241
+
242
+ MIT
243
+
244
+ ## Contributors
245
+
246
+ Built with assistance from Claude (Anthropic AI) for architecture design, pagination algorithm implementation, and bug fixes.
Document-Editor--master/client/.gitignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # production
12
+ /build
13
+
14
+ # misc
15
+ .DS_Store
16
+ .env.local
17
+ .env.development.local
18
+ .env.test.local
19
+ .env.production.local
20
+
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
Document-Editor--master/client/README.md ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Getting Started with Create React App
2
+
3
+ This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4
+
5
+ ## Available Scripts
6
+
7
+ In the project directory, you can run:
8
+
9
+ ### `npm start`
10
+
11
+ Runs the app in the development mode.\
12
+ Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13
+
14
+ The page will reload if you make edits.\
15
+ You will also see any lint errors in the console.
16
+
17
+ ### `npm test`
18
+
19
+ Launches the test runner in the interactive watch mode.\
20
+ See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21
+
22
+ ### `npm run build`
23
+
24
+ Builds the app for production to the `build` folder.\
25
+ It correctly bundles React in production mode and optimizes the build for the best performance.
26
+
27
+ The build is minified and the filenames include the hashes.\
28
+ Your app is ready to be deployed!
29
+
30
+ See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31
+
32
+ ### `npm run eject`
33
+
34
+ **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35
+
36
+ If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37
+
38
+ Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39
+
40
+ You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41
+
42
+ ## Learn More
43
+
44
+ You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45
+
46
+ To learn React, check out the [React documentation](https://reactjs.org/).
Document-Editor--master/client/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
Document-Editor--master/client/package.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "client",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@testing-library/dom": "^10.4.1",
7
+ "@testing-library/jest-dom": "^6.9.1",
8
+ "@testing-library/react": "^16.3.0",
9
+ "@testing-library/user-event": "^13.5.0",
10
+ "@types/jest": "^27.5.2",
11
+ "@types/node": "^16.18.126",
12
+ "@types/react": "^19.2.7",
13
+ "@types/react-dom": "^19.2.3",
14
+ "react": "^19.2.0",
15
+ "react-dom": "^19.2.0",
16
+ "react-scripts": "5.0.1",
17
+ "typescript": "^4.9.5",
18
+ "web-vitals": "^2.1.4"
19
+ },
20
+ "scripts": {
21
+ "start": "react-scripts start",
22
+ "build": "react-scripts build",
23
+ "test": "react-scripts test",
24
+ "eject": "react-scripts eject"
25
+ },
26
+ "eslintConfig": {
27
+ "extends": [
28
+ "react-app",
29
+ "react-app/jest"
30
+ ]
31
+ },
32
+ "browserslist": {
33
+ "production": [
34
+ ">0.2%",
35
+ "not dead",
36
+ "not op_mini all"
37
+ ],
38
+ "development": [
39
+ "last 1 chrome version",
40
+ "last 1 firefox version",
41
+ "last 1 safari version"
42
+ ]
43
+ }
44
+ }
Document-Editor--master/client/public/favicon.ico ADDED
Document-Editor--master/client/public/index.html ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <meta
9
+ name="description"
10
+ content="Web site created using create-react-app"
11
+ />
12
+ <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13
+ <!--
14
+ manifest.json provides metadata used when your web app is installed on a
15
+ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16
+ -->
17
+ <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18
+ <!--
19
+ Notice the use of %PUBLIC_URL% in the tags above.
20
+ It will be replaced with the URL of the `public` folder during the build.
21
+ Only files inside the `public` folder can be referenced from the HTML.
22
+
23
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24
+ work correctly both with client-side routing and a non-root public URL.
25
+ Learn how to configure a non-root public URL by running `npm run build`.
26
+ -->
27
+ <title>React App</title>
28
+ </head>
29
+ <body>
30
+ <noscript>You need to enable JavaScript to run this app.</noscript>
31
+ <div id="root"></div>
32
+ <!--
33
+ This HTML file is a template.
34
+ If you open it directly in the browser, you will see an empty page.
35
+
36
+ You can add webfonts, meta tags, or analytics to this file.
37
+ The build step will place the bundled scripts into the <body> tag.
38
+
39
+ To begin the development, run `npm start` or `yarn start`.
40
+ To create a production bundle, use `npm run build` or `yarn build`.
41
+ -->
42
+ </body>
43
+ </html>
Document-Editor--master/client/public/logo192.png ADDED
Document-Editor--master/client/public/logo512.png ADDED
Document-Editor--master/client/public/manifest.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "short_name": "React App",
3
+ "name": "Create React App Sample",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ },
10
+ {
11
+ "src": "logo192.png",
12
+ "type": "image/png",
13
+ "sizes": "192x192"
14
+ },
15
+ {
16
+ "src": "logo512.png",
17
+ "type": "image/png",
18
+ "sizes": "512x512"
19
+ }
20
+ ],
21
+ "start_url": ".",
22
+ "display": "standalone",
23
+ "theme_color": "#000000",
24
+ "background_color": "#ffffff"
25
+ }
Document-Editor--master/client/public/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
Document-Editor--master/client/src/App.css ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ padding: 0;
8
+ background: #f0f0f0;
9
+ font-family: Arial, sans-serif;
10
+ }
11
+
12
+ .App {
13
+ min-height: 100vh;
14
+ padding-bottom: 100px;
15
+ }
16
+
17
+ .controls {
18
+ position: sticky;
19
+ top: 0;
20
+ background: white;
21
+ padding: 15px 20px;
22
+ border-bottom: 2px solid #333;
23
+ display: flex;
24
+ gap: 10px;
25
+ align-items: center;
26
+ z-index: 1000;
27
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
28
+ }
29
+
30
+ .controls button {
31
+ padding: 10px 20px;
32
+ font-size: 14px;
33
+ cursor: pointer;
34
+ background: #007bff;
35
+ color: white;
36
+ border: none;
37
+ border-radius: 4px;
38
+ font-weight: 500;
39
+ }
40
+
41
+ .controls button:hover {
42
+ background: #0056b3;
43
+ }
44
+
45
+ .controls input {
46
+ padding: 10px;
47
+ font-size: 14px;
48
+ width: 300px;
49
+ border: 1px solid #ccc;
50
+ border-radius: 4px;
51
+ }
52
+
53
+ .doc-id {
54
+ font-size: 12px;
55
+ color: #666;
56
+ padding: 8px 12px;
57
+ background: #f5f5f5;
58
+ border-radius: 4px;
59
+ }
60
+
61
+ .page-count {
62
+ font-size: 12px;
63
+ color: #666;
64
+ margin-left: auto;
65
+ padding: 8px 12px;
66
+ background: #f5f5f5;
67
+ border-radius: 4px;
68
+ }
69
+
70
+ .document-container {
71
+ max-width: 900px;
72
+ margin: 40px auto;
73
+ padding: 0 20px;
74
+ }
75
+
76
+ .page {
77
+ background: white;
78
+ border: 1px solid #d0d0d0;
79
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
80
+ margin-bottom: 20px;
81
+ position: relative;
82
+ box-sizing: border-box;
83
+ }
84
+
85
+ .page-content {
86
+ position: relative;
87
+ width: 100%;
88
+ height: 100%;
89
+ }
90
+
91
+ .page-number {
92
+ position: absolute;
93
+ bottom: 20px;
94
+ left: 50%;
95
+ transform: translateX(-50%);
96
+ font-size: 11px;
97
+ color: #999;
98
+ pointer-events: none;
99
+ user-select: none;
100
+ }
101
+
102
+ /* Ensure contenteditable elements look clean */
103
+ [contenteditable] {
104
+ cursor: text;
105
+ }
106
+
107
+ [contenteditable]:focus {
108
+ outline: none;
109
+ }
110
+
111
+ /* [contenteditable]:empty:before {
112
+ content: 'Start typing...';
113
+ color: #ccc;
114
+ } */
115
+
116
+ .page-content [contenteditable] {
117
+ white-space: pre-wrap;
118
+ word-wrap: break-word;
119
+ tab-size: 4;
120
+ }
121
+
122
+ .page {
123
+ background: white;
124
+ border: 1px solid #d0d0d0;
125
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
126
+ margin-bottom: 20px;
127
+ position: relative;
128
+ box-sizing: border-box;
129
+ overflow: hidden; /* CRITICAL: Clips content at page boundaries */
130
+ }
131
+
132
+ .page-content {
133
+ position: relative;
134
+ width: 100%;
135
+ height: 100%;
136
+ direction: ltr;
137
+ text-align: left;
138
+ overflow: hidden; /* CRITICAL: Ensures no overflow */
139
+ }
140
+
141
+ .hint {
142
+ font-size: 12px;
143
+ color: #999;
144
+ margin-top: 10px;
145
+ }
146
+
147
+ kbd {
148
+ background: #f0f0f0;
149
+ border: 1px solid #ccc;
150
+ border-radius: 3px;
151
+ padding: 2px 6px;
152
+ font-family: monospace;
153
+ font-size: 11px;
154
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
155
+ }
156
+
157
+ .editable-block::selection {
158
+ background: #b3d7ff;
159
+ }
160
+
161
+ .editable-block::-moz-selection {
162
+ background: #b3d7ff;
163
+ }
Document-Editor--master/client/src/App.test.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import App from './App';
4
+
5
+ test('renders learn react link', () => {
6
+ render(<App />);
7
+ const linkElement = screen.getByText(/learn react/i);
8
+ expect(linkElement).toBeInTheDocument();
9
+ });
Document-Editor--master/client/src/App.tsx ADDED
@@ -0,0 +1,611 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useCallback, useEffect, useRef } from 'react';
2
+ import { ContentBlock, Page, PAGE_CONFIG } from './types';
3
+ import { PaginationEngine } from './paginationEngine';
4
+ import './App.css';
5
+
6
+ const App: React.FC = () => {
7
+ const [blocks, setBlocks] = useState<ContentBlock[]>([
8
+ { id: Date.now().toString(), text: '' }
9
+ ]);
10
+ const [pages, setPages] = useState<Page[]>([]);
11
+ const [docId, setDocId] = useState<string>('');
12
+ const [loadId, setLoadId] = useState<string>('');
13
+ const [engine] = useState(() => new PaginationEngine());
14
+ const editorRef = useRef<HTMLDivElement>(null);
15
+ const cursorPositionRef = useRef<Map<string, number>>(new Map());
16
+ const isComposingRef = useRef(false);
17
+ const [selectAllActive, setSelectAllActive] = useState(false);
18
+
19
+ // Repaginate whenever blocks change
20
+ useEffect(() => {
21
+ const newPages = engine.paginate(blocks);
22
+ setPages(newPages);
23
+ }, [blocks, engine]);
24
+
25
+ // Auto-split blocks that span multiple pages
26
+ useEffect(() => {
27
+ if (pages.length === 0) return;
28
+
29
+ // Find blocks that appear on multiple pages
30
+ const blockPageMap = new Map<string, Set<number>>();
31
+ pages.forEach(page => {
32
+ page.lines.forEach(line => {
33
+ if (!blockPageMap.has(line.blockId)) {
34
+ blockPageMap.set(line.blockId, new Set());
35
+ }
36
+ blockPageMap.get(line.blockId)!.add(page.pageNumber);
37
+ });
38
+ });
39
+
40
+ // Find the first block that spans pages
41
+ const spanningBlockId = Array.from(blockPageMap.entries())
42
+ .find(([_, pageSet]) => pageSet.size > 1)?.[0];
43
+
44
+ if (spanningBlockId) {
45
+ const block = blocks.find(b => b.id === spanningBlockId);
46
+ if (!block) return;
47
+
48
+ // Find where to split - at the page boundary
49
+ const firstPage = Math.min(...Array.from(blockPageMap.get(spanningBlockId)!));
50
+ const firstPageLines = pages[firstPage - 1].lines.filter(l => l.blockId === spanningBlockId);
51
+
52
+ // Calculate how much text fits on the first page
53
+ const textMeasurer = new (require('./textMeasurer').TextMeasurer)();
54
+ const contentWidth = PAGE_CONFIG.width - PAGE_CONFIG.marginLeft - PAGE_CONFIG.marginRight;
55
+ const allLines = textMeasurer.breakIntoLines(block.text, contentWidth);
56
+
57
+ // Find the split point (end of lines on first page)
58
+ const linesOnFirstPage = firstPageLines.length;
59
+ const textForFirstPage = allLines.slice(0, linesOnFirstPage).join('\n');
60
+ const textForNextPage = allLines.slice(linesOnFirstPage).join('\n');
61
+
62
+ if (textForFirstPage && textForNextPage) {
63
+ const blockIndex = blocks.findIndex(b => b.id === spanningBlockId);
64
+
65
+ setBlocks(prev => {
66
+ const newBlocks = [...prev];
67
+ newBlocks[blockIndex] = { ...newBlocks[blockIndex], text: textForFirstPage };
68
+ newBlocks.splice(blockIndex + 1, 0, {
69
+ id: `block-${Date.now()}-${Math.random()}`,
70
+ text: textForNextPage
71
+ });
72
+ return newBlocks;
73
+ });
74
+ }
75
+ }
76
+ }, [pages, blocks]);
77
+
78
+ // Restore cursor positions after render
79
+ useEffect(() => {
80
+ if (selectAllActive) return; // Don't restore cursor during select all
81
+
82
+ cursorPositionRef.current.forEach((offset, blockId) => {
83
+ const element = document.querySelector(`[data-block-id="${blockId}"]`) as HTMLElement;
84
+ if (element && document.activeElement === element) {
85
+ restoreCursorPosition(element, offset);
86
+ }
87
+ });
88
+ cursorPositionRef.current.clear();
89
+ });
90
+
91
+ const handleSave = async () => {
92
+ try {
93
+ const response = await fetch('http://localhost:3001/documents', {
94
+ method: 'POST',
95
+ headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify({
97
+ title: 'My Document',
98
+ content: blocks
99
+ })
100
+ });
101
+ const data = await response.json();
102
+ setDocId(data.id);
103
+ alert(`Document saved! ID: ${data.id}`);
104
+ } catch (err) {
105
+ alert('Save failed: ' + err);
106
+ }
107
+ };
108
+
109
+ const handleLoad = async () => {
110
+ if (!loadId.trim()) {
111
+ alert('Enter a document ID');
112
+ return;
113
+ }
114
+ try {
115
+ const response = await fetch(`http://localhost:3001/documents/${loadId}`);
116
+ const data = await response.json();
117
+ setBlocks(data.content);
118
+ setDocId(data.id);
119
+ alert('Document loaded!');
120
+ } catch (err) {
121
+ alert('Load failed: ' + err);
122
+ }
123
+ };
124
+
125
+ const saveCursorPosition = (element: HTMLElement): number | null => {
126
+ const selection = window.getSelection();
127
+ if (!selection || selection.rangeCount === 0) return null;
128
+
129
+ const range = selection.getRangeAt(0);
130
+ const preCaretRange = range.cloneRange();
131
+ preCaretRange.selectNodeContents(element);
132
+ preCaretRange.setEnd(range.endContainer, range.endOffset);
133
+ const caretOffset = preCaretRange.toString().length;
134
+
135
+ return caretOffset;
136
+ };
137
+
138
+ const restoreCursorPosition = (element: HTMLElement, offset: number) => {
139
+ const selection = window.getSelection();
140
+ if (!selection) return;
141
+
142
+ if (!element.textContent) {
143
+ const range = document.createRange();
144
+ range.setStart(element, 0);
145
+ range.collapse(true);
146
+ selection.removeAllRanges();
147
+ selection.addRange(range);
148
+ return;
149
+ }
150
+
151
+ let charCount = 0;
152
+
153
+ const traverseNodes = (node: Node): boolean => {
154
+ if (node.nodeType === Node.TEXT_NODE) {
155
+ const textLength = node.textContent?.length || 0;
156
+ if (charCount + textLength >= offset) {
157
+ const range = document.createRange();
158
+ const nodeOffset = Math.min(offset - charCount, textLength);
159
+ range.setStart(node, nodeOffset);
160
+ range.collapse(true);
161
+ selection.removeAllRanges();
162
+ selection.addRange(range);
163
+ return true;
164
+ }
165
+ charCount += textLength;
166
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
167
+ for (let i = 0; i < node.childNodes.length; i++) {
168
+ if (traverseNodes(node.childNodes[i])) {
169
+ return true;
170
+ }
171
+ }
172
+ }
173
+ return false;
174
+ };
175
+
176
+ traverseNodes(element);
177
+ };
178
+
179
+ // Check if block will overflow page
180
+ const willBlockOverflowPage = (blockId: string, newText: string): boolean => {
181
+ // Create temporary blocks array with the new text
182
+ const tempBlocks = blocks.map(b =>
183
+ b.id === blockId ? { ...b, text: newText } : b
184
+ );
185
+
186
+ // Paginate with new content
187
+ const tempPages = engine.paginate(tempBlocks);
188
+
189
+ // Find which pages this block appears on
190
+ const blockPages = new Set<number>();
191
+ tempPages.forEach(page => {
192
+ page.lines.forEach(line => {
193
+ if (line.blockId === blockId) {
194
+ blockPages.add(page.pageNumber);
195
+ }
196
+ });
197
+ });
198
+
199
+ // If block spans more than one page, it overflows
200
+ return blockPages.size > 1;
201
+ };
202
+
203
+ // Split block that overflows into current and new block
204
+ const splitOverflowingBlock = (blockId: string, newText: string) => {
205
+ const blockIndex = blocks.findIndex(b => b.id === blockId);
206
+ if (blockIndex === -1) return;
207
+
208
+ // Binary search to find the maximum text that fits on one page
209
+ let left = 0;
210
+ let right = newText.length;
211
+ let maxFitLength = 0;
212
+
213
+ while (left <= right) {
214
+ const mid = Math.floor((left + right) / 2);
215
+ const testText = newText.substring(0, mid);
216
+
217
+ const tempBlocks = blocks.map(b =>
218
+ b.id === blockId ? { ...b, text: testText } : b
219
+ );
220
+ const tempPages = engine.paginate(tempBlocks);
221
+
222
+ const blockPages = new Set<number>();
223
+ tempPages.forEach(page => {
224
+ page.lines.forEach(line => {
225
+ if (line.blockId === blockId) {
226
+ blockPages.add(page.pageNumber);
227
+ }
228
+ });
229
+ });
230
+
231
+ if (blockPages.size <= 1) {
232
+ maxFitLength = mid;
233
+ left = mid + 1;
234
+ } else {
235
+ right = mid - 1;
236
+ }
237
+ }
238
+
239
+ // Find last word boundary before maxFitLength
240
+ let splitPoint = maxFitLength;
241
+ while (splitPoint > 0 && newText[splitPoint] !== ' ' && newText[splitPoint] !== '\n') {
242
+ splitPoint--;
243
+ }
244
+
245
+ // If no word boundary found, split at character
246
+ if (splitPoint === 0) {
247
+ splitPoint = maxFitLength;
248
+ }
249
+
250
+ const textForCurrentBlock = newText.substring(0, splitPoint).trimEnd();
251
+ const textForNewBlock = newText.substring(splitPoint).trimStart();
252
+
253
+ // Create new block with overflow text
254
+ const newBlock: ContentBlock = {
255
+ id: `block-${Date.now()}-${Math.random()}`,
256
+ text: textForNewBlock
257
+ };
258
+
259
+ // Update blocks
260
+ setBlocks(prev => {
261
+ const newBlocks = [...prev];
262
+ newBlocks[blockIndex] = { ...newBlocks[blockIndex], text: textForCurrentBlock };
263
+ newBlocks.splice(blockIndex + 1, 0, newBlock);
264
+ return newBlocks;
265
+ });
266
+
267
+ // Focus new block
268
+ setTimeout(() => {
269
+ const newElement = document.querySelector(`[data-block-id="${newBlock.id}"]`) as HTMLDivElement;
270
+ if (newElement) {
271
+ newElement.focus();
272
+ restoreCursorPosition(newElement, 0);
273
+ }
274
+ }, 10);
275
+ };
276
+
277
+ const handleCompositionStart = useCallback(() => {
278
+ isComposingRef.current = true;
279
+ }, []);
280
+
281
+ const handleCompositionEnd = useCallback(() => {
282
+ isComposingRef.current = false;
283
+ }, []);
284
+
285
+ const handleInput = useCallback((e: React.FormEvent<HTMLDivElement>) => {
286
+ if (isComposingRef.current) return;
287
+
288
+ const target = e.currentTarget;
289
+ const blockId = target.getAttribute('data-block-id');
290
+ if (!blockId) return;
291
+
292
+ const cursorPosition = saveCursorPosition(target);
293
+ const newText = target.textContent || '';
294
+
295
+ // Check if this will cause overflow
296
+ if (willBlockOverflowPage(blockId, newText)) {
297
+ // Split the block
298
+ splitOverflowingBlock(blockId, newText);
299
+ return;
300
+ }
301
+
302
+ if (cursorPosition !== null) {
303
+ cursorPositionRef.current.set(blockId, cursorPosition);
304
+ }
305
+
306
+ setBlocks(prev => prev.map(block =>
307
+ block.id === blockId ? { ...block, text: newText } : block
308
+ ));
309
+ }, [blocks, engine]);
310
+
311
+ // Handle Ctrl+A to select all content
312
+ const handleSelectAll = useCallback((e: React.KeyboardEvent) => {
313
+ if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
314
+ e.preventDefault();
315
+ setSelectAllActive(true);
316
+
317
+ // Select all contentEditable elements
318
+ const allEditables = document.querySelectorAll('[contenteditable="true"]');
319
+ const selection = window.getSelection();
320
+
321
+ if (selection && allEditables.length > 0) {
322
+ const range = document.createRange();
323
+ const firstElement = allEditables[0];
324
+ const lastElement = allEditables[allEditables.length - 1];
325
+
326
+ range.setStartBefore(firstElement);
327
+ range.setEndAfter(lastElement);
328
+
329
+ selection.removeAllRanges();
330
+ selection.addRange(range);
331
+ }
332
+
333
+ // Reset select all state after a moment
334
+ setTimeout(() => {
335
+ setSelectAllActive(false);
336
+ }, 100);
337
+ }
338
+ }, []);
339
+
340
+ const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
341
+ // Handle Ctrl+A first
342
+ if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
343
+ handleSelectAll(e);
344
+ return;
345
+ }
346
+
347
+ const target = e.currentTarget;
348
+ const blockId = target.getAttribute('data-block-id');
349
+ if (!blockId) return;
350
+
351
+ if (e.key === 'Enter') {
352
+ e.preventDefault();
353
+
354
+ const currentIndex = blocks.findIndex(b => b.id === blockId);
355
+ if (currentIndex === -1) return;
356
+
357
+ const selection = window.getSelection();
358
+ if (!selection || selection.rangeCount === 0) return;
359
+
360
+ const range = selection.getRangeAt(0);
361
+ const preCaretRange = range.cloneRange();
362
+ preCaretRange.selectNodeContents(target);
363
+ preCaretRange.setEnd(range.endContainer, range.endOffset);
364
+ const caretOffset = preCaretRange.toString().length;
365
+
366
+ const currentText = target.textContent || '';
367
+ const beforeCursor = currentText.substring(0, caretOffset);
368
+ const afterCursor = currentText.substring(caretOffset);
369
+
370
+ const newBlock: ContentBlock = {
371
+ id: `block-${Date.now()}-${Math.random()}`,
372
+ text: afterCursor
373
+ };
374
+
375
+ setBlocks(prev => {
376
+ const newBlocks = [...prev];
377
+ newBlocks[currentIndex] = { ...newBlocks[currentIndex], text: beforeCursor };
378
+ newBlocks.splice(currentIndex + 1, 0, newBlock);
379
+ return newBlocks;
380
+ });
381
+
382
+ setTimeout(() => {
383
+ const newElement = document.querySelector(`[data-block-id="${newBlock.id}"]`) as HTMLDivElement;
384
+ if (newElement) {
385
+ newElement.focus();
386
+ const range = document.createRange();
387
+ const sel = window.getSelection();
388
+ if (newElement.childNodes.length > 0) {
389
+ range.setStart(newElement.childNodes[0], 0);
390
+ } else {
391
+ range.setStart(newElement, 0);
392
+ }
393
+ range.collapse(true);
394
+ sel?.removeAllRanges();
395
+ sel?.addRange(range);
396
+ }
397
+ }, 10);
398
+ } else if (e.key === 'Backspace') {
399
+ const selection = window.getSelection();
400
+ if (!selection || selection.rangeCount === 0) return;
401
+
402
+ const range = selection.getRangeAt(0);
403
+ const preCaretRange = range.cloneRange();
404
+ preCaretRange.selectNodeContents(target);
405
+ preCaretRange.setEnd(range.endContainer, range.endOffset);
406
+ const caretOffset = preCaretRange.toString().length;
407
+
408
+ if (caretOffset === 0 && blocks.length > 1) {
409
+ e.preventDefault();
410
+ const currentIndex = blocks.findIndex(b => b.id === blockId);
411
+ if (currentIndex > 0) {
412
+ const prevBlock = blocks[currentIndex - 1];
413
+ const currentBlock = blocks[currentIndex];
414
+ const prevTextLength = prevBlock.text.length;
415
+
416
+ setBlocks(prev => {
417
+ const newBlocks = [...prev];
418
+ newBlocks[currentIndex - 1] = {
419
+ ...prevBlock,
420
+ text: prevBlock.text + currentBlock.text
421
+ };
422
+ newBlocks.splice(currentIndex, 1);
423
+ return newBlocks;
424
+ });
425
+
426
+ setTimeout(() => {
427
+ const prevElement = document.querySelector(`[data-block-id="${prevBlock.id}"]`) as HTMLDivElement;
428
+ if (prevElement) {
429
+ prevElement.focus();
430
+ restoreCursorPosition(prevElement, prevTextLength);
431
+ }
432
+ }, 10);
433
+ }
434
+ }
435
+ } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
436
+ const currentIndex = blocks.findIndex(b => b.id === blockId);
437
+ const selection = window.getSelection();
438
+ if (!selection || selection.rangeCount === 0) return;
439
+
440
+ const range = selection.getRangeAt(0);
441
+ const preCaretRange = range.cloneRange();
442
+ preCaretRange.selectNodeContents(target);
443
+ preCaretRange.setEnd(range.endContainer, range.endOffset);
444
+ const caretOffset = preCaretRange.toString().length;
445
+
446
+ if (e.key === 'ArrowUp' && caretOffset === 0 && currentIndex > 0) {
447
+ e.preventDefault();
448
+ const prevBlock = blocks[currentIndex - 1];
449
+ const prevElement = document.querySelector(`[data-block-id="${prevBlock.id}"]`) as HTMLDivElement;
450
+ if (prevElement) {
451
+ prevElement.focus();
452
+ restoreCursorPosition(prevElement, prevBlock.text.length);
453
+ }
454
+ } else if (e.key === 'ArrowDown' && caretOffset === target.textContent?.length && currentIndex < blocks.length - 1) {
455
+ e.preventDefault();
456
+ const nextBlock = blocks[currentIndex + 1];
457
+ const nextElement = document.querySelector(`[data-block-id="${nextBlock.id}"]`) as HTMLDivElement;
458
+ if (nextElement) {
459
+ nextElement.focus();
460
+ restoreCursorPosition(nextElement, 0);
461
+ }
462
+ }
463
+ }
464
+ }, [blocks, handleSelectAll]);
465
+
466
+ // Global keyboard event listener for Ctrl+A
467
+ useEffect(() => {
468
+ const handleGlobalKeyDown = (e: KeyboardEvent) => {
469
+ if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
470
+ // Check if focus is inside document container
471
+ const activeElement = document.activeElement;
472
+ const isInEditor = activeElement?.closest('.document-container');
473
+
474
+ if (isInEditor) {
475
+ e.preventDefault();
476
+ setSelectAllActive(true);
477
+
478
+ const allEditables = document.querySelectorAll('[contenteditable="true"]');
479
+ const selection = window.getSelection();
480
+
481
+ if (selection && allEditables.length > 0) {
482
+ const range = document.createRange();
483
+ const firstElement = allEditables[0];
484
+ const lastElement = allEditables[allEditables.length - 1];
485
+
486
+ range.setStartBefore(firstElement);
487
+ range.setEndAfter(lastElement);
488
+
489
+ selection.removeAllRanges();
490
+ selection.addRange(range);
491
+ }
492
+
493
+ setTimeout(() => {
494
+ setSelectAllActive(false);
495
+ }, 100);
496
+ }
497
+ }
498
+ };
499
+
500
+ document.addEventListener('keydown', handleGlobalKeyDown);
501
+ return () => document.removeEventListener('keydown', handleGlobalKeyDown);
502
+ }, []);
503
+
504
+ return (
505
+ <div className="App">
506
+ <div className="controls">
507
+ <button onClick={handleSave}>💾 Save</button>
508
+ <input
509
+ type="text"
510
+ placeholder="Enter Document ID to load"
511
+ value={loadId}
512
+ onChange={e => setLoadId(e.target.value)}
513
+ />
514
+ <button onClick={handleLoad}>📂 Load</button>
515
+ {docId && <span className="doc-id">ID: {docId}</span>}
516
+ <span className="page-count">📄 {pages.length} {pages.length === 1 ? 'Page' : 'Pages'}</span>
517
+ </div>
518
+
519
+ <div className="document-container" ref={editorRef}>
520
+ {pages.length === 0 ? (
521
+ <div className="empty-state">
522
+ <p>Click on the page below to start typing...</p>
523
+ <p className="hint">Press <kbd>Ctrl+A</kbd> (or <kbd>Cmd+A</kbd>) to select all content</p>
524
+ </div>
525
+ ) : null}
526
+
527
+ {pages.map(page => (
528
+ <div
529
+ key={page.pageNumber}
530
+ className="page"
531
+ style={{
532
+ width: PAGE_CONFIG.width,
533
+ height: PAGE_CONFIG.height,
534
+ padding: `${PAGE_CONFIG.marginTop}px ${PAGE_CONFIG.marginRight}px ${PAGE_CONFIG.marginBottom}px ${PAGE_CONFIG.marginLeft}px`,
535
+ }}
536
+ >
537
+ <div className="page-content">
538
+ {(() => {
539
+ // Group lines by blockId to handle blocks that span across pages
540
+ const blockGroups = new Map<string, typeof page.lines>();
541
+ page.lines.forEach(line => {
542
+ if (!blockGroups.has(line.blockId)) {
543
+ blockGroups.set(line.blockId, []);
544
+ }
545
+ blockGroups.get(line.blockId)!.push(line);
546
+ });
547
+
548
+ // Track which block instances we've seen on this page for unique keys
549
+ const blockInstanceCounts = new Map<string, number>();
550
+
551
+ return Array.from(blockGroups.entries()).map(([blockId, lines], groupIndex) => {
552
+ const block = blocks.find(b => b.id === blockId);
553
+ if (!block) return null;
554
+
555
+ // Generate unique key combining page number, block ID, and instance index
556
+ const instanceCount = blockInstanceCounts.get(blockId) || 0;
557
+ blockInstanceCounts.set(blockId, instanceCount + 1);
558
+ const uniqueKey = `page-${page.pageNumber}-block-${blockId}-instance-${instanceCount}`;
559
+
560
+ const firstLine = lines[0];
561
+ const lastLine = lines[lines.length - 1];
562
+
563
+ // Calculate the height needed for this block segment on this page
564
+ const blockHeight = (lastLine.y - firstLine.y) + PAGE_CONFIG.lineHeight;
565
+
566
+ return (
567
+ <div
568
+ key={uniqueKey}
569
+ data-block-id={blockId}
570
+ contentEditable
571
+ suppressContentEditableWarning
572
+ onInput={handleInput}
573
+ onKeyDown={handleKeyDown}
574
+ onCompositionStart={handleCompositionStart}
575
+ onCompositionEnd={handleCompositionEnd}
576
+ className="editable-block"
577
+ style={{
578
+ position: 'absolute',
579
+ left: 0,
580
+ top: firstLine.y - PAGE_CONFIG.marginTop,
581
+ width: '100%',
582
+ height: blockHeight,
583
+ lineHeight: `${PAGE_CONFIG.lineHeight}px`,
584
+ fontSize: PAGE_CONFIG.fontSize,
585
+ fontFamily: PAGE_CONFIG.fontFamily,
586
+ outline: 'none',
587
+ whiteSpace: 'pre-wrap',
588
+ wordWrap: 'break-word',
589
+ direction: 'ltr',
590
+ textAlign: 'left',
591
+ unicodeBidi: 'embed',
592
+ }}
593
+ >
594
+ {block.text}
595
+ </div>
596
+ );
597
+ });
598
+ })()}
599
+ </div>
600
+
601
+ <div className="page-number">
602
+ Page {page.pageNumber} of {pages.length}
603
+ </div>
604
+ </div>
605
+ ))}
606
+ </div>
607
+ </div>
608
+ );
609
+ };
610
+
611
+ export default App;
Document-Editor--master/client/src/index.css ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ margin: 0;
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5
+ sans-serif;
6
+ -webkit-font-smoothing: antialiased;
7
+ -moz-osx-font-smoothing: grayscale;
8
+ }
9
+
10
+ code {
11
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12
+ monospace;
13
+ }
Document-Editor--master/client/src/index.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import './index.css';
4
+ import App from './App';
5
+ import reportWebVitals from './reportWebVitals';
6
+
7
+ const root = ReactDOM.createRoot(
8
+ document.getElementById('root') as HTMLElement
9
+ );
10
+ root.render(
11
+ <React.StrictMode>
12
+ <App />
13
+ </React.StrictMode>
14
+ );
15
+
16
+ // If you want to start measuring performance in your app, pass a function
17
+ // to log results (for example: reportWebVitals(console.log))
18
+ // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19
+ reportWebVitals();
Document-Editor--master/client/src/logo.svg ADDED
Document-Editor--master/client/src/paginationEngine.ts ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PAGE_CONFIG, ContentBlock, Page, Line } from './types';
2
+ import { TextMeasurer } from './textMeasurer';
3
+
4
+ export class PaginationEngine {
5
+ private measurer: TextMeasurer;
6
+
7
+ constructor() {
8
+ this.measurer = new TextMeasurer();
9
+ }
10
+
11
+ private getContentWidth(): number {
12
+ return PAGE_CONFIG.width - PAGE_CONFIG.marginLeft - PAGE_CONFIG.marginRight;
13
+ }
14
+
15
+ private getContentHeight(): number {
16
+ return PAGE_CONFIG.height - PAGE_CONFIG.marginTop - PAGE_CONFIG.marginBottom;
17
+ }
18
+
19
+ paginate(blocks: ContentBlock[]): Page[] {
20
+ const pages: Page[] = [];
21
+ let currentPage: Page = { pageNumber: 1, lines: [] };
22
+ let currentY = PAGE_CONFIG.marginTop;
23
+
24
+ for (const block of blocks) {
25
+ // Handle completely empty blocks
26
+ if (block.text.length === 0) {
27
+ const lineHeight = PAGE_CONFIG.lineHeight;
28
+
29
+ if (currentY + lineHeight > PAGE_CONFIG.height - PAGE_CONFIG.marginBottom) {
30
+ pages.push(currentPage);
31
+ currentPage = { pageNumber: pages.length + 1, lines: [] };
32
+ currentY = PAGE_CONFIG.marginTop;
33
+ }
34
+
35
+ currentPage.lines.push({
36
+ text: '',
37
+ y: currentY,
38
+ blockId: block.id
39
+ });
40
+ currentY += lineHeight;
41
+ continue;
42
+ }
43
+
44
+ // Break text into lines (preserving whitespace)
45
+ const lines = this.measurer.breakIntoLines(
46
+ block.text,
47
+ this.getContentWidth()
48
+ );
49
+
50
+ for (const lineText of lines) {
51
+ const lineHeight = PAGE_CONFIG.lineHeight;
52
+
53
+ // Check if line fits on current page
54
+ if (currentY + lineHeight > PAGE_CONFIG.height - PAGE_CONFIG.marginBottom) {
55
+ pages.push(currentPage);
56
+ currentPage = { pageNumber: pages.length + 1, lines: [] };
57
+ currentY = PAGE_CONFIG.marginTop;
58
+ }
59
+
60
+ currentPage.lines.push({
61
+ text: lineText,
62
+ y: currentY,
63
+ blockId: block.id
64
+ });
65
+
66
+ currentY += lineHeight;
67
+ }
68
+ }
69
+
70
+ // Push last page
71
+ if (currentPage.lines.length > 0) {
72
+ pages.push(currentPage);
73
+ }
74
+
75
+ return pages;
76
+ }
77
+ }
Document-Editor--master/client/src/react-app-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="react-scripts" />
Document-Editor--master/client/src/reportWebVitals.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReportHandler } from 'web-vitals';
2
+
3
+ const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4
+ if (onPerfEntry && onPerfEntry instanceof Function) {
5
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6
+ getCLS(onPerfEntry);
7
+ getFID(onPerfEntry);
8
+ getFCP(onPerfEntry);
9
+ getLCP(onPerfEntry);
10
+ getTTFB(onPerfEntry);
11
+ });
12
+ }
13
+ };
14
+
15
+ export default reportWebVitals;
Document-Editor--master/client/src/setupTests.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom';
Document-Editor--master/client/src/textMeasurer.ts ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PAGE_CONFIG } from './types';
2
+
3
+ export class TextMeasurer {
4
+ private canvas: HTMLCanvasElement;
5
+ private ctx: CanvasRenderingContext2D;
6
+
7
+ constructor() {
8
+ this.canvas = document.createElement('canvas');
9
+ this.ctx = this.canvas.getContext('2d')!;
10
+ this.ctx.font = `${PAGE_CONFIG.fontSize}px ${PAGE_CONFIG.fontFamily}`;
11
+ }
12
+
13
+ measureText(text: string): number {
14
+ return this.ctx.measureText(text).width;
15
+ }
16
+
17
+ breakIntoLines(text: string, maxWidth: number): string[] {
18
+ if (!text) return [''];
19
+
20
+ // Replace tabs with 4 spaces (standard tab width)
21
+ const processedText = text.replace(/\t/g, ' ');
22
+
23
+ // Handle explicit line breaks
24
+ const paragraphs = processedText.split('\n');
25
+ const allLines: string[] = [];
26
+
27
+ for (const paragraph of paragraphs) {
28
+ if (!paragraph.trim()) {
29
+ // Preserve empty lines
30
+ allLines.push('');
31
+ continue;
32
+ }
33
+
34
+ // Word wrap algorithm that preserves spaces
35
+ const words = paragraph.split(/(\s+)/); // Split but keep spaces
36
+ const lines: string[] = [];
37
+ let currentLine = '';
38
+
39
+ for (let i = 0; i < words.length; i++) {
40
+ const word = words[i];
41
+ const testLine = currentLine + word;
42
+ const width = this.measureText(testLine);
43
+
44
+ if (width > maxWidth && currentLine.trim()) {
45
+ // Line would overflow, push current line and start new
46
+ lines.push(currentLine);
47
+
48
+ // Skip leading space on new line if current word is whitespace
49
+ if (word.trim() === '') {
50
+ currentLine = '';
51
+ } else {
52
+ currentLine = word;
53
+ }
54
+ } else {
55
+ currentLine = testLine;
56
+ }
57
+ }
58
+
59
+ if (currentLine) {
60
+ lines.push(currentLine);
61
+ }
62
+
63
+ allLines.push(...(lines.length > 0 ? lines : ['']));
64
+ }
65
+
66
+ return allLines;
67
+ }
68
+ }
Document-Editor--master/client/src/types.ts ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const PAGE_CONFIG = {
2
+ width: 794,
3
+ height: 1123,
4
+ marginTop: 72,
5
+ marginBottom: 72,
6
+ marginLeft: 72,
7
+ marginRight: 72,
8
+ lineHeight: 24,
9
+ fontSize: 16,
10
+ fontFamily: 'Arial, sans-serif'
11
+ };
12
+
13
+ export interface ContentBlock {
14
+ id: string;
15
+ text: string;
16
+ }
17
+
18
+ export interface Document {
19
+ id?: string;
20
+ title: string;
21
+ content: ContentBlock[];
22
+ created_at?: string;
23
+ updated_at?: string;
24
+ }
25
+
26
+ export interface Line {
27
+ text: string;
28
+ y: number;
29
+ blockId: string;
30
+ }
31
+
32
+ export interface Page {
33
+ pageNumber: number;
34
+ lines: Line[];
35
+ }
Document-Editor--master/client/tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "strict": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "module": "esnext",
17
+ "moduleResolution": "node",
18
+ "resolveJsonModule": true,
19
+ "isolatedModules": true,
20
+ "noEmit": true,
21
+ "jsx": "react-jsx"
22
+ },
23
+ "include": [
24
+ "src"
25
+ ]
26
+ }
Document-Editor--master/package.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fullstack-app",
3
+ "type": "module",
4
+ "scripts": {
5
+ "start": "node server/index.js",
6
+ "server": "nodemon server/index.js",
7
+ "client": "npm start --prefix client",
8
+ "build": "npm install --prefix client && npm run build --prefix client"
9
+ }
10
+ }
Document-Editor--master/server/data/040e38fc-f5ff-448b-a38e-528b77d62c7c.json ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "040e38fc-f5ff-448b-a38e-528b77d62c7c",
3
+ "title": "My Document",
4
+ "content": [
5
+ {
6
+ "id": "1764185322326",
7
+ "text": "Hello world"
8
+ },
9
+ {
10
+ "id": "block-1764185326179-0.745027487574992",
11
+ "text": ""
12
+ },
13
+ {
14
+ "id": "block-1764185326459-0.8631461999435833",
15
+ "text": "Hello "
16
+ },
17
+ {
18
+ "id": "block-1764185332099-0.4020291953985998",
19
+ "text": ""
20
+ },
21
+ {
22
+ "id": "block-1764185333507-0.4573760865672791",
23
+ "text": ""
24
+ },
25
+ {
26
+ "id": "block-1764185333723-0.2570954943893221",
27
+ "text": "Hello "
28
+ },
29
+ {
30
+ "id": "block-1764185341291-0.460156802426543",
31
+ "text": ""
32
+ },
33
+ {
34
+ "id": "block-1764185342946-0.3570515473860154",
35
+ "text": ""
36
+ },
37
+ {
38
+ "id": "block-1764185345731-0.0795202500495431",
39
+ "text": ""
40
+ },
41
+ {
42
+ "id": "block-1764185345867-0.8343009476817695",
43
+ "text": ""
44
+ },
45
+ {
46
+ "id": "block-1764185346058-0.10172043543927756",
47
+ "text": ""
48
+ },
49
+ {
50
+ "id": "block-1764185346250-0.6051099698361062",
51
+ "text": "\n\n\nBackend: Express (Node.js)\n\n\nGoal: Host for free\n\n\nThe best free choices right now are:\n🥇 Railway (free tier) or 🥈 Render (free tier)\nBoth can host your full-stack Node + static React build. I’ll show the easiest full-stack method (one deployment).\n\n🎉 Best Option: Host Both Together on Railway/Render\n📌 Step 1 — Build your React Client\nOpen terminal:\ncd client\nnpm install\nnpm run build\n\nThis creates a build/ folder.\n\n📌 Step 2 — Make Express Serve the React Build\nIn your server folder, modify your Express app:\nimport express from \"express\";"
52
+ },
53
+ {
54
+ "id": "block-1764185358773-0.36930011138627317",
55
+ "text": "import path from \"path\";\nimport { fileURLToPath } from \"url\";\n"
56
+ },
57
+ {
58
+ "id": "block-1764185387263-0.5847362075190897",
59
+ "text": "const __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst app = express();\n\n// 👉 Serve React build\napp.use(express.static(path.join(__dirname, \"../client/build\")));\n\napp.get(\"*\", (_, res) => {\n res.sendFile(path.join(__dirname, \"../client/build/index.html\"));\n});\n\napp.listen(process.env.PORT || 5000, () => {\n console.log(\"Server running...\");\n});\n\n\n📌 Step 3 — Add a Root package.json Script\nIn the root folder (not client or server), create a package.json if it doesn’t exist:\n{\n \"name\": \"fullstack-app\",\n \"scripts\": {\n \"start\": \"node server/index.js\",\n \"server\": \"nodemon server/index.js\",\n \"client\": \"npm start --prefix client\",\n \"build\": \"npm install --prefix client && npm run build --prefix client\"\n }\n}\n\n(Make sure your entry file is server/index.js or adjust path.)\n\n📌 Step 4 — Push to GitHub\nMake sure your repo has this structure:\n/client\n/server\npackage.json (root)\n"
60
+ },
61
+ {
62
+ "id": "block-1764185358797-0.7712865336178318",
63
+ "text": "Commit and push to GitHub.\n\n🚀 Step 5 — Deploy on Railway (FREE)"
64
+ },
65
+ {
66
+ "id": "block-1764185387271-0.6136933963371233",
67
+ "text": "\n\nGo to https://railway.app/\n\n\nLogin with GitHub\n\n\nClick New Project → Deploy from GitHub Repo\n\n\nSelect your repo\n\n\nPORT=8080\nFeature\nRailway\nRender"
68
+ },
69
+ {
70
+ "id": "block-1764185358813-0.12024335256399588",
71
+ "text": "\n\n\n\nFree limit\n~5$/month usage\nFree 750 hours\n"
72
+ },
73
+ {
74
+ "id": "block-1764185372204-0.7695423897884532",
75
+ "text": "\nSleeps on inactivity\nYes\nYes\n\n\nBuild both together easily\n✔️ Best\n✔️ Good\n\n"
76
+ },
77
+ {
78
+ "id": "block-1764185379334-0.4269752564809537",
79
+ "text": "\nBoth work, but Railway is simpler.\n"
80
+ },
81
+ {
82
+ "id": "block-1764185387283-0.18512521330701626",
83
+ "text": "🧐 Want Separate Hosting Instead?\nIf you want:\n\n\nBackend on Railway\n\n\nFrontend on Vercel\n\n\nI can show that too — just say I want them separate.\n\n📌 Need Help with Environment Variables or Build Errors?\nSend:\n\n\nyour folder structure\n"
84
+ },
85
+ {
86
+ "id": "block-1764185358824-0.6519678463146473",
87
+ "text": "\npackage.json files\n\n\nI’ll fix them for you 🚀🛠️"
88
+ },
89
+ {
90
+ "id": "block-1764185341458-0.3415649538707576",
91
+ "text": ""
92
+ },
93
+ {
94
+ "id": "block-1764185341692-0.4827620206238017",
95
+ "text": "world"
96
+ },
97
+ {
98
+ "id": "block-1764185336714-0.6645613696878259",
99
+ "text": ""
100
+ },
101
+ {
102
+ "id": "block-1764185336883-0.6190069978347903",
103
+ "text": "Great! You have:\n\n\nFrontend: TypeScript React\n\n\nBackend: Express (Node.js)\n\n\nGoal: Host for free\n"
104
+ },
105
+ {
106
+ "id": "block-1764185379346-0.9188524656035867",
107
+ "text": "\nThe best free choices right now are:\n🥇 Railway (free tier) or 🥈 Render (free tier)"
108
+ },
109
+ {
110
+ "id": "block-1764185387293-0.39363787926915195",
111
+ "text": "Both can host your full-stack Node + static React build. I’ll show the easiest full-stack \nmethod (one deployment).\n\n🎉 Best Option: Host Both Together on Railway/Render\n📌 Step 1 — Build your React Client\nOpen terminal:\ncd client\nnpm install\nnpm run build\n\nThis creates a build/ folder.\n\n📌 Step 2 — Make Express Serve the React Build\nIn your server folder, modify your Express app:\nimport express from \"express\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";"
112
+ },
113
+ {
114
+ "id": "block-1764185366760-0.2924867741893613",
115
+ "text": "const __filename = fileURLToPath(import.meta.url);"
116
+ },
117
+ {
118
+ "id": "block-1764185366772-0.647275959118245",
119
+ "text": "const __dirname = path.dirname(__filename);\n\nconst app = express();\n\n// 👉 Serve React build\napp.use(express.static(path.join(__dirname, \"../client/build\")));\n\napp.get(\"*\", (_, res) => {"
120
+ },
121
+ {
122
+ "id": "block-1764185372217-0.3848900377626562",
123
+ "text": " res.sendFile(path.join(__dirname, \"../client/build/index.html\"));\n});\n\napp.listen(process.env.PORT || 5000, () => {\n console.log(\"Server running...\");\n});\n\n\n📌 Step 3 — Add a Root package.json Script\nIn the root folder (not client or server), create a package.json if it doesn’t exist:\n{"
124
+ },
125
+ {
126
+ "id": "block-1764185379354-0.21129516703548934",
127
+ "text": " \"name\": \"fullstack-app\",\n \"scripts\": {\n \"start\": \"node server/index.js\","
128
+ },
129
+ {
130
+ "id": "block-1764185387300-0.37138435896043853",
131
+ "text": " \"server\": \"nodemon server/index.js\",\n \"client\": \"npm start --prefix client\",\n \"build\": \"npm install --prefix client && npm run build --prefix client\"\n }\n}\n\n(Make sure your entry file is server/index.js or adjust path.)\n\n📌 Step 4 — Push to GitHub\nMake sure your repo has this structure:\n/client\n/server\npackage.json (root)\n\nCommit and push to GitHub.\n\n🚀 Step 5 — Deploy on Railway (FREE)\n"
132
+ },
133
+ {
134
+ "id": "block-1764185366791-0.7988936374851168",
135
+ "text": "\nGo to https://railway.app/\n\n\nLogin with GitHub\n\n\nClick New Project → Deploy from GitHub Repo"
136
+ },
137
+ {
138
+ "id": "block-1764185372227-0.20480405535862745",
139
+ "text": "\n\nSelect your repo\n\n\nImportant:\n\n\nSet Root project path to your root, not /client or /server.\n\n"
140
+ },
141
+ {
142
+ "id": "block-1764185379364-0.2759495671627247",
143
+ "text": "Add Environment Variables\n→ Railway → Variables → Add:\nPORT=8080"
144
+ },
145
+ {
146
+ "id": "block-1764185387310-0.8035233227403694",
147
+ "text": "(any other secrets like Mongo URL)\n\n\n🎊 Done! Your app URL will look like:\nhttps://yourapp.up.railway.app/\n\n\n⚠️ Important Notes for Free Hosting\n\n\n\nFeature\nRailway\nRender\n\n\n\n"
148
+ },
149
+ {
150
+ "id": "block-1764185366808-0.7356968921107767",
151
+ "text": "Free limit\n~5$/month usage\nFree 750 hours\n\n\nSleeps on inactivity\nYes\nYes"
152
+ },
153
+ {
154
+ "id": "block-1764185372236-0.9535100220474891",
155
+ "text": "\n\nBuild both together easily\n✔️ Best\n✔️ Good\n\n\n\nBoth work, but Railway is simpler.\n\n🧐 Want Separate Hosting Instead?"
156
+ },
157
+ {
158
+ "id": "block-1764185379371-0.9362557720680834",
159
+ "text": "If you want:\n\n"
160
+ },
161
+ {
162
+ "id": "block-1764185387319-0.4459054843320799",
163
+ "text": "Backend on Railway\n\n\nFrontend on Vercel\n\n\nI can show that too — just say I want them separate.\n\n📌 Need Help with Environment Variables or Build Errors?\nSend:\n\n\nyour folder structure\n\n\npackage.json files\n\n"
164
+ },
165
+ {
166
+ "id": "block-1764185366823-0.34698356755452187",
167
+ "text": "I’ll fix them for you 🚀🛠️"
168
+ },
169
+ {
170
+ "id": "block-1764185337403-0.8706107040484482",
171
+ "text": ""
172
+ },
173
+ {
174
+ "id": "block-1764185337490-0.6390174492297295",
175
+ "text": ""
176
+ },
177
+ {
178
+ "id": "block-1764185333882-0.22483528279064768",
179
+ "text": "world"
180
+ },
181
+ {
182
+ "id": "block-1764185330842-0.6293666256813296",
183
+ "text": ""
184
+ },
185
+ {
186
+ "id": "block-1764185329171-0.5813949064381904",
187
+ "text": ""
188
+ }
189
+ ],
190
+ "created_at": "2025-11-26T19:29:49.804Z",
191
+ "updated_at": "2025-11-26T19:29:49.804Z"
192
+ }
Document-Editor--master/server/data/71b8d791-d7b2-44e3-bf5b-bfa4ca3a34de.json ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "71b8d791-d7b2-44e3-bf5b-bfa4ca3a34de",
3
+ "title": "My Document",
4
+ "content": [
5
+ {
6
+ "id": "1764119897129",
7
+ "text": "Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds."
8
+ },
9
+ {
10
+ "id": "block-1764119915257-0.4292094413105557",
11
+ "text": ""
12
+ },
13
+ {
14
+ "id": "block-1764119915769-0.9249138680032881",
15
+ "text": ""
16
+ },
17
+ {
18
+ "id": "block-1764119915799-0.7964691271821067",
19
+ "text": ""
20
+ },
21
+ {
22
+ "id": "block-1764119915827-0.018955485880886136",
23
+ "text": ""
24
+ },
25
+ {
26
+ "id": "block-1764119915862-0.8424512262384346",
27
+ "text": ""
28
+ },
29
+ {
30
+ "id": "block-1764119915895-0.7436883957463118",
31
+ "text": "Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds."
32
+ },
33
+ {
34
+ "id": "block-1764119917418-0.5005262987305623",
35
+ "text": "Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds."
36
+ },
37
+ {
38
+ "id": "block-1764119919503-0.6967482027215017",
39
+ "text": ""
40
+ },
41
+ {
42
+ "id": "block-1764119924536-0.9639945946185713",
43
+ "text": "Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds."
44
+ },
45
+ {
46
+ "id": "block-1764119926560-0.9575022093644173",
47
+ "text": "Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello"
48
+ },
49
+ {
50
+ "id": "block-1764119928702-0.18037476296660337",
51
+ "text": "Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.HelloHello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Worlds.HeHello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello"
52
+ },
53
+ {
54
+ "id": "block-1764120044271-0.04877565396456174",
55
+ "text": "Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.HelloHello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Worlds.HeWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello"
56
+ },
57
+ {
58
+ "id": "block-1764120045607-0.3191495481339375",
59
+ "text": "Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worlds Worlds.Hello Worlds.Hello Worlds.HellossssssssHello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello"
60
+ },
61
+ {
62
+ "id": "block-1764120096263-0.7501005669073386",
63
+ "text": "Worlds.Hello Worlds.Worlds.He.He"
64
+ },
65
+ {
66
+ "id": "block-1764120098441-0.995989493698729",
67
+ "text": "Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worlds Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worldsWorlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Worlds.Hello Hello worlds"
68
+ }
69
+ ],
70
+ "created_at": "2025-11-26T01:24:16.750Z",
71
+ "updated_at": "2025-11-26T01:24:16.750Z"
72
+ }
Document-Editor--master/server/data/92a85bb2-938a-41b0-9c69-1c36919da23c.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "92a85bb2-938a-41b0-9c69-1c36919da23c",
3
+ "title": "My Document",
4
+ "content": [
5
+ {
6
+ "id": "1764117603114",
7
+ "text": "Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world."
8
+ },
9
+ {
10
+ "id": "block-1764117826759-0.5817665547194508",
11
+ "text": "Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world."
12
+ },
13
+ {
14
+ "id": "block-1764117828658-0.037742925663947324",
15
+ "text": "Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world."
16
+ },
17
+ {
18
+ "id": "block-1764117833450-0.4200369076586793",
19
+ "text": "Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world."
20
+ },
21
+ {
22
+ "id": "block-1764117844918-0.9777791714244016",
23
+ "text": "Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world."
24
+ },
25
+ {
26
+ "id": "block-1764117837034-0.7351909718040901",
27
+ "text": "Hello world.Hello world.Hello world.Hello world.Hello world.Hello world.Hello world."
28
+ }
29
+ ],
30
+ "created_at": "2025-11-26T00:44:55.001Z",
31
+ "updated_at": "2025-11-26T00:44:55.001Z"
32
+ }
Document-Editor--master/server/data/d8b5e4ae-e7c1-4d7b-a70d-45f7e9d5f2d8.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "d8b5e4ae-e7c1-4d7b-a70d-45f7e9d5f2d8",
3
+ "title": "My Document",
4
+ "content": [
5
+ {
6
+ "id": "1764106395642",
7
+ "text": "hello sdfsfsdgaefawrw sejrqworj;wqroej"
8
+ },
9
+ {
10
+ "id": "1764106670364",
11
+ "text": "import express from 'express';\nimport cors from 'cors';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { v4 as uuidv4 } from 'uuid';\n\n// Get __dirname in ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst app = express();\napp.use(cors());\napp.use(express.json());\n\nconst DATA_DIR = path.join(__dirname, 'data');\n\n// Ensure data directory exists\nfs.mkdir(DATA_DIR, { recursive: true }).catch(() => {});\n\n// POST /documents\napp.post('/documents', async (req, res) => {\n try {\n const doc = {\n id: uuidv4(),\n title: req.body.title || 'Untitled',\n content: req.body.content,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString()\n };\n \n await fs.writeFile(\n path.join(DATA_DIR, `${doc.id}.json`),\n JSON.stringify(doc, null, 2)\n );\n \n res.json({ id: doc.id });\n } catch (err) {\n console.error('Save error:', err);\n res.status(500).json({ error: 'Save failed', message: err.message });\n }\n});\n\n// GET /documents/:id\napp.get('/documents/:id', async (req, res) => {\n try {\n const data = await fs.readFile(\n path.join(DATA_DIR, `${req.params.id}.json`),\n 'utf-8'\n );\n res.json(JSON.parse(data));\n } catch (err) {\n console.error('Load error:', err);\n res.status(404).json({ error: 'Document not found' });\n }\n});\n\nconst PORT = 3001;\napp.listen(PORT, () => {\n console.log(`✓ Server running on http://localhost:${PORT}`);\n});import express from 'express';\nimport cors from 'cors';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { v4 as uuidv4 } from 'uuid';\n\n// Get __dirname in ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst app = express();\napp.use(cors());\napp.use(express.json());\n\nconst DATA_DIR = path.join(__dirname, 'data');\n\n// Ensure data directory exists\nfs.mkdir(DATA_DIR, { recursive: true }).catch(() => {});\n\n// POST /documents\napp.post('/documents', async (req, res) => {\n try {\n const doc = {\n id: uuidv4(),\n title: req.body.title || 'Untitled',\n content: req.body.content,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString()\n };\n \n await fs.writeFile(\n path.join(DATA_DIR, `${doc.id}.json`),\n JSON.stringify(doc, null, 2)\n );\n \n res.json({ id: doc.id });\n } catch (err) {\n console.error('Save error:', err);\n res.status(500).json({ error: 'Save failed', message: err.message });\n }\n});\n\n// GET /documents/:id\napp.get('/documents/:id', async (req, res) => {\n try {\n const data = await fs.readFile(\n path.join(DATA_DIR, `${req.params.id}.json`),\n 'utf-8'\n );\n res.json(JSON.parse(data));\n } catch (err) {\n console.error('Load error:', err);\n res.status(404).json({ error: 'Document not found' });\n }\n});\n\nconst PORT = 3001;\napp.listen(PORT, () => {\n console.log(`✓ Server running on http://localhost:${PORT}`);\n});import express from 'express';\nimport cors from 'cors';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { v4 as uuidv4 } from 'uuid';\n\n// Get __dirname in ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst app = express();\napp.use(cors());\napp.use(express.json());\n\nconst DATA_DIR = path.join(__dirname, 'data');\n\n// Ensure data directory exists\nfs.mkdir(DATA_DIR, { recursive: true }).catch(() => {});\n\n// POST /documents\napp.post('/documents', async (req, res) => {\n try {\n const doc = {\n id: uuidv4(),\n title: req.body.title || 'Untitled',\n content: req.body.content,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString()\n };\n \n await fs.writeFile(\n path.join(DATA_DIR, `${doc.id}.json`),\n JSON.stringify(doc, null, 2)\n );\n \n res.json({ id: doc.id });\n } catch (err) {\n console.error('Save error:', err);\n res.status(500).json({ error: 'Save failed', message: err.message });\n }\n});\n\n// GET /documents/:id\napp.get('/documents/:id', async (req, res) => {\n try {\n const data = await fs.readFile(\n path.join(DATA_DIR, `${req.params.id}.json`),\n 'utf-8'\n );\n res.json(JSON.parse(data));\n } catch (err) {\n console.error('Load error:', err);\n res.status(404).json({ error: 'Document not found' });\n }\n});\n\nconst PORT = 3001;\napp.listen(PORT, () => {\n console.log(`✓ Server running on http://localhost:${PORT}`);\n});"
12
+ },
13
+ {
14
+ "id": "1764106670567",
15
+ "text": "ewjklnrjwernflewnfwlrhnweornhi3hni32r"
16
+ }
17
+ ],
18
+ "created_at": "2025-11-25T21:38:43.439Z",
19
+ "updated_at": "2025-11-25T21:38:43.439Z"
20
+ }
Document-Editor--master/server/index.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import { promises as fs } from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { v4 as uuidv4 } from 'uuid';
7
+
8
+ // Get __dirname in ES modules
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const app = express();
13
+ app.use(cors());
14
+ app.use(express.json());
15
+
16
+ const DATA_DIR = path.join(__dirname, 'data');
17
+
18
+ // Ensure data directory exists
19
+ fs.mkdir(DATA_DIR, { recursive: true }).catch(() => {});
20
+
21
+ // POST /documents
22
+ app.post('/documents', async (req, res) => {
23
+ try {
24
+ const doc = {
25
+ id: uuidv4(),
26
+ title: req.body.title || 'Untitled',
27
+ content: req.body.content,
28
+ created_at: new Date().toISOString(),
29
+ updated_at: new Date().toISOString()
30
+ };
31
+
32
+ await fs.writeFile(
33
+ path.join(DATA_DIR, `${doc.id}.json`),
34
+ JSON.stringify(doc, null, 2)
35
+ );
36
+
37
+ res.json({ id: doc.id });
38
+ } catch (err) {
39
+ console.error('Save error:', err);
40
+ res.status(500).json({ error: 'Save failed', message: err.message });
41
+ }
42
+ });
43
+
44
+ // GET /documents/:id
45
+ app.get('/documents/:id', async (req, res) => {
46
+ try {
47
+ const data = await fs.readFile(
48
+ path.join(DATA_DIR, `${req.params.id}.json`),
49
+ 'utf-8'
50
+ );
51
+ res.json(JSON.parse(data));
52
+ } catch (err) {
53
+ console.error('Load error:', err);
54
+ res.status(404).json({ error: 'Document not found' });
55
+ }
56
+ });
57
+
58
+ const PORT = 3001;
59
+ app.listen(PORT, () => {
60
+ console.log(`✓ Server running on http://localhost:${PORT}`);
61
+ });
Document-Editor--master/server/package-lock.json ADDED
@@ -0,0 +1,851 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "server",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "server",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "cors": "^2.8.5",
13
+ "express": "^5.1.0",
14
+ "uuid": "^13.0.0"
15
+ }
16
+ },
17
+ "node_modules/accepts": {
18
+ "version": "2.0.0",
19
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
20
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "mime-types": "^3.0.0",
24
+ "negotiator": "^1.0.0"
25
+ },
26
+ "engines": {
27
+ "node": ">= 0.6"
28
+ }
29
+ },
30
+ "node_modules/body-parser": {
31
+ "version": "2.2.1",
32
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
33
+ "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "bytes": "^3.1.2",
37
+ "content-type": "^1.0.5",
38
+ "debug": "^4.4.3",
39
+ "http-errors": "^2.0.0",
40
+ "iconv-lite": "^0.7.0",
41
+ "on-finished": "^2.4.1",
42
+ "qs": "^6.14.0",
43
+ "raw-body": "^3.0.1",
44
+ "type-is": "^2.0.1"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "funding": {
50
+ "type": "opencollective",
51
+ "url": "https://opencollective.com/express"
52
+ }
53
+ },
54
+ "node_modules/bytes": {
55
+ "version": "3.1.2",
56
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
57
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
58
+ "license": "MIT",
59
+ "engines": {
60
+ "node": ">= 0.8"
61
+ }
62
+ },
63
+ "node_modules/call-bind-apply-helpers": {
64
+ "version": "1.0.2",
65
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
66
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
67
+ "license": "MIT",
68
+ "dependencies": {
69
+ "es-errors": "^1.3.0",
70
+ "function-bind": "^1.1.2"
71
+ },
72
+ "engines": {
73
+ "node": ">= 0.4"
74
+ }
75
+ },
76
+ "node_modules/call-bound": {
77
+ "version": "1.0.4",
78
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
79
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
80
+ "license": "MIT",
81
+ "dependencies": {
82
+ "call-bind-apply-helpers": "^1.0.2",
83
+ "get-intrinsic": "^1.3.0"
84
+ },
85
+ "engines": {
86
+ "node": ">= 0.4"
87
+ },
88
+ "funding": {
89
+ "url": "https://github.com/sponsors/ljharb"
90
+ }
91
+ },
92
+ "node_modules/content-disposition": {
93
+ "version": "1.0.1",
94
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
95
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
96
+ "license": "MIT",
97
+ "engines": {
98
+ "node": ">=18"
99
+ },
100
+ "funding": {
101
+ "type": "opencollective",
102
+ "url": "https://opencollective.com/express"
103
+ }
104
+ },
105
+ "node_modules/content-type": {
106
+ "version": "1.0.5",
107
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
108
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
109
+ "license": "MIT",
110
+ "engines": {
111
+ "node": ">= 0.6"
112
+ }
113
+ },
114
+ "node_modules/cookie": {
115
+ "version": "0.7.2",
116
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
117
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
118
+ "license": "MIT",
119
+ "engines": {
120
+ "node": ">= 0.6"
121
+ }
122
+ },
123
+ "node_modules/cookie-signature": {
124
+ "version": "1.2.2",
125
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
126
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
127
+ "license": "MIT",
128
+ "engines": {
129
+ "node": ">=6.6.0"
130
+ }
131
+ },
132
+ "node_modules/cors": {
133
+ "version": "2.8.5",
134
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
135
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
136
+ "license": "MIT",
137
+ "dependencies": {
138
+ "object-assign": "^4",
139
+ "vary": "^1"
140
+ },
141
+ "engines": {
142
+ "node": ">= 0.10"
143
+ }
144
+ },
145
+ "node_modules/debug": {
146
+ "version": "4.4.3",
147
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
148
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
149
+ "license": "MIT",
150
+ "dependencies": {
151
+ "ms": "^2.1.3"
152
+ },
153
+ "engines": {
154
+ "node": ">=6.0"
155
+ },
156
+ "peerDependenciesMeta": {
157
+ "supports-color": {
158
+ "optional": true
159
+ }
160
+ }
161
+ },
162
+ "node_modules/depd": {
163
+ "version": "2.0.0",
164
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
165
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
166
+ "license": "MIT",
167
+ "engines": {
168
+ "node": ">= 0.8"
169
+ }
170
+ },
171
+ "node_modules/dunder-proto": {
172
+ "version": "1.0.1",
173
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
174
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
175
+ "license": "MIT",
176
+ "dependencies": {
177
+ "call-bind-apply-helpers": "^1.0.1",
178
+ "es-errors": "^1.3.0",
179
+ "gopd": "^1.2.0"
180
+ },
181
+ "engines": {
182
+ "node": ">= 0.4"
183
+ }
184
+ },
185
+ "node_modules/ee-first": {
186
+ "version": "1.1.1",
187
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
188
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
189
+ "license": "MIT"
190
+ },
191
+ "node_modules/encodeurl": {
192
+ "version": "2.0.0",
193
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
194
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
195
+ "license": "MIT",
196
+ "engines": {
197
+ "node": ">= 0.8"
198
+ }
199
+ },
200
+ "node_modules/es-define-property": {
201
+ "version": "1.0.1",
202
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
203
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
204
+ "license": "MIT",
205
+ "engines": {
206
+ "node": ">= 0.4"
207
+ }
208
+ },
209
+ "node_modules/es-errors": {
210
+ "version": "1.3.0",
211
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
212
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
213
+ "license": "MIT",
214
+ "engines": {
215
+ "node": ">= 0.4"
216
+ }
217
+ },
218
+ "node_modules/es-object-atoms": {
219
+ "version": "1.1.1",
220
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
221
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
222
+ "license": "MIT",
223
+ "dependencies": {
224
+ "es-errors": "^1.3.0"
225
+ },
226
+ "engines": {
227
+ "node": ">= 0.4"
228
+ }
229
+ },
230
+ "node_modules/escape-html": {
231
+ "version": "1.0.3",
232
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
233
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
234
+ "license": "MIT"
235
+ },
236
+ "node_modules/etag": {
237
+ "version": "1.8.1",
238
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
239
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
240
+ "license": "MIT",
241
+ "engines": {
242
+ "node": ">= 0.6"
243
+ }
244
+ },
245
+ "node_modules/express": {
246
+ "version": "5.1.0",
247
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
248
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
249
+ "license": "MIT",
250
+ "dependencies": {
251
+ "accepts": "^2.0.0",
252
+ "body-parser": "^2.2.0",
253
+ "content-disposition": "^1.0.0",
254
+ "content-type": "^1.0.5",
255
+ "cookie": "^0.7.1",
256
+ "cookie-signature": "^1.2.1",
257
+ "debug": "^4.4.0",
258
+ "encodeurl": "^2.0.0",
259
+ "escape-html": "^1.0.3",
260
+ "etag": "^1.8.1",
261
+ "finalhandler": "^2.1.0",
262
+ "fresh": "^2.0.0",
263
+ "http-errors": "^2.0.0",
264
+ "merge-descriptors": "^2.0.0",
265
+ "mime-types": "^3.0.0",
266
+ "on-finished": "^2.4.1",
267
+ "once": "^1.4.0",
268
+ "parseurl": "^1.3.3",
269
+ "proxy-addr": "^2.0.7",
270
+ "qs": "^6.14.0",
271
+ "range-parser": "^1.2.1",
272
+ "router": "^2.2.0",
273
+ "send": "^1.1.0",
274
+ "serve-static": "^2.2.0",
275
+ "statuses": "^2.0.1",
276
+ "type-is": "^2.0.1",
277
+ "vary": "^1.1.2"
278
+ },
279
+ "engines": {
280
+ "node": ">= 18"
281
+ },
282
+ "funding": {
283
+ "type": "opencollective",
284
+ "url": "https://opencollective.com/express"
285
+ }
286
+ },
287
+ "node_modules/finalhandler": {
288
+ "version": "2.1.0",
289
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
290
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
291
+ "license": "MIT",
292
+ "dependencies": {
293
+ "debug": "^4.4.0",
294
+ "encodeurl": "^2.0.0",
295
+ "escape-html": "^1.0.3",
296
+ "on-finished": "^2.4.1",
297
+ "parseurl": "^1.3.3",
298
+ "statuses": "^2.0.1"
299
+ },
300
+ "engines": {
301
+ "node": ">= 0.8"
302
+ }
303
+ },
304
+ "node_modules/forwarded": {
305
+ "version": "0.2.0",
306
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
307
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
308
+ "license": "MIT",
309
+ "engines": {
310
+ "node": ">= 0.6"
311
+ }
312
+ },
313
+ "node_modules/fresh": {
314
+ "version": "2.0.0",
315
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
316
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
317
+ "license": "MIT",
318
+ "engines": {
319
+ "node": ">= 0.8"
320
+ }
321
+ },
322
+ "node_modules/function-bind": {
323
+ "version": "1.1.2",
324
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
325
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
326
+ "license": "MIT",
327
+ "funding": {
328
+ "url": "https://github.com/sponsors/ljharb"
329
+ }
330
+ },
331
+ "node_modules/get-intrinsic": {
332
+ "version": "1.3.0",
333
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
334
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
335
+ "license": "MIT",
336
+ "dependencies": {
337
+ "call-bind-apply-helpers": "^1.0.2",
338
+ "es-define-property": "^1.0.1",
339
+ "es-errors": "^1.3.0",
340
+ "es-object-atoms": "^1.1.1",
341
+ "function-bind": "^1.1.2",
342
+ "get-proto": "^1.0.1",
343
+ "gopd": "^1.2.0",
344
+ "has-symbols": "^1.1.0",
345
+ "hasown": "^2.0.2",
346
+ "math-intrinsics": "^1.1.0"
347
+ },
348
+ "engines": {
349
+ "node": ">= 0.4"
350
+ },
351
+ "funding": {
352
+ "url": "https://github.com/sponsors/ljharb"
353
+ }
354
+ },
355
+ "node_modules/get-proto": {
356
+ "version": "1.0.1",
357
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
358
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
359
+ "license": "MIT",
360
+ "dependencies": {
361
+ "dunder-proto": "^1.0.1",
362
+ "es-object-atoms": "^1.0.0"
363
+ },
364
+ "engines": {
365
+ "node": ">= 0.4"
366
+ }
367
+ },
368
+ "node_modules/gopd": {
369
+ "version": "1.2.0",
370
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
371
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
372
+ "license": "MIT",
373
+ "engines": {
374
+ "node": ">= 0.4"
375
+ },
376
+ "funding": {
377
+ "url": "https://github.com/sponsors/ljharb"
378
+ }
379
+ },
380
+ "node_modules/has-symbols": {
381
+ "version": "1.1.0",
382
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
383
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
384
+ "license": "MIT",
385
+ "engines": {
386
+ "node": ">= 0.4"
387
+ },
388
+ "funding": {
389
+ "url": "https://github.com/sponsors/ljharb"
390
+ }
391
+ },
392
+ "node_modules/hasown": {
393
+ "version": "2.0.2",
394
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
395
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
396
+ "license": "MIT",
397
+ "dependencies": {
398
+ "function-bind": "^1.1.2"
399
+ },
400
+ "engines": {
401
+ "node": ">= 0.4"
402
+ }
403
+ },
404
+ "node_modules/http-errors": {
405
+ "version": "2.0.1",
406
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
407
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
408
+ "license": "MIT",
409
+ "dependencies": {
410
+ "depd": "~2.0.0",
411
+ "inherits": "~2.0.4",
412
+ "setprototypeof": "~1.2.0",
413
+ "statuses": "~2.0.2",
414
+ "toidentifier": "~1.0.1"
415
+ },
416
+ "engines": {
417
+ "node": ">= 0.8"
418
+ },
419
+ "funding": {
420
+ "type": "opencollective",
421
+ "url": "https://opencollective.com/express"
422
+ }
423
+ },
424
+ "node_modules/iconv-lite": {
425
+ "version": "0.7.0",
426
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
427
+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
428
+ "license": "MIT",
429
+ "dependencies": {
430
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
431
+ },
432
+ "engines": {
433
+ "node": ">=0.10.0"
434
+ },
435
+ "funding": {
436
+ "type": "opencollective",
437
+ "url": "https://opencollective.com/express"
438
+ }
439
+ },
440
+ "node_modules/inherits": {
441
+ "version": "2.0.4",
442
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
443
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
444
+ "license": "ISC"
445
+ },
446
+ "node_modules/ipaddr.js": {
447
+ "version": "1.9.1",
448
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
449
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
450
+ "license": "MIT",
451
+ "engines": {
452
+ "node": ">= 0.10"
453
+ }
454
+ },
455
+ "node_modules/is-promise": {
456
+ "version": "4.0.0",
457
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
458
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
459
+ "license": "MIT"
460
+ },
461
+ "node_modules/math-intrinsics": {
462
+ "version": "1.1.0",
463
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
464
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
465
+ "license": "MIT",
466
+ "engines": {
467
+ "node": ">= 0.4"
468
+ }
469
+ },
470
+ "node_modules/media-typer": {
471
+ "version": "1.1.0",
472
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
473
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
474
+ "license": "MIT",
475
+ "engines": {
476
+ "node": ">= 0.8"
477
+ }
478
+ },
479
+ "node_modules/merge-descriptors": {
480
+ "version": "2.0.0",
481
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
482
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
483
+ "license": "MIT",
484
+ "engines": {
485
+ "node": ">=18"
486
+ },
487
+ "funding": {
488
+ "url": "https://github.com/sponsors/sindresorhus"
489
+ }
490
+ },
491
+ "node_modules/mime-db": {
492
+ "version": "1.54.0",
493
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
494
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
495
+ "license": "MIT",
496
+ "engines": {
497
+ "node": ">= 0.6"
498
+ }
499
+ },
500
+ "node_modules/mime-types": {
501
+ "version": "3.0.2",
502
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
503
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
504
+ "license": "MIT",
505
+ "dependencies": {
506
+ "mime-db": "^1.54.0"
507
+ },
508
+ "engines": {
509
+ "node": ">=18"
510
+ },
511
+ "funding": {
512
+ "type": "opencollective",
513
+ "url": "https://opencollective.com/express"
514
+ }
515
+ },
516
+ "node_modules/ms": {
517
+ "version": "2.1.3",
518
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
519
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
520
+ "license": "MIT"
521
+ },
522
+ "node_modules/negotiator": {
523
+ "version": "1.0.0",
524
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
525
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
526
+ "license": "MIT",
527
+ "engines": {
528
+ "node": ">= 0.6"
529
+ }
530
+ },
531
+ "node_modules/object-assign": {
532
+ "version": "4.1.1",
533
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
534
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
535
+ "license": "MIT",
536
+ "engines": {
537
+ "node": ">=0.10.0"
538
+ }
539
+ },
540
+ "node_modules/object-inspect": {
541
+ "version": "1.13.4",
542
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
543
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
544
+ "license": "MIT",
545
+ "engines": {
546
+ "node": ">= 0.4"
547
+ },
548
+ "funding": {
549
+ "url": "https://github.com/sponsors/ljharb"
550
+ }
551
+ },
552
+ "node_modules/on-finished": {
553
+ "version": "2.4.1",
554
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
555
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
556
+ "license": "MIT",
557
+ "dependencies": {
558
+ "ee-first": "1.1.1"
559
+ },
560
+ "engines": {
561
+ "node": ">= 0.8"
562
+ }
563
+ },
564
+ "node_modules/once": {
565
+ "version": "1.4.0",
566
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
567
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
568
+ "license": "ISC",
569
+ "dependencies": {
570
+ "wrappy": "1"
571
+ }
572
+ },
573
+ "node_modules/parseurl": {
574
+ "version": "1.3.3",
575
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
576
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
577
+ "license": "MIT",
578
+ "engines": {
579
+ "node": ">= 0.8"
580
+ }
581
+ },
582
+ "node_modules/path-to-regexp": {
583
+ "version": "8.3.0",
584
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
585
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
586
+ "license": "MIT",
587
+ "funding": {
588
+ "type": "opencollective",
589
+ "url": "https://opencollective.com/express"
590
+ }
591
+ },
592
+ "node_modules/proxy-addr": {
593
+ "version": "2.0.7",
594
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
595
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
596
+ "license": "MIT",
597
+ "dependencies": {
598
+ "forwarded": "0.2.0",
599
+ "ipaddr.js": "1.9.1"
600
+ },
601
+ "engines": {
602
+ "node": ">= 0.10"
603
+ }
604
+ },
605
+ "node_modules/qs": {
606
+ "version": "6.14.0",
607
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
608
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
609
+ "license": "BSD-3-Clause",
610
+ "dependencies": {
611
+ "side-channel": "^1.1.0"
612
+ },
613
+ "engines": {
614
+ "node": ">=0.6"
615
+ },
616
+ "funding": {
617
+ "url": "https://github.com/sponsors/ljharb"
618
+ }
619
+ },
620
+ "node_modules/range-parser": {
621
+ "version": "1.2.1",
622
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
623
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
624
+ "license": "MIT",
625
+ "engines": {
626
+ "node": ">= 0.6"
627
+ }
628
+ },
629
+ "node_modules/raw-body": {
630
+ "version": "3.0.2",
631
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
632
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
633
+ "license": "MIT",
634
+ "dependencies": {
635
+ "bytes": "~3.1.2",
636
+ "http-errors": "~2.0.1",
637
+ "iconv-lite": "~0.7.0",
638
+ "unpipe": "~1.0.0"
639
+ },
640
+ "engines": {
641
+ "node": ">= 0.10"
642
+ }
643
+ },
644
+ "node_modules/router": {
645
+ "version": "2.2.0",
646
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
647
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
648
+ "license": "MIT",
649
+ "dependencies": {
650
+ "debug": "^4.4.0",
651
+ "depd": "^2.0.0",
652
+ "is-promise": "^4.0.0",
653
+ "parseurl": "^1.3.3",
654
+ "path-to-regexp": "^8.0.0"
655
+ },
656
+ "engines": {
657
+ "node": ">= 18"
658
+ }
659
+ },
660
+ "node_modules/safer-buffer": {
661
+ "version": "2.1.2",
662
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
663
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
664
+ "license": "MIT"
665
+ },
666
+ "node_modules/send": {
667
+ "version": "1.2.0",
668
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
669
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
670
+ "license": "MIT",
671
+ "dependencies": {
672
+ "debug": "^4.3.5",
673
+ "encodeurl": "^2.0.0",
674
+ "escape-html": "^1.0.3",
675
+ "etag": "^1.8.1",
676
+ "fresh": "^2.0.0",
677
+ "http-errors": "^2.0.0",
678
+ "mime-types": "^3.0.1",
679
+ "ms": "^2.1.3",
680
+ "on-finished": "^2.4.1",
681
+ "range-parser": "^1.2.1",
682
+ "statuses": "^2.0.1"
683
+ },
684
+ "engines": {
685
+ "node": ">= 18"
686
+ }
687
+ },
688
+ "node_modules/serve-static": {
689
+ "version": "2.2.0",
690
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
691
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
692
+ "license": "MIT",
693
+ "dependencies": {
694
+ "encodeurl": "^2.0.0",
695
+ "escape-html": "^1.0.3",
696
+ "parseurl": "^1.3.3",
697
+ "send": "^1.2.0"
698
+ },
699
+ "engines": {
700
+ "node": ">= 18"
701
+ }
702
+ },
703
+ "node_modules/setprototypeof": {
704
+ "version": "1.2.0",
705
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
706
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
707
+ "license": "ISC"
708
+ },
709
+ "node_modules/side-channel": {
710
+ "version": "1.1.0",
711
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
712
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
713
+ "license": "MIT",
714
+ "dependencies": {
715
+ "es-errors": "^1.3.0",
716
+ "object-inspect": "^1.13.3",
717
+ "side-channel-list": "^1.0.0",
718
+ "side-channel-map": "^1.0.1",
719
+ "side-channel-weakmap": "^1.0.2"
720
+ },
721
+ "engines": {
722
+ "node": ">= 0.4"
723
+ },
724
+ "funding": {
725
+ "url": "https://github.com/sponsors/ljharb"
726
+ }
727
+ },
728
+ "node_modules/side-channel-list": {
729
+ "version": "1.0.0",
730
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
731
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
732
+ "license": "MIT",
733
+ "dependencies": {
734
+ "es-errors": "^1.3.0",
735
+ "object-inspect": "^1.13.3"
736
+ },
737
+ "engines": {
738
+ "node": ">= 0.4"
739
+ },
740
+ "funding": {
741
+ "url": "https://github.com/sponsors/ljharb"
742
+ }
743
+ },
744
+ "node_modules/side-channel-map": {
745
+ "version": "1.0.1",
746
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
747
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
748
+ "license": "MIT",
749
+ "dependencies": {
750
+ "call-bound": "^1.0.2",
751
+ "es-errors": "^1.3.0",
752
+ "get-intrinsic": "^1.2.5",
753
+ "object-inspect": "^1.13.3"
754
+ },
755
+ "engines": {
756
+ "node": ">= 0.4"
757
+ },
758
+ "funding": {
759
+ "url": "https://github.com/sponsors/ljharb"
760
+ }
761
+ },
762
+ "node_modules/side-channel-weakmap": {
763
+ "version": "1.0.2",
764
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
765
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
766
+ "license": "MIT",
767
+ "dependencies": {
768
+ "call-bound": "^1.0.2",
769
+ "es-errors": "^1.3.0",
770
+ "get-intrinsic": "^1.2.5",
771
+ "object-inspect": "^1.13.3",
772
+ "side-channel-map": "^1.0.1"
773
+ },
774
+ "engines": {
775
+ "node": ">= 0.4"
776
+ },
777
+ "funding": {
778
+ "url": "https://github.com/sponsors/ljharb"
779
+ }
780
+ },
781
+ "node_modules/statuses": {
782
+ "version": "2.0.2",
783
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
784
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
785
+ "license": "MIT",
786
+ "engines": {
787
+ "node": ">= 0.8"
788
+ }
789
+ },
790
+ "node_modules/toidentifier": {
791
+ "version": "1.0.1",
792
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
793
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
794
+ "license": "MIT",
795
+ "engines": {
796
+ "node": ">=0.6"
797
+ }
798
+ },
799
+ "node_modules/type-is": {
800
+ "version": "2.0.1",
801
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
802
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
803
+ "license": "MIT",
804
+ "dependencies": {
805
+ "content-type": "^1.0.5",
806
+ "media-typer": "^1.1.0",
807
+ "mime-types": "^3.0.0"
808
+ },
809
+ "engines": {
810
+ "node": ">= 0.6"
811
+ }
812
+ },
813
+ "node_modules/unpipe": {
814
+ "version": "1.0.0",
815
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
816
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
817
+ "license": "MIT",
818
+ "engines": {
819
+ "node": ">= 0.8"
820
+ }
821
+ },
822
+ "node_modules/uuid": {
823
+ "version": "13.0.0",
824
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
825
+ "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
826
+ "funding": [
827
+ "https://github.com/sponsors/broofa",
828
+ "https://github.com/sponsors/ctavan"
829
+ ],
830
+ "license": "MIT",
831
+ "bin": {
832
+ "uuid": "dist-node/bin/uuid"
833
+ }
834
+ },
835
+ "node_modules/vary": {
836
+ "version": "1.1.2",
837
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
838
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
839
+ "license": "MIT",
840
+ "engines": {
841
+ "node": ">= 0.8"
842
+ }
843
+ },
844
+ "node_modules/wrappy": {
845
+ "version": "1.0.2",
846
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
847
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
848
+ "license": "ISC"
849
+ }
850
+ }
851
+ }
Document-Editor--master/server/package.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "server",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "description": "",
13
+ "dependencies": {
14
+ "cors": "^2.8.5",
15
+ "express": "^5.1.0",
16
+ "uuid": "^13.0.0"
17
+ }
18
+ }
Document-Editor--master/server/tempCodeRunnerFile.js ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const fs = require('fs').promises;