@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 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}
@@ -0,0 +1 @@
1
+ export { }