@rhwp/editor 0.6.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.
Files changed (4) hide show
  1. package/README.md +148 -0
  2. package/index.d.ts +46 -0
  3. package/index.js +174 -0
  4. package/package.json +33 -0
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # @rhwp/editor
2
+
3
+ **알(R), 모두의 한글** — 3줄로 HWP 에디터를 웹 페이지에 임베드
4
+
5
+ [![npm](https://img.shields.io/npm/v/@rhwp/editor)](https://www.npmjs.com/package/@rhwp/editor)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ 웹 페이지에 HWP 에디터를 통째로 임베드합니다.
9
+ 메뉴, 툴바, 서식, 표 편집 — rhwp-studio의 모든 기능을 그대로 사용할 수 있습니다.
10
+
11
+ > **[온라인 데모](https://edwardkim.github.io/rhwp/)** 에서 먼저 체험해보세요.
12
+
13
+ ## 설치
14
+
15
+ ```bash
16
+ npm install @rhwp/editor
17
+ ```
18
+
19
+ ## 빠른 시작 — 3줄이면 충분합니다
20
+
21
+ ```html
22
+ <!DOCTYPE html>
23
+ <html>
24
+ <head>
25
+ <title>내 HWP 에디터</title>
26
+ <style>
27
+ #editor { width: 100%; height: 100vh; }
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <div id="editor"></div>
32
+ <script type="module">
33
+ import { createEditor } from '@rhwp/editor';
34
+
35
+ const editor = await createEditor('#editor');
36
+ </script>
37
+ </body>
38
+ </html>
39
+ ```
40
+
41
+ 이것만으로 메뉴바, 툴바, 편집 영역, 상태 표시줄이 포함된 완전한 HWP 에디터가 표시됩니다.
42
+
43
+ ## HWP 파일 로드
44
+
45
+ ```javascript
46
+ import { createEditor } from '@rhwp/editor';
47
+
48
+ const editor = await createEditor('#editor');
49
+
50
+ // 파일 선택 또는 fetch로 HWP 데이터 가져오기
51
+ const response = await fetch('document.hwp');
52
+ const buffer = await response.arrayBuffer();
53
+
54
+ // 에디터에 로드
55
+ const result = await editor.loadFile(buffer, 'document.hwp');
56
+ console.log(`${result.pageCount}페이지 로드 완료`);
57
+ ```
58
+
59
+ ## API
60
+
61
+ ### createEditor(container, options?)
62
+
63
+ 에디터를 생성하고 컨테이너에 마운트합니다.
64
+
65
+ ```javascript
66
+ const editor = await createEditor('#editor');
67
+ // 또는
68
+ const editor = await createEditor(document.getElementById('editor'));
69
+ ```
70
+
71
+ **옵션:**
72
+
73
+ | 옵션 | 기본값 | 설명 |
74
+ |------|--------|------|
75
+ | `studioUrl` | `https://edwardkim.github.io/rhwp/` | rhwp-studio URL |
76
+ | `width` | `'100%'` | iframe 너비 |
77
+ | `height` | `'100%'` | iframe 높이 |
78
+
79
+ ### editor.loadFile(data, fileName?)
80
+
81
+ HWP 파일을 로드합니다.
82
+
83
+ ```javascript
84
+ const result = await editor.loadFile(buffer, 'sample.hwp');
85
+ // result = { pageCount: 5 }
86
+ ```
87
+
88
+ ### editor.pageCount()
89
+
90
+ 현재 문서의 페이지 수를 반환합니다.
91
+
92
+ ```javascript
93
+ const count = await editor.pageCount();
94
+ ```
95
+
96
+ ### editor.getPageSvg(page?)
97
+
98
+ 특정 페이지를 SVG 문자열로 렌더링합니다.
99
+
100
+ ```javascript
101
+ const svg = await editor.getPageSvg(0); // 첫 페이지
102
+ ```
103
+
104
+ ### editor.destroy()
105
+
106
+ 에디터를 제거합니다.
107
+
108
+ ```javascript
109
+ editor.destroy();
110
+ ```
111
+
112
+ ## 셀프 호스팅
113
+
114
+ 기본적으로 `https://edwardkim.github.io/rhwp/`에 호스팅된 에디터를 사용합니다.
115
+ 자체 서버에서 호스팅하려면:
116
+
117
+ ```bash
118
+ # rhwp-studio 빌드
119
+ cd rhwp-studio
120
+ npm install
121
+ npx vite build --base=/your-path/
122
+
123
+ # 빌드 결과물(dist/)을 서버에 배포
124
+ ```
125
+
126
+ ```javascript
127
+ const editor = await createEditor('#editor', {
128
+ studioUrl: 'https://your-domain.com/your-path/'
129
+ });
130
+ ```
131
+
132
+ ## 패키지 비교
133
+
134
+ | 패키지 | 용도 |
135
+ |--------|------|
136
+ | **@rhwp/core** | WASM 파서/렌더러 (직접 API 호출) |
137
+ | **@rhwp/editor** | 완전한 에디터 UI (iframe 임베드) |
138
+
139
+ - 뷰어만 필요하면 → `@rhwp/core`
140
+ - 편집 기능이 필요하면 → `@rhwp/editor`
141
+
142
+ ## Notice
143
+
144
+ 본 제품은 한글과컴퓨터의 한글 문서 파일(.hwp) 공개 문서를 참고하여 개발하였습니다.
145
+
146
+ ## License
147
+
148
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @rhwp/editor — HWP 에디터 웹 컴포넌트
3
+ */
4
+
5
+ export interface EditorOptions {
6
+ /** rhwp-studio URL (기본: https://edwardkim.github.io/rhwp/) */
7
+ studioUrl?: string;
8
+ /** iframe 너비 (기본: '100%') */
9
+ width?: string;
10
+ /** iframe 높이 (기본: '100%') */
11
+ height?: string;
12
+ }
13
+
14
+ export interface LoadResult {
15
+ pageCount: number;
16
+ }
17
+
18
+ export declare class RhwpEditor {
19
+ /** HWP 파일을 로드합니다 */
20
+ loadFile(data: ArrayBuffer | Uint8Array, fileName?: string): Promise<LoadResult>;
21
+ /** 현재 문서의 페이지 수를 반환합니다 */
22
+ pageCount(): Promise<number>;
23
+ /** 특정 페이지를 SVG 문자열로 렌더링합니다 */
24
+ getPageSvg(page?: number): Promise<string>;
25
+ /** iframe 엘리먼트를 반환합니다 */
26
+ readonly element: HTMLIFrameElement;
27
+ /** 에디터를 제거합니다 */
28
+ destroy(): void;
29
+ }
30
+
31
+ /**
32
+ * HWP 에디터를 생성하여 지정된 컨테이너에 마운트합니다.
33
+ *
34
+ * @example
35
+ * ```javascript
36
+ * import { createEditor } from '@rhwp/editor';
37
+ *
38
+ * const editor = await createEditor('#container');
39
+ * const resp = await fetch('document.hwp');
40
+ * await editor.loadFile(await resp.arrayBuffer());
41
+ * ```
42
+ */
43
+ export declare function createEditor(
44
+ container: string | HTMLElement,
45
+ options?: EditorOptions,
46
+ ): Promise<RhwpEditor>;
package/index.js ADDED
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @rhwp/editor — HWP 에디터를 iframe으로 임베드
3
+ *
4
+ * 사용법:
5
+ * import { createEditor } from '@rhwp/editor';
6
+ * const editor = await createEditor('#container');
7
+ * await editor.loadFile(buffer, 'document.hwp');
8
+ *
9
+ * 본 제품은 한글과컴퓨터의 한글 문서 파일(.hwp) 공개 문서를 참고하여 개발하였습니다.
10
+ */
11
+
12
+ const DEFAULT_STUDIO_URL = 'https://edwardkim.github.io/rhwp/';
13
+
14
+ let requestId = 0;
15
+
16
+ /**
17
+ * HWP 에디터를 생성하여 지정된 컨테이너에 마운트합니다.
18
+ *
19
+ * @param container - CSS 셀렉터 또는 HTMLElement
20
+ * @param options - 에디터 옵션
21
+ * @returns RhwpEditor 인스턴스
22
+ *
23
+ * @example
24
+ * ```javascript
25
+ * const editor = await createEditor('#editor');
26
+ * await editor.loadFile(hwpBuffer, 'sample.hwp');
27
+ * console.log(await editor.pageCount());
28
+ * ```
29
+ */
30
+ export async function createEditor(container, options = {}) {
31
+ const el = typeof container === 'string'
32
+ ? document.querySelector(container)
33
+ : container;
34
+
35
+ if (!el) {
36
+ throw new Error(`Container not found: ${container}`);
37
+ }
38
+
39
+ const studioUrl = options.studioUrl || DEFAULT_STUDIO_URL;
40
+
41
+ // iframe 생성
42
+ const iframe = document.createElement('iframe');
43
+ iframe.src = studioUrl;
44
+ iframe.style.width = options.width || '100%';
45
+ iframe.style.height = options.height || '100%';
46
+ iframe.style.border = 'none';
47
+ iframe.allow = 'clipboard-read; clipboard-write';
48
+ el.appendChild(iframe);
49
+
50
+ // iframe 로드 대기
51
+ await new Promise((resolve) => {
52
+ iframe.addEventListener('load', resolve, { once: true });
53
+ });
54
+
55
+ // WASM 초기화 대기 (ready 메서드로 확인)
56
+ const editor = new RhwpEditor(iframe);
57
+ await editor._waitReady();
58
+ return editor;
59
+ }
60
+
61
+ /**
62
+ * HWP 에디터 인스턴스
63
+ *
64
+ * iframe 내부의 rhwp-studio와 postMessage로 통신합니다.
65
+ */
66
+ class RhwpEditor {
67
+ constructor(iframe) {
68
+ this._iframe = iframe;
69
+ this._pending = new Map();
70
+
71
+ // 응답 수신 리스너
72
+ window.addEventListener('message', (e) => {
73
+ if (e.data?.type === 'rhwp-response' && e.data.id != null) {
74
+ const resolver = this._pending.get(e.data.id);
75
+ if (resolver) {
76
+ this._pending.delete(e.data.id);
77
+ if (e.data.error) {
78
+ resolver.reject(new Error(e.data.error));
79
+ } else {
80
+ resolver.resolve(e.data.result);
81
+ }
82
+ }
83
+ }
84
+ });
85
+ }
86
+
87
+ /**
88
+ * iframe에 요청을 보내고 응답을 기다립니다.
89
+ * @internal
90
+ */
91
+ _request(method, params = {}) {
92
+ return new Promise((resolve, reject) => {
93
+ const id = ++requestId;
94
+ this._pending.set(id, { resolve, reject });
95
+ this._iframe.contentWindow.postMessage(
96
+ { type: 'rhwp-request', id, method, params },
97
+ '*'
98
+ );
99
+ // 10초 타임아웃
100
+ setTimeout(() => {
101
+ if (this._pending.has(id)) {
102
+ this._pending.delete(id);
103
+ reject(new Error(`Request timeout: ${method}`));
104
+ }
105
+ }, 10000);
106
+ });
107
+ }
108
+
109
+ /** WASM 초기화 완료 대기 @internal */
110
+ async _waitReady() {
111
+ for (let i = 0; i < 30; i++) {
112
+ try {
113
+ const result = await this._request('ready');
114
+ if (result) return;
115
+ } catch {
116
+ // 아직 준비 안 됨 — 재시도
117
+ }
118
+ await new Promise((r) => setTimeout(r, 500));
119
+ }
120
+ throw new Error('Editor initialization timeout');
121
+ }
122
+
123
+ /**
124
+ * HWP 파일을 로드합니다.
125
+ *
126
+ * @param data - HWP 파일의 ArrayBuffer 또는 Uint8Array
127
+ * @param fileName - 파일 이름 (선택)
128
+ * @returns { pageCount: number }
129
+ *
130
+ * @example
131
+ * ```javascript
132
+ * const resp = await fetch('document.hwp');
133
+ * const buffer = await resp.arrayBuffer();
134
+ * const result = await editor.loadFile(buffer, 'document.hwp');
135
+ * console.log(`${result.pageCount}페이지`);
136
+ * ```
137
+ */
138
+ async loadFile(data, fileName = 'document.hwp') {
139
+ const bytes = data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : Array.from(data);
140
+ return this._request('loadFile', { data: bytes, fileName });
141
+ }
142
+
143
+ /**
144
+ * 현재 문서의 페이지 수를 반환합니다.
145
+ * @returns 페이지 수
146
+ */
147
+ async pageCount() {
148
+ return this._request('pageCount');
149
+ }
150
+
151
+ /**
152
+ * 특정 페이지를 SVG 문자열로 렌더링합니다.
153
+ * @param page - 0부터 시작하는 페이지 번호
154
+ * @returns SVG 문자열
155
+ */
156
+ async getPageSvg(page = 0) {
157
+ return this._request('getPageSvg', { page });
158
+ }
159
+
160
+ /**
161
+ * iframe 엘리먼트를 반환합니다.
162
+ */
163
+ get element() {
164
+ return this._iframe;
165
+ }
166
+
167
+ /**
168
+ * 에디터를 제거합니다.
169
+ */
170
+ destroy() {
171
+ this._iframe.remove();
172
+ this._pending.clear();
173
+ }
174
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@rhwp/editor",
3
+ "version": "0.6.0",
4
+ "description": "HWP 에디터 웹 컴포넌트 — iframe 기반 임베드",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "types": "index.d.ts",
8
+ "files": [
9
+ "index.js",
10
+ "index.d.ts"
11
+ ],
12
+ "keywords": [
13
+ "hwp",
14
+ "hwpx",
15
+ "hancom",
16
+ "hangul",
17
+ "한글",
18
+ "editor",
19
+ "viewer",
20
+ "wasm",
21
+ "webassembly"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/edwardkim/rhwp"
26
+ },
27
+ "homepage": "https://edwardkim.github.io/rhwp/",
28
+ "bugs": {
29
+ "url": "https://github.com/edwardkim/rhwp/issues"
30
+ },
31
+ "license": "MIT",
32
+ "author": "Edward Kim"
33
+ }