@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.
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Pagination Worker
3
+ *
4
+ * ⭐⭐⭐ OffscreenCanvas 기반 HTML 블록 측정
5
+ * - 메인 스레드 블로킹 방지
6
+ * - 백그라운드에서 Canvas TextMetrics 측정
7
+ */
8
+
9
+ // ========================================
10
+ // Canvas 측정 함수
11
+ // ========================================
12
+
13
+ /**
14
+ * HTML 블록의 높이를 Canvas로 측정
15
+ */
16
+ function measureHTMLBlock(html, id, maxWidth) {
17
+ try {
18
+ // ⭐ 간단한 추정: HTML 길이 기반
19
+ // (실제 OffscreenCanvas 구현은 복잡하므로 간단한 추정 사용)
20
+
21
+ const textContent = html.replace(/<[^>]*>/g, ''); // HTML 태그 제거
22
+ const charCount = textContent.length;
23
+
24
+ // 평균 문자 너비 (11pt Calibri 기준)
25
+ const avgCharWidth = 7.5;
26
+ const lineHeight = 21.6; // 11pt × 1.2 × 1.5
27
+
28
+ // 줄 수 계산
29
+ const charsPerLine = Math.floor(maxWidth / avgCharWidth);
30
+ const lines = Math.max(1, Math.ceil(charCount / charsPerLine));
31
+
32
+ // 블록 타입에 따른 추가 패딩
33
+ let paddingTop = 0;
34
+ let paddingBottom = 0;
35
+
36
+ if (html.includes('<h1')) {
37
+ paddingTop = 16;
38
+ paddingBottom = 8;
39
+ } else if (html.includes('<h2')) {
40
+ paddingTop = 12;
41
+ paddingBottom = 6;
42
+ } else if (html.includes('<p')) {
43
+ paddingBottom = 4;
44
+ } else if (html.includes('<table')) {
45
+ paddingTop = 8;
46
+ paddingBottom = 8;
47
+ }
48
+
49
+ const height = lines * lineHeight + paddingTop + paddingBottom;
50
+
51
+ // 블록 타입 감지
52
+ let type = 'paragraph';
53
+ if (html.includes('<h')) type = 'heading';
54
+ else if (html.includes('<ul') || html.includes('<ol') || html.includes('<li')) type = 'list';
55
+ else if (html.includes('<table')) type = 'table';
56
+
57
+ return {
58
+ id,
59
+ type,
60
+ html,
61
+ width: maxWidth,
62
+ height,
63
+ lines,
64
+ };
65
+
66
+ } catch (error) {
67
+ console.error('[Worker] 측정 실패:', error);
68
+ return {
69
+ id,
70
+ type: 'paragraph',
71
+ html,
72
+ width: maxWidth,
73
+ height: 100,
74
+ lines: 5,
75
+ };
76
+ }
77
+ }
78
+
79
+ // ========================================
80
+ // 메시지 핸들러
81
+ // ========================================
82
+
83
+ self.addEventListener('message', (event) => {
84
+ const { type, id, payload } = event.data;
85
+
86
+ if (type === 'MEASURE_BLOCKS') {
87
+ try {
88
+ const { blocks, maxWidth } = payload;
89
+
90
+ console.log(`[Worker] 📏 ${blocks.length}개 블록 측정 시작 (maxWidth=${maxWidth}px)`);
91
+
92
+ // 모든 블록 측정
93
+ const results = blocks.map(block =>
94
+ measureHTMLBlock(block.html, block.id, maxWidth)
95
+ );
96
+
97
+ console.log(`[Worker] ✅ 측정 완료: ${results.length}개 블록`);
98
+
99
+ // 결과 반환
100
+ self.postMessage({
101
+ type: 'MEASURE_COMPLETE',
102
+ id,
103
+ payload: results,
104
+ });
105
+
106
+ } catch (error) {
107
+ console.error('[Worker] ❌ 에러:', error);
108
+ self.postMessage({
109
+ type: 'MEASURE_ERROR',
110
+ id,
111
+ payload: {
112
+ message: error.message || String(error),
113
+ },
114
+ });
115
+ }
116
+ }
117
+ });
118
+
119
+ console.log('[Worker] 🚀 Pagination Worker 시작됨');
package/package.json ADDED
@@ -0,0 +1,151 @@
1
+ {
2
+ "name": "@cepoltd/lpeditor",
3
+ "version": "0.9.0",
4
+ "description": "A production-ready paginated text editor built on Lexical with Google Docs/MS Word style A4 pagination",
5
+ "author": "jworker7@gmail.com",
6
+ "license": "UNLICENSED",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.mts",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/racktime/lpeditor.git"
14
+ },
15
+ "publishConfig": {
16
+ "registry": "https://registry.npmjs.org",
17
+ "access": "public"
18
+ },
19
+ "keywords": [
20
+ "lexical",
21
+ "editor",
22
+ "paginated",
23
+ "rich-text",
24
+ "react"
25
+ ],
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.mts",
29
+ "import": "./dist/index.mjs",
30
+ "require": "./dist/index.js"
31
+ },
32
+ "./style.css": "./dist/index.css",
33
+ "./dist/index.css": "./dist/index.css"
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "scripts": {
39
+ "build": "vite build",
40
+ "build:lib": "vite build --config vite.lib.config.ts",
41
+ "prepublishOnly": "npm run build:lib",
42
+ "dev": "vite"
43
+ },
44
+ "dependencies": {
45
+ "@emotion/react": "11.14.0",
46
+ "@emotion/styled": "11.14.1",
47
+ "@floating-ui/dom": "^1.7.4",
48
+ "@lexical/code": "^0.39.0",
49
+ "@lexical/html": "^0.39.0",
50
+ "@lexical/link": "^0.39.0",
51
+ "@lexical/list": "^0.39.0",
52
+ "@lexical/react": "^0.39.0",
53
+ "@lexical/rich-text": "^0.39.0",
54
+ "@lexical/selection": "^0.39.0",
55
+ "@lexical/table": "^0.39.0",
56
+ "@lexical/utils": "^0.39.0",
57
+ "@lexical/yjs": "^0.39.0",
58
+ "@mui/icons-material": "7.3.5",
59
+ "@mui/material": "7.3.5",
60
+ "@popperjs/core": "2.11.8",
61
+ "@radix-ui/react-accordion": "1.2.3",
62
+ "@radix-ui/react-alert-dialog": "1.1.6",
63
+ "@radix-ui/react-aspect-ratio": "1.1.2",
64
+ "@radix-ui/react-avatar": "1.1.3",
65
+ "@radix-ui/react-checkbox": "1.1.4",
66
+ "@radix-ui/react-collapsible": "1.1.3",
67
+ "@radix-ui/react-context-menu": "2.2.6",
68
+ "@radix-ui/react-dialog": "1.1.6",
69
+ "@radix-ui/react-dropdown-menu": "2.1.6",
70
+ "@radix-ui/react-hover-card": "1.1.6",
71
+ "@radix-ui/react-label": "2.1.2",
72
+ "@radix-ui/react-menubar": "1.1.6",
73
+ "@radix-ui/react-navigation-menu": "1.2.5",
74
+ "@radix-ui/react-popover": "1.1.6",
75
+ "@radix-ui/react-progress": "1.1.2",
76
+ "@radix-ui/react-radio-group": "1.2.3",
77
+ "@radix-ui/react-scroll-area": "1.2.3",
78
+ "@radix-ui/react-select": "2.1.6",
79
+ "@radix-ui/react-separator": "1.1.2",
80
+ "@radix-ui/react-slider": "1.2.3",
81
+ "@radix-ui/react-slot": "1.1.2",
82
+ "@radix-ui/react-switch": "1.1.3",
83
+ "@radix-ui/react-tabs": "1.1.3",
84
+ "@radix-ui/react-toggle": "1.1.2",
85
+ "@radix-ui/react-toggle-group": "1.1.2",
86
+ "@radix-ui/react-tooltip": "1.1.8",
87
+ "class-variance-authority": "0.7.1",
88
+ "clsx": "2.1.1",
89
+ "cmdk": "1.1.1",
90
+ "date-fns": "3.6.0",
91
+ "docx": "^9.5.1",
92
+ "embla-carousel-react": "8.6.0",
93
+ "html-docx-js": "^0.3.1",
94
+ "input-otp": "1.4.2",
95
+ "jspdf": "^4.0.0",
96
+ "jspdf-autotable": "^5.0.7",
97
+ "jszip": "^3.10.1",
98
+ "lexical": "^0.39.0",
99
+ "lucide-react": "0.487.0",
100
+ "mammoth": "^1.11.0",
101
+ "motion": "12.23.24",
102
+ "next-themes": "0.4.6",
103
+ "postcss-import": "^16.1.1",
104
+ "re-resizable": "^6.11.2",
105
+ "react-colorful": "^5.6.1",
106
+ "react-day-picker": "8.10.1",
107
+ "react-dnd": "16.0.1",
108
+ "react-dnd-html5-backend": "16.0.1",
109
+ "react-draggable": "^4.5.0",
110
+ "react-hook-form": "7.55.0",
111
+ "react-moveable": "^0.56.0",
112
+ "react-popper": "2.3.0",
113
+ "react-resizable-panels": "2.1.7",
114
+ "react-responsive-masonry": "2.7.1",
115
+ "react-slick": "0.31.0",
116
+ "recharts": "2.15.2",
117
+ "sonner": "2.0.3",
118
+ "tailwind-merge": "3.2.0",
119
+ "terser": "^5.46.0",
120
+ "tw-animate-css": "1.3.8",
121
+ "vaul": "1.1.2",
122
+ "vite-plugin-dts": "^4.5.4",
123
+ "y-protocols": "^1.0.7",
124
+ "y-websocket": "^3.0.0",
125
+ "yjs": "^13.6.29",
126
+ "zustand": "^5.0.9"
127
+ },
128
+ "devDependencies": {
129
+ "@tailwindcss/vite": "4.1.12",
130
+ "@vitejs/plugin-react": "4.7.0",
131
+ "tailwindcss": "4.1.12",
132
+ "vite": "6.3.5"
133
+ },
134
+ "peerDependencies": {
135
+ "react": "18.3.1",
136
+ "react-dom": "18.3.1"
137
+ },
138
+ "peerDependenciesMeta": {
139
+ "react": {
140
+ "optional": true
141
+ },
142
+ "react-dom": {
143
+ "optional": true
144
+ }
145
+ },
146
+ "pnpm": {
147
+ "overrides": {
148
+ "vite": "6.3.5"
149
+ }
150
+ }
151
+ }