@cepoltd/lpeditor 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -0
- package/dist/index.css +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.js +387 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +98200 -0
- package/dist/index.mjs.map +1 -0
- package/dist/pagination-worker.js +119 -0
- package/package.json +151 -0
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Lexical Paginated Editor
|
|
2
|
+
|
|
3
|
+
A production-ready, paginated text editor built on Lexical with Google Docs/MS Word style pagination.
|
|
4
|
+
|
|
5
|
+
## ⚡ Recent Update: v0.4.0 - Bundle Optimization
|
|
6
|
+
|
|
7
|
+
**CollaborationPlugin (Yjs) 메인 번들에서 분리**
|
|
8
|
+
|
|
9
|
+
메인 번들 크기를 22% 감소시켰습니다 (900 KB → 700 KB).
|
|
10
|
+
실시간 협업 기능은 선택적으로 동적 import하여 사용할 수 있습니다.
|
|
11
|
+
|
|
12
|
+
상세 내용: [BUNDLE_OPTIMIZATION.md](./BUNDLE_OPTIMIZATION.md) | [CHANGE_LOG.md](./CHANGE_LOG.md)
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- ✅ **Automatic Pagination**: Content automatically flows to next page when current page is full
|
|
17
|
+
- ✅ **Multiple Page Formats**: A4, Letter, Legal
|
|
18
|
+
- ✅ **Portrait/Landscape Orientation**: Flexible page orientation
|
|
19
|
+
- ✅ **Smart Content Management**: Only moves newly added content to next page (preserves existing content)
|
|
20
|
+
- ✅ **Manual Page Breaks**: Ctrl+Enter to insert manual page breaks
|
|
21
|
+
- ✅ **Rich Text Formatting**: Bold, Italic, Underline, H1-H3 headings
|
|
22
|
+
- ✅ **Template System**: Meta fields, approval lines, stamps
|
|
23
|
+
- ✅ **Real-time Collaboration**: Optional Yjs-based collaboration (separate bundle)
|
|
24
|
+
- ✅ **Comment System**: Built-in commenting without Yjs
|
|
25
|
+
- ✅ **Print Support**: Optimized for printing
|
|
26
|
+
- ✅ **Performance Optimized**: Debounced pagination with requestAnimationFrame
|
|
27
|
+
- ✅ **Bundle Optimization**: Tree-shaking friendly, code-splitting ready
|
|
28
|
+
|
|
29
|
+
## Project Structure
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
/src
|
|
33
|
+
/lib # Library source (distributable)
|
|
34
|
+
/plugins # Lexical plugins
|
|
35
|
+
/pagination # 📁 All pagination-related code
|
|
36
|
+
paginationCore.ts # 🔒 CORE: Pagination logic - DO NOT MODIFY
|
|
37
|
+
PageNode.ts # Page container node
|
|
38
|
+
PageBreakNode.ts # Manual page break node
|
|
39
|
+
PageManagerPlugin.tsx # Main pagination manager
|
|
40
|
+
PageToolbarPlugin.tsx # Page settings toolbar
|
|
41
|
+
index.ts # Pagination exports
|
|
42
|
+
FormattingToolbarPlugin.tsx # Text formatting toolbar
|
|
43
|
+
/components # React components
|
|
44
|
+
PaginatedEditor.tsx # Main editor component
|
|
45
|
+
/styles # CSS styles
|
|
46
|
+
editor.css # Editor styles
|
|
47
|
+
index.ts # Main library export
|
|
48
|
+
|
|
49
|
+
/app # Demo application
|
|
50
|
+
App.tsx # Demo app entry
|
|
51
|
+
|
|
52
|
+
/styles # Global styles
|
|
53
|
+
fonts.css
|
|
54
|
+
index.css
|
|
55
|
+
tailwind.css
|
|
56
|
+
theme.css
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Basic Usage
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { PaginatedEditor } from './lib';
|
|
71
|
+
|
|
72
|
+
function App() {
|
|
73
|
+
return <PaginatedEditor />;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Custom Integration
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import {
|
|
81
|
+
PageNode,
|
|
82
|
+
PageBreakNode,
|
|
83
|
+
PageManagerPlugin,
|
|
84
|
+
FormattingToolbarPlugin,
|
|
85
|
+
PageToolbarPlugin
|
|
86
|
+
} from './lib';
|
|
87
|
+
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
|
88
|
+
|
|
89
|
+
function CustomEditor() {
|
|
90
|
+
const initialConfig = {
|
|
91
|
+
namespace: 'MyEditor',
|
|
92
|
+
nodes: [PageNode, PageBreakNode, /* ... other nodes */],
|
|
93
|
+
onError: console.error,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<LexicalComposer initialConfig={initialConfig}>
|
|
98
|
+
<PageToolbarPlugin
|
|
99
|
+
pageFormat="a4"
|
|
100
|
+
pageOrientation="portrait"
|
|
101
|
+
onFormatChange={setPageFormat}
|
|
102
|
+
onOrientationChange={setPageOrientation}
|
|
103
|
+
/>
|
|
104
|
+
<FormattingToolbarPlugin />
|
|
105
|
+
{/* ... RichTextPlugin, etc. */}
|
|
106
|
+
<PageManagerPlugin
|
|
107
|
+
pageFormat="a4"
|
|
108
|
+
pageOrientation="portrait"
|
|
109
|
+
/>
|
|
110
|
+
</LexicalComposer>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Core Pagination Logic
|
|
116
|
+
|
|
117
|
+
The pagination system is built around a protected core logic in `/src/lib/plugins/pagination/paginationCore.ts`:
|
|
118
|
+
|
|
119
|
+
### Key Principles
|
|
120
|
+
|
|
121
|
+
1. **Last Child Only**: When a page overflows, only the LAST child (newly added content) is moved to the next page
|
|
122
|
+
2. **Preserve Existing Content**: Existing content never moves unexpectedly
|
|
123
|
+
3. **Automatic Page Creation**: New pages are created automatically when needed
|
|
124
|
+
4. **Debounced Updates**: Pagination calculations are debounced (50ms) for performance
|
|
125
|
+
|
|
126
|
+
### Core Functions
|
|
127
|
+
|
|
128
|
+
- `managePagination()`: Main pagination manager
|
|
129
|
+
- `handlePageOverflow()`: Handles page overflow detection and content movement
|
|
130
|
+
- `measureContentHeight()`: Accurately measures page content height
|
|
131
|
+
- `isNodeOrDescendant()`: Helper for node hierarchy checks
|
|
132
|
+
|
|
133
|
+
⚠️ **WARNING**: The core pagination logic is stable and tested. Modifications may break the pagination system.
|
|
134
|
+
|
|
135
|
+
## Keyboard Shortcuts
|
|
136
|
+
|
|
137
|
+
- **Ctrl+B**: Bold
|
|
138
|
+
- **Ctrl+I**: Italic
|
|
139
|
+
- **Ctrl+U**: Underline
|
|
140
|
+
- **Ctrl+Enter**: Insert manual page break
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm run dev
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Build
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm run build
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Architecture
|
|
155
|
+
|
|
156
|
+
### Node Hierarchy
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
RootNode
|
|
160
|
+
└─ PageNode (physical page container)
|
|
161
|
+
├─ ParagraphNode
|
|
162
|
+
├─ HeadingNode
|
|
163
|
+
├─ PageBreakNode (manual page break)
|
|
164
|
+
└─ ... other content nodes
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Pagination Flow
|
|
168
|
+
|
|
169
|
+
1. User types content
|
|
170
|
+
2. `PageManagerPlugin` listens for content changes (debounced)
|
|
171
|
+
3. `managePagination()` checks all pages for overflow
|
|
172
|
+
4. `handlePageOverflow()` moves last child to next page if needed
|
|
173
|
+
5. Pages are renumbered automatically
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
|
178
|
+
|
|
179
|
+
## Credits
|
|
180
|
+
|
|
181
|
+
Built with [Lexical](https://lexical.dev/) by Meta
|
package/dist/index.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.page-node{color:#000;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.page-node:after{content:"";position:absolute;bottom:0;left:0;right:0;height:96px;pointer-events:all;cursor:default;background:transparent;z-index:1000;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.page-node[contenteditable=true]:after{content:"";position:absolute;bottom:0;left:0;right:0;height:96px;pointer-events:all;cursor:default;background:transparent;z-index:1000}.page-node ul,.page-node ol{list-style:none;padding-left:0;margin:0}.page-node li{margin:0}.page-node hr{height:1px;margin:16px 0;border:none;background-color:#000;box-sizing:content-box}.page-node table{border-collapse:collapse;border-spacing:0}.page-node a{color:inherit;text-decoration:inherit}.page-node strong,.page-node b{font-weight:700}.page-node em,.page-node i{font-style:italic}.page-node u{text-decoration:underline}.page-node div{margin:0;padding:0}.editor-input{position:relative;outline:none;min-height:100%;font-family:맑은 고딕,Calibri,sans-serif;font-size:14.67px;line-height:15.84px;color:#000}.editor-input.input-mode{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:default!important}.editor-input.input-mode [contenteditable=true]{cursor:default!important}.editor-input.input-mode [data-lexical-decorator=true],.editor-input.input-mode .meta-field-node,.editor-input.input-mode .stamp-field-node,.editor-input.input-mode .image-seal-node,.editor-input.input-mode .approval-line-node{user-select:text;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;cursor:pointer!important;pointer-events:auto!important}.meta-field-node[data-display-mode=VIEW]{background-color:transparent!important;color:inherit!important;font-family:inherit!important;font-size:inherit!important;font-weight:inherit!important;font-style:inherit!important;text-decoration:inherit!important;line-height:inherit!important;letter-spacing:inherit!important;padding:0!important;margin:0!important;border:none!important;border-radius:0!important}.editor-text-italic{font-style:italic}[data-lexical-text=true].editor-text-italic,span.editor-text-italic{font-style:oblique;display:inline-block;transform:matrix(1,0,-.17,1,0,0)}.editor-text-bold{font-weight:700}.editor-text-underline{text-decoration:underline}.editor-text-strikethrough{text-decoration:line-through}[data-lexical-text=true].editor-text-bold.editor-text-italic,span.editor-text-bold.editor-text-italic{font-weight:700;font-style:oblique;display:inline-block;transform:matrix(1,0,-.17,1,0,0)}[role=separator][aria-orientation=vertical]{align-self:center;flex-shrink:0}[data-toolbar-group]{position:relative}[data-toolbar-group]:hover:after{content:"";position:absolute;top:-2px;right:-2px;bottom:-2px;left:-2px;border-radius:4px;background:#00000005;pointer-events:none;z-index:-1}@media print{body *{visibility:hidden!important}.pages-container,.pages-container *,.editor-scroll-container{visibility:visible!important}@page{size:A4;margin:0}body{background:#fff!important;margin:0!important;padding:0!important}.editor-scroll-container{overflow:visible!important;padding:0!important;background:#fff!important;display:flex!important;justify-content:center!important;align-items:flex-start!important;position:static!important;transform:none!important}.pages-container{transform:none!important;width:210mm!important;margin:0 auto!important;padding:0!important;background:#fff!important;position:static!important;display:flex!important;flex-direction:column!important;align-items:center!important}.page-node{page-break-after:always;page-break-inside:avoid;margin:0 0 5mm!important;padding:25.4mm!important;box-shadow:none!important;border:none!important;background:#fff!important;transform:none!important;width:210mm!important;height:297mm!important;min-height:297mm!important;max-height:297mm!important;display:block!important;box-sizing:border-box!important;overflow:hidden!important;position:relative!important}.page-node:last-child{page-break-after:auto;margin-bottom:0!important}.page-number{display:none!important}.editor-input{background:#fff!important;border:none!important;box-shadow:none!important;overflow:visible!important}p,h1,h2,h3,h4,h5,h6,li,td{page-break-inside:avoid;orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}table{page-break-inside:avoid}img{page-break-inside:avoid;max-width:100%!important}a{color:inherit;text-decoration:underline}a[href]:after{content:none}}.editor-table{border-collapse:collapse;border-spacing:0;overflow-y:scroll;overflow-x:scroll;table-layout:fixed;width:100%!important;max-width:100%;margin:20px 0;transition:box-shadow .2s ease,outline .2s ease,transform .15s ease;position:relative;outline:2px solid transparent;outline-offset:2px}.editor-table:hover{outline:2px solid rgba(59,130,246,.4);outline-offset:2px}.editor-table[data-selected=true]{outline:3px solid #3b82f6!important;outline-offset:2px;box-shadow:0 0 0 6px #3b82f626,0 4px 12px #0000001a;z-index:10;position:relative;cursor:move}.editor-table-cell{border:1px solid #bbb;width:auto;min-width:50px;max-width:none!important;vertical-align:top;text-align:start;padding:6px 8px;position:relative;outline:none;background-color:#fff;transition:background-color .15s ease;cursor:text;word-wrap:break-word;word-break:break-word;overflow-wrap:break-word}.editor-table-cell:focus-within{background-color:#fefefe;border-color:#3b82f6;box-shadow:inset 0 0 0 1px #3b82f6}.editor-table-cell::selection,.editor-table-cell *::selection{background-color:#3b82f6;color:#fff}.editor-table-cell[data-selected]{background-color:inherit!important;border-color:inherit!important;box-shadow:none!important}.editor-table-cell-header{background-color:#f2f3f5;text-align:start;font-weight:600}.editor-table[data-selected=true] .editor-table-cell{background-color:#eff6ff!important}.editor-table[data-selected=true] .editor-table-cell-header{background-color:#dbeafe!important}.editor-list-ul{padding:0;margin:0}.editor-list-ul .editor-listitem{margin:4px 0}.editor-list-ol{padding:0;margin:0}.editor-list-ol .editor-listitem{margin:4px 0}.editor-listitem{margin:4px 0;position:relative}.list-style-disc{list-style-type:disc!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-disc>.editor-listitem,.list-style-disc>li{list-style-type:disc!important;display:list-item!important}.list-style-circle{list-style-type:circle!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-circle>.editor-listitem,.list-style-circle>li{list-style-type:circle!important;display:list-item!important}.list-style-square{list-style-type:square!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-square>.editor-listitem,.list-style-square>li{list-style-type:square!important;display:list-item!important}.list-style-decimal{list-style-type:decimal!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-decimal>.editor-listitem,.list-style-decimal>li{list-style-type:decimal!important;display:list-item!important}.list-style-lower-alpha{list-style-type:lower-alpha!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-lower-alpha>.editor-listitem,.list-style-lower-alpha>li{list-style-type:lower-alpha!important;display:list-item!important}.list-style-upper-alpha{list-style-type:upper-alpha!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-upper-alpha>.editor-listitem,.list-style-upper-alpha>li{list-style-type:upper-alpha!important;display:list-item!important}.list-style-lower-roman{list-style-type:lower-roman!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-lower-roman>.editor-listitem,.list-style-lower-roman>li{list-style-type:lower-roman!important;display:list-item!important}.list-style-upper-roman{list-style-type:upper-roman!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-upper-roman>.editor-listitem,.list-style-upper-roman>li{list-style-type:upper-roman!important;display:list-item!important}.list-style-korean{list-style-type:hangul!important;list-style-position:inside!important;padding-left:0!important;margin:8px 0!important}.list-style-korean>.editor-listitem,.list-style-korean>li{list-style-type:hangul!important;display:list-item!important}.list-style-diamond{list-style-type:none!important;padding-left:0!important;margin:8px 0!important}.list-style-diamond>.editor-listitem,.list-style-diamond>li{list-style-type:none!important;display:list-item!important;padding-left:1.5em!important;position:relative}.list-style-diamond>.editor-listitem:before,.list-style-diamond>li:before{content:"◆ ";color:#555;position:absolute;left:0;display:inline-block;width:1.5em}.list-style-arrow{list-style-type:none!important;padding-left:0!important;margin:8px 0!important}.list-style-arrow>.editor-listitem,.list-style-arrow>li{list-style-type:none!important;display:list-item!important;padding-left:1.5em!important;position:relative}.list-style-arrow>.editor-listitem:before,.list-style-arrow>li:before{content:"➤ ";color:#007bff;position:absolute;left:0;display:inline-block;width:1.5em}.list-style-check{list-style-type:none!important;padding-left:0!important;margin:8px 0!important}.list-style-check>.editor-listitem,.list-style-check>li{list-style-type:none!important;display:list-item!important;padding-left:1.5em!important;position:relative}.list-style-check>.editor-listitem:before,.list-style-check>li:before{content:"✓ ";color:#28a745;position:absolute;left:0;display:inline-block;width:1.5em}.page-node .editor-list-ul,.page-node .editor-list-ol{margin:8px 0}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { }
|