@lumir-company/editor 0.3.3 β 0.4.1
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 +358 -466
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +64 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,97 +1,82 @@
|
|
|
1
1
|
# LumirEditor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**μ΄λ―Έμ§ μ μ©** BlockNote κΈ°λ° Rich Text μλν°
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@lumir-company/editor)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
> μ΄λ―Έμ§ μ
λ‘λμ μ΅μ νλ κ²½λ μλν°. S3 μ
λ‘λ, νμΌλͺ
컀μ€ν°λ§μ΄μ§, λ‘λ© μ€νΌλ λ΄μ₯.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## λͺ©μ°¨
|
|
13
|
+
|
|
14
|
+
- [νΉμ§](#νΉμ§)
|
|
15
|
+
- [λΉ λ₯Έ μμ](#λΉ λ₯Έ-μμ)
|
|
16
|
+
- [μ΄λ―Έμ§ μ
λ‘λ](#μ΄λ―Έμ§-μ
λ‘λ)
|
|
17
|
+
- [S3 μ
λ‘λ μ€μ ](#1-s3-μ
λ‘λ-κΆμ₯)
|
|
18
|
+
- [νμΌλͺ
컀μ€ν°λ§μ΄μ§](#νμΌλͺ
-컀μ€ν°λ§μ΄μ§)
|
|
19
|
+
- [컀μ€ν
μ
λ‘λ](#2-컀μ€ν
-μ
λ‘λ)
|
|
20
|
+
- [Props API](#props-api)
|
|
21
|
+
- [μ¬μ© μμ ](#μ¬μ©-μμ )
|
|
22
|
+
- [μ€νμΌλ§](#μ€νμΌλ§)
|
|
23
|
+
- [νΈλ¬λΈμν
](#νΈλ¬λΈμν
)
|
|
21
24
|
|
|
22
25
|
---
|
|
23
26
|
|
|
24
|
-
##
|
|
27
|
+
## νΉμ§
|
|
25
28
|
|
|
26
|
-
| νΉμ§
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
| π± **λ°μν** | λͺ¨λ°μΌ/λ°μ€ν¬ν± μ΅μ ν |
|
|
29
|
+
| νΉμ§ | μ€λͺ
|
|
|
30
|
+
| ----------------------- | ------------------------------------------------------ |
|
|
31
|
+
| **μ΄λ―Έμ§ μ μ©** | μ΄λ―Έμ§ μ
λ‘λ/λλκ·Έμ€λλ‘λ§ μ§μ (λΉλμ€/μ€λμ€ μ κ±°) |
|
|
32
|
+
| **S3 μ°λ** | Presigned URL κΈ°λ° S3 μ
λ‘λ λ΄μ₯ |
|
|
33
|
+
| **νμΌλͺ
컀μ€ν°λ§μ΄μ§** | μ
λ‘λ νμΌλͺ
λ³κ²½ μ½λ°± + UUID μλ μΆκ° μ§μ |
|
|
34
|
+
| **λ‘λ© μ€νΌλ** | μ΄λ―Έμ§ μ
λ‘λ μ€ μλ μ€νΌλ νμ |
|
|
35
|
+
| **μ±λ₯ μ΅μ ν** | μ λλ©μ΄μ
λΉνμ±νλ‘ λΉ λ₯Έ λ λλ§ |
|
|
36
|
+
| **TypeScript** | μμ ν νμ
μμ μ± |
|
|
37
|
+
| **ν
λ§ μ§μ** | λΌμ΄νΈ/λ€ν¬ ν
λ§ λ° μ»€μ€ν
ν
λ§ |
|
|
36
38
|
|
|
37
39
|
### μ§μ μ΄λ―Έμ§ νμ
|
|
38
40
|
|
|
39
41
|
```
|
|
40
|
-
PNG, JPEG/JPG, GIF
|
|
42
|
+
PNG, JPEG/JPG, GIF, WebP, BMP, SVG
|
|
41
43
|
```
|
|
42
44
|
|
|
43
45
|
---
|
|
44
46
|
|
|
45
|
-
##
|
|
47
|
+
## λΉ λ₯Έ μμ
|
|
48
|
+
|
|
49
|
+
### 1. μ€μΉ
|
|
46
50
|
|
|
47
51
|
```bash
|
|
48
|
-
# npm
|
|
49
52
|
npm install @lumir-company/editor
|
|
50
|
-
|
|
51
|
-
# yarn
|
|
53
|
+
# λλ
|
|
52
54
|
yarn add @lumir-company/editor
|
|
53
|
-
|
|
54
|
-
# pnpm
|
|
55
|
-
pnpm add @lumir-company/editor
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Peer Dependencies
|
|
59
|
-
|
|
60
|
-
```json
|
|
61
|
-
{
|
|
62
|
-
"react": ">=18.0.0",
|
|
63
|
-
"react-dom": ">=18.0.0"
|
|
64
|
-
}
|
|
65
55
|
```
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
## π λΉ λ₯Έ μμ
|
|
70
|
-
|
|
71
|
-
### 1λ¨κ³: CSS μν¬νΈ (νμ)
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
import "@lumir-company/editor/style.css";
|
|
75
|
-
```
|
|
57
|
+
**νμ Peer Dependencies:**
|
|
76
58
|
|
|
77
|
-
|
|
59
|
+
- `react` >= 18.0.0
|
|
60
|
+
- `react-dom` >= 18.0.0
|
|
78
61
|
|
|
79
|
-
### 2
|
|
62
|
+
### 2. κΈ°λ³Έ μ¬μ©
|
|
80
63
|
|
|
81
64
|
```tsx
|
|
82
65
|
import { LumirEditor } from "@lumir-company/editor";
|
|
83
|
-
import "@lumir-company/editor/style.css";
|
|
66
|
+
import "@lumir-company/editor/style.css"; // νμ!
|
|
84
67
|
|
|
85
68
|
export default function App() {
|
|
86
69
|
return (
|
|
87
|
-
<div className="w-full h-[
|
|
70
|
+
<div className="w-full h-[500px]">
|
|
88
71
|
<LumirEditor onContentChange={(blocks) => console.log(blocks)} />
|
|
89
72
|
</div>
|
|
90
73
|
);
|
|
91
74
|
}
|
|
92
75
|
```
|
|
93
76
|
|
|
94
|
-
|
|
77
|
+
> **μ€μ**: `style.css`λ₯Ό μν¬νΈνμ§ μμΌλ©΄ μλν°κ° μ μ μλνμ§ μμ΅λλ€.
|
|
78
|
+
|
|
79
|
+
### 3. Next.jsμμ μ¬μ©
|
|
95
80
|
|
|
96
81
|
```tsx
|
|
97
82
|
"use client";
|
|
@@ -99,6 +84,7 @@ export default function App() {
|
|
|
99
84
|
import dynamic from "next/dynamic";
|
|
100
85
|
import "@lumir-company/editor/style.css";
|
|
101
86
|
|
|
87
|
+
// SSR λΉνμ±ν νμ
|
|
102
88
|
const LumirEditor = dynamic(
|
|
103
89
|
() =>
|
|
104
90
|
import("@lumir-company/editor").then((m) => ({ default: m.LumirEditor })),
|
|
@@ -107,10 +93,8 @@ const LumirEditor = dynamic(
|
|
|
107
93
|
|
|
108
94
|
export default function EditorPage() {
|
|
109
95
|
return (
|
|
110
|
-
<div className="
|
|
111
|
-
<LumirEditor
|
|
112
|
-
onContentChange={(blocks) => console.log("Content:", blocks)}
|
|
113
|
-
/>
|
|
96
|
+
<div className="h-[500px]">
|
|
97
|
+
<LumirEditor />
|
|
114
98
|
</div>
|
|
115
99
|
);
|
|
116
100
|
}
|
|
@@ -118,108 +102,188 @@ export default function EditorPage() {
|
|
|
118
102
|
|
|
119
103
|
---
|
|
120
104
|
|
|
121
|
-
##
|
|
122
|
-
|
|
123
|
-
### μλν° μ΅μ
(Editor Options)
|
|
124
|
-
|
|
125
|
-
| Prop | νμ
| κΈ°λ³Έκ° | μ€λͺ
|
|
|
126
|
-
| -------------------- | ----------------------------------------- | --------------------------- | ---------------------------------------- |
|
|
127
|
-
| `initialContent` | `DefaultPartialBlock[] \| string` | `undefined` | μ΄κΈ° μ½ν
μΈ (λΈλ‘ λ°°μ΄ λλ JSON λ¬Έμμ΄) |
|
|
128
|
-
| `initialEmptyBlocks` | `number` | `3` | μ΄κΈ° λΉ λΈλ‘ κ°μ |
|
|
129
|
-
| `placeholder` | `string` | `undefined` | 첫 λ²μ§Έ λΈλ‘μ placeholder ν
μ€νΈ |
|
|
130
|
-
| `uploadFile` | `(file: File) => Promise<string>` | `undefined` | 컀μ€ν
νμΌ μ
λ‘λ ν¨μ |
|
|
131
|
-
| `s3Upload` | `S3UploaderConfig` | `undefined` | S3 μ
λ‘λ μ€μ |
|
|
132
|
-
| `tables` | `TableConfig` | `{...}` | ν
μ΄λΈ κΈ°λ₯ μ€μ |
|
|
133
|
-
| `heading` | `{ levels?: (1\|2\|3\|4\|5\|6)[] }` | `{ levels: [1,2,3,4,5,6] }` | ν€λ© λ 벨 μ€μ |
|
|
134
|
-
| `defaultStyles` | `boolean` | `true` | κΈ°λ³Έ μ€νμΌ νμ±ν |
|
|
135
|
-
| `disableExtensions` | `string[]` | `[]` | λΉνμ±νν νμ₯ κΈ°λ₯ λͺ©λ‘ |
|
|
136
|
-
| `tabBehavior` | `"prefer-navigate-ui" \| "prefer-indent"` | `"prefer-navigate-ui"` | ν ν€ λμ |
|
|
137
|
-
| `trailingBlock` | `boolean` | `true` | λ§μ§λ§μ λΉ λΈλ‘ μλ μΆκ° |
|
|
138
|
-
| `allowVideoUpload` | `boolean` | `false` | λΉλμ€ μ
λ‘λ νμ© (κΈ°λ³Έ λΉνμ±) |
|
|
139
|
-
| `allowAudioUpload` | `boolean` | `false` | μ€λμ€ μ
λ‘λ νμ© (κΈ°λ³Έ λΉνμ±) |
|
|
140
|
-
| `allowFileUpload` | `boolean` | `false` | μΌλ° νμΌ μ
λ‘λ νμ© (κΈ°λ³Έ λΉνμ±) |
|
|
141
|
-
|
|
142
|
-
### λ·° μ΅μ
(View Options)
|
|
143
|
-
|
|
144
|
-
| Prop | νμ
| κΈ°λ³Έκ° | μ€λͺ
|
|
|
145
|
-
| ------------------- | ---------------------------------- | --------- | ---------------------------------------------------- |
|
|
146
|
-
| `editable` | `boolean` | `true` | νΈμ§ κ°λ₯ μ¬λΆ |
|
|
147
|
-
| `theme` | `"light" \| "dark" \| ThemeObject` | `"light"` | μλν° ν
λ§ |
|
|
148
|
-
| `formattingToolbar` | `boolean` | `true` | μμ ν΄λ° νμ |
|
|
149
|
-
| `linkToolbar` | `boolean` | `true` | λ§ν¬ ν΄λ° νμ |
|
|
150
|
-
| `sideMenu` | `boolean` | `true` | μ¬μ΄λ λ©λ΄ νμ |
|
|
151
|
-
| `sideMenuAddButton` | `boolean` | `false` | μ¬μ΄λ λ©λ΄ + λ²νΌ νμ (falseμ λλκ·Έ νΈλ€λ§ νμ) |
|
|
152
|
-
| `emojiPicker` | `boolean` | `true` | μ΄λͺ¨μ§ μ νκΈ° νμ |
|
|
153
|
-
| `filePanel` | `boolean` | `true` | νμΌ ν¨λ νμ |
|
|
154
|
-
| `tableHandles` | `boolean` | `true` | ν
μ΄λΈ νΈλ€ νμ |
|
|
155
|
-
| `className` | `string` | `""` | 컨ν
μ΄λ CSS ν΄λμ€ |
|
|
156
|
-
|
|
157
|
-
### μ½λ°± (Callbacks)
|
|
158
|
-
|
|
159
|
-
| Prop | νμ
| μ€λͺ
|
|
|
160
|
-
| ------------------- | ----------------------------------------- | ---------------------- |
|
|
161
|
-
| `onContentChange` | `(blocks: DefaultPartialBlock[]) => void` | μ½ν
μΈ λ³κ²½ μ νΈμΆ |
|
|
162
|
-
| `onSelectionChange` | `() => void` | μ ν μμ λ³κ²½ μ νΈμΆ |
|
|
105
|
+
## μ΄λ―Έμ§ μ
λ‘λ
|
|
163
106
|
|
|
164
|
-
###
|
|
107
|
+
### 1. S3 μ
λ‘λ (κΆμ₯)
|
|
108
|
+
|
|
109
|
+
Presigned URLμ μ¬μ©ν μμ ν S3 μ
λ‘λ λ°©μμ
λλ€.
|
|
165
110
|
|
|
166
111
|
```tsx
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
112
|
+
<LumirEditor
|
|
113
|
+
s3Upload={{
|
|
114
|
+
apiEndpoint: "/api/s3/presigned",
|
|
115
|
+
env: "production",
|
|
116
|
+
path: "blog/images",
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
172
119
|
```
|
|
173
120
|
|
|
174
|
-
|
|
121
|
+
#### S3 νμΌ μ μ₯ κ²½λ‘
|
|
175
122
|
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
123
|
+
```
|
|
124
|
+
{env}/{path}/{filename}
|
|
125
|
+
|
|
126
|
+
μμ:
|
|
127
|
+
production/blog/images/my-photo.png
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### API μλν¬μΈνΈ μλ΅ νμ
|
|
131
|
+
|
|
132
|
+
μλ²λ λ€μ νμμΌλ‘ μλ΅ν΄μΌ ν©λλ€:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"presignedUrl": "https://s3.amazonaws.com/bucket/upload-url",
|
|
137
|
+
"publicUrl": "https://cdn.example.com/production/blog/images/my-photo.png"
|
|
182
138
|
}
|
|
183
139
|
```
|
|
184
140
|
|
|
185
141
|
---
|
|
186
142
|
|
|
187
|
-
|
|
143
|
+
### νμΌλͺ
컀μ€ν°λ§μ΄μ§
|
|
188
144
|
|
|
189
|
-
|
|
145
|
+
μ¬λ¬ μ΄λ―Έμ§λ₯Ό λμμ μ
λ‘λν λ νμΌλͺ
μ€λ³΅μ λ°©μ§νκ³ κ΄λ¦¬νκΈ° μ½κ² λ§λλ κΈ°λ₯μ
λλ€.
|
|
190
146
|
|
|
191
|
-
|
|
147
|
+
> **μ°Έκ³ **: κΈ°λ³Έμ μΌλ‘ νμ₯μλ μλμΌλ‘ λΆμ΅λλ€. `preserveExtension: false`λ‘ μ€μ νλ©΄ νμ₯μλ₯Ό λΆμ΄μ§ μμ΅λλ€.
|
|
148
|
+
|
|
149
|
+
#### μ΅μ
1: UUID μλ μΆκ°
|
|
192
150
|
|
|
193
151
|
```tsx
|
|
194
152
|
<LumirEditor
|
|
195
153
|
s3Upload={{
|
|
196
154
|
apiEndpoint: "/api/s3/presigned",
|
|
197
|
-
env: "
|
|
198
|
-
path: "
|
|
155
|
+
env: "production",
|
|
156
|
+
path: "uploads",
|
|
157
|
+
appendUUID: true, // νμΌλͺ
λ€μ UUID μλ μΆκ°
|
|
199
158
|
}}
|
|
200
|
-
onContentChange={(blocks) => console.log(blocks)}
|
|
201
159
|
/>
|
|
202
160
|
```
|
|
203
161
|
|
|
204
|
-
|
|
162
|
+
**κ²°κ³Ό:**
|
|
205
163
|
|
|
206
164
|
```
|
|
207
|
-
|
|
208
|
-
|
|
165
|
+
μλ³Έ: photo.png
|
|
166
|
+
μ
λ‘λ: photo_550e8400-e29b-41d4-a716-446655440000.png
|
|
209
167
|
```
|
|
210
168
|
|
|
211
|
-
|
|
169
|
+
#### μ΅μ
2: νμΌλͺ
λ³ν μ½λ°±
|
|
212
170
|
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
171
|
+
```tsx
|
|
172
|
+
<LumirEditor
|
|
173
|
+
s3Upload={{
|
|
174
|
+
apiEndpoint: "/api/s3/presigned",
|
|
175
|
+
env: "production",
|
|
176
|
+
path: "uploads",
|
|
177
|
+
fileNameTransform: (nameWithoutExt, file) => {
|
|
178
|
+
// nameWithoutExtλ νμ₯μκ° μ κ±°λ νμΌλͺ
(μ: "photo")
|
|
179
|
+
// νμ₯μλ μλμΌλ‘ λΆμ΅λλ€
|
|
180
|
+
const userId = getCurrentUserId();
|
|
181
|
+
return `${userId}_${nameWithoutExt}`;
|
|
182
|
+
},
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**κ²°κ³Ό:**
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
μλ³Έ: photo.png
|
|
191
|
+
β nameWithoutExt: "photo"
|
|
192
|
+
β λ³ν ν: "user123_photo"
|
|
193
|
+
β μ΅μ’
: user123_photo.png
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### μ΅μ
3: μ‘°ν© μ¬μ© (κΆμ₯)
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
<LumirEditor
|
|
200
|
+
s3Upload={{
|
|
201
|
+
apiEndpoint: "/api/s3/presigned",
|
|
202
|
+
env: "production",
|
|
203
|
+
path: "uploads",
|
|
204
|
+
fileNameTransform: (nameWithoutExt) => `user123_${nameWithoutExt}`,
|
|
205
|
+
appendUUID: true, // λ³ν ν UUID μΆκ°
|
|
206
|
+
}}
|
|
207
|
+
/>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**κ²°κ³Ό:**
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
μλ³Έ: photo.png
|
|
214
|
+
β nameWithoutExt: "photo"
|
|
215
|
+
1. fileNameTransform μ μ©: "user123_photo"
|
|
216
|
+
2. appendUUID μ μ©: "user123_photo_550e8400-e29b-41d4"
|
|
217
|
+
3. νμ₯μ λΆμ΄κΈ°: user123_photo_550e8400-e29b-41d4.png
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### μ€μ μμ : νμμ€ν¬ν + UUID
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
function MyEditor() {
|
|
224
|
+
return (
|
|
225
|
+
<LumirEditor
|
|
226
|
+
s3Upload={{
|
|
227
|
+
apiEndpoint: "/api/s3/presigned",
|
|
228
|
+
env: "production",
|
|
229
|
+
path: "uploads",
|
|
230
|
+
fileNameTransform: (nameWithoutExt, file) => {
|
|
231
|
+
// nameWithoutExtλ μ΄λ―Έ νμ₯μκ° μ κ±°λ¨
|
|
232
|
+
const timestamp = new Date().toISOString().split("T")[0]; // 2024-01-15
|
|
233
|
+
return `${timestamp}_${nameWithoutExt}`;
|
|
234
|
+
},
|
|
235
|
+
appendUUID: true,
|
|
236
|
+
}}
|
|
237
|
+
/>
|
|
238
|
+
);
|
|
217
239
|
}
|
|
218
240
|
```
|
|
219
241
|
|
|
220
|
-
|
|
242
|
+
**κ²°κ³Ό:**
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
μλ³Έ: photo.png
|
|
246
|
+
β nameWithoutExt: "photo"
|
|
247
|
+
1. fileNameTransform: "2024-01-15_photo"
|
|
248
|
+
2. appendUUID: "2024-01-15_photo_550e8400-e29b-41d4"
|
|
249
|
+
3. νμ₯μ λΆμ΄κΈ°: 2024-01-15_photo_550e8400-e29b-41d4.png
|
|
250
|
+
```
|
|
221
251
|
|
|
222
|
-
|
|
252
|
+
#### μ΅μ
4: νμ₯μ μ κ±° (preserveExtension: false)
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
<LumirEditor
|
|
256
|
+
s3Upload={{
|
|
257
|
+
apiEndpoint: "/api/s3/presigned",
|
|
258
|
+
env: "production",
|
|
259
|
+
path: "uploads",
|
|
260
|
+
fileNameTransform: (nameWithoutExt) => `${nameWithoutExt}_custom`,
|
|
261
|
+
preserveExtension: false, // νμ₯μ μ λΆμ
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**κ²°κ³Ό:**
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
μλ³Έ: photo.png
|
|
270
|
+
β nameWithoutExt: "photo"
|
|
271
|
+
β λ³ν ν: "photo_custom"
|
|
272
|
+
β μ΅μ’
: photo_custom (νμ₯μ μμ)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**μ¬μ© μ¬λ‘**: WebP λ³ν λ± μλ²μμ νμ₯μλ₯Ό λ³κ²½νλ κ²½μ°
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
fileNameTransform: (nameWithoutExt) => `${nameWithoutExt}.webp`,
|
|
279
|
+
preserveExtension: false,
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
### 2. 컀μ€ν
μ
λ‘λ
|
|
285
|
+
|
|
286
|
+
μ체 μ
λ‘λ λ‘μ§μ μ¬μ©ν λ:
|
|
223
287
|
|
|
224
288
|
```tsx
|
|
225
289
|
<LumirEditor
|
|
@@ -232,24 +296,22 @@ Presigned URLμ μ¬μ©ν μμ ν S3 μ
λ‘λ λ°©μμ
λλ€.
|
|
|
232
296
|
body: formData,
|
|
233
297
|
});
|
|
234
298
|
|
|
235
|
-
const
|
|
236
|
-
return
|
|
299
|
+
const { url } = await response.json();
|
|
300
|
+
return url; // μ
λ‘λλ μ΄λ―Έμ§ URL λ°ν
|
|
237
301
|
}}
|
|
238
302
|
/>
|
|
239
303
|
```
|
|
240
304
|
|
|
241
|
-
###
|
|
242
|
-
|
|
243
|
-
S3 μ
λ‘λλ₯Ό μ§μ μμ±νμ¬ μ¬μ©ν μ μμ΅λλ€.
|
|
305
|
+
### 3. ν¬νΌ ν¨μ μ¬μ©
|
|
244
306
|
|
|
245
307
|
```tsx
|
|
246
|
-
import {
|
|
308
|
+
import { createS3Uploader } from "@lumir-company/editor";
|
|
247
309
|
|
|
248
|
-
// S3 μ
λ‘λ μμ±
|
|
249
310
|
const s3Uploader = createS3Uploader({
|
|
250
311
|
apiEndpoint: "/api/s3/presigned",
|
|
251
312
|
env: "production",
|
|
252
|
-
path: "
|
|
313
|
+
path: "images",
|
|
314
|
+
appendUUID: true,
|
|
253
315
|
});
|
|
254
316
|
|
|
255
317
|
// μλν°μ μ μ©
|
|
@@ -262,203 +324,97 @@ const imageUrl = await s3Uploader(imageFile);
|
|
|
262
324
|
### μ
λ‘λ μ°μ μμ
|
|
263
325
|
|
|
264
326
|
1. `uploadFile` propμ΄ μμΌλ©΄ μ°μ μ¬μ©
|
|
265
|
-
2. `uploadFile
|
|
327
|
+
2. `uploadFile` μκ³ `s3Upload`κ° μμΌλ©΄ S3 μ
λ‘λ μ¬μ©
|
|
266
328
|
3. λ λ€ μμΌλ©΄ μ
λ‘λ μ€ν¨
|
|
267
329
|
|
|
268
330
|
---
|
|
269
331
|
|
|
270
|
-
##
|
|
332
|
+
## Props API
|
|
271
333
|
|
|
272
|
-
###
|
|
334
|
+
### ν΅μ¬ Props
|
|
273
335
|
|
|
274
|
-
|
|
336
|
+
| Prop | νμ
| κΈ°λ³Έκ° | μ€λͺ
|
|
|
337
|
+
| ----------------- | --------------------------------- | ----------- | ------------------ |
|
|
338
|
+
| `s3Upload` | `S3UploaderConfig` | `undefined` | S3 μ
λ‘λ μ€μ |
|
|
339
|
+
| `uploadFile` | `(file: File) => Promise<string>` | `undefined` | 컀μ€ν
μ
λ‘λ ν¨μ |
|
|
340
|
+
| `onContentChange` | `(blocks) => void` | `undefined` | μ½ν
μΈ λ³κ²½ μ½λ°± |
|
|
341
|
+
| `initialContent` | `Block[] \| string` | `undefined` | μ΄κΈ° μ½ν
μΈ |
|
|
342
|
+
| `editable` | `boolean` | `true` | νΈμ§ κ°λ₯ μ¬λΆ |
|
|
343
|
+
| `theme` | `"light" \| "dark"` | `"light"` | ν
λ§ |
|
|
344
|
+
| `className` | `string` | `""` | CSS ν΄λμ€ |
|
|
275
345
|
|
|
276
|
-
|
|
277
|
-
import { ContentUtils } from "@lumir-company/editor";
|
|
278
|
-
|
|
279
|
-
// JSON λ¬Έμμ΄ μ ν¨μ± κ²μ¦
|
|
280
|
-
const isValid = ContentUtils.isValidJSONString('[{"type":"paragraph"}]');
|
|
281
|
-
// true
|
|
282
|
-
|
|
283
|
-
// JSON λ¬Έμμ΄μ λΈλ‘ λ°°μ΄λ‘ νμ±
|
|
284
|
-
const blocks = ContentUtils.parseJSONContent(jsonString);
|
|
285
|
-
// DefaultPartialBlock[] | null
|
|
286
|
-
|
|
287
|
-
// κΈ°λ³Έ λΉ λΈλ‘ μμ±
|
|
288
|
-
const emptyBlock = ContentUtils.createDefaultBlock();
|
|
289
|
-
// { type: "paragraph", props: {...}, content: [...], children: [] }
|
|
290
|
-
|
|
291
|
-
// μ½ν
μΈ μ ν¨μ± κ²μ¦ λ° κΈ°λ³Έκ° μ€μ
|
|
292
|
-
const validatedContent = ContentUtils.validateContent(content, 3);
|
|
293
|
-
// λΉ μ½ν
μΈ λ©΄ 3κ°μ λΉ λΈλ‘ λ°ν
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### EditorConfig
|
|
297
|
-
|
|
298
|
-
μλν° μ€μ μ νΈλ¦¬ν° ν΄λμ€μ
λλ€.
|
|
299
|
-
|
|
300
|
-
```tsx
|
|
301
|
-
import { EditorConfig } from "@lumir-company/editor";
|
|
302
|
-
|
|
303
|
-
// ν
μ΄λΈ κΈ°λ³Έ μ€μ κ°μ Έμ€κΈ°
|
|
304
|
-
const tableConfig = EditorConfig.getDefaultTableConfig({
|
|
305
|
-
splitCells: true,
|
|
306
|
-
headers: false,
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// ν€λ© κΈ°λ³Έ μ€μ κ°μ Έμ€κΈ°
|
|
310
|
-
const headingConfig = EditorConfig.getDefaultHeadingConfig({
|
|
311
|
-
levels: [1, 2, 3],
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// λΉνμ±ν νμ₯ λͺ©λ‘ μμ±
|
|
315
|
-
const disabledExt = EditorConfig.getDisabledExtensions(
|
|
316
|
-
["codeBlock"], // μ¬μ©μ μ μ λΉνμ± νμ₯
|
|
317
|
-
false, // allowVideo
|
|
318
|
-
false, // allowAudio
|
|
319
|
-
false // allowFile
|
|
320
|
-
);
|
|
321
|
-
// ["codeBlock", "video", "audio", "file"]
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### cn (className μ νΈλ¦¬ν°)
|
|
325
|
-
|
|
326
|
-
μ‘°κ±΄λΆ className κ²°ν© μ νΈλ¦¬ν°μ
λλ€.
|
|
346
|
+
### S3UploaderConfig
|
|
327
347
|
|
|
328
348
|
```tsx
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
)
|
|
337
|
-
|
|
349
|
+
interface S3UploaderConfig {
|
|
350
|
+
// νμ
|
|
351
|
+
apiEndpoint: string; // Presigned URL API μλν¬μΈνΈ
|
|
352
|
+
env: "development" | "production";
|
|
353
|
+
path: string; // S3 μ μ₯ κ²½λ‘
|
|
354
|
+
|
|
355
|
+
// μ ν (νμΌλͺ
컀μ€ν°λ§μ΄μ§)
|
|
356
|
+
fileNameTransform?: (nameWithoutExt: string, file: File) => string; // νμ₯μ μ μΈν μ΄λ¦ λ³ν
|
|
357
|
+
appendUUID?: boolean; // true: νμΌλͺ
λ€μ UUID μΆκ° (νμ₯μ μμ μ½μ
)
|
|
358
|
+
preserveExtension?: boolean; // false: νμ₯μλ₯Ό λΆμ΄μ§ μμ (κΈ°λ³Έ: true)
|
|
359
|
+
}
|
|
338
360
|
```
|
|
339
361
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
## π νμ
μ μ
|
|
343
|
-
|
|
344
|
-
### μ£Όμ νμ
import
|
|
362
|
+
### μ 체 Props
|
|
345
363
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// μλν° Props
|
|
349
|
-
LumirEditorProps,
|
|
350
|
-
|
|
351
|
-
// μλν° μΈμ€ν΄μ€ νμ
|
|
352
|
-
EditorType,
|
|
353
|
-
|
|
354
|
-
// λΈλ‘ κ΄λ ¨ νμ
|
|
355
|
-
DefaultPartialBlock,
|
|
356
|
-
DefaultBlockSchema,
|
|
357
|
-
DefaultInlineContentSchema,
|
|
358
|
-
DefaultStyleSchema,
|
|
359
|
-
PartialBlock,
|
|
360
|
-
BlockNoteEditor,
|
|
361
|
-
} from "@lumir-company/editor";
|
|
362
|
-
|
|
363
|
-
import type { S3UploaderConfig } from "@lumir-company/editor";
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### LumirEditorProps μ 체 μΈν°νμ΄μ€
|
|
364
|
+
<details>
|
|
365
|
+
<summary>μ 체 Props 보기</summary>
|
|
367
366
|
|
|
368
367
|
```tsx
|
|
369
368
|
interface LumirEditorProps {
|
|
370
|
-
// ===
|
|
371
|
-
initialContent?: DefaultPartialBlock[] | string;
|
|
372
|
-
initialEmptyBlocks?: number;
|
|
373
|
-
|
|
374
|
-
uploadFile?: (file: File) => Promise<string>;
|
|
369
|
+
// === μλν° μ€μ ===
|
|
370
|
+
initialContent?: DefaultPartialBlock[] | string; // μ΄κΈ° μ½ν
μΈ (λΈλ‘ λ°°μ΄ λλ JSON λ¬Έμμ΄)
|
|
371
|
+
initialEmptyBlocks?: number; // μ΄κΈ° λΉ λΈλ‘ κ°μ (κΈ°λ³Έ: 3)
|
|
372
|
+
uploadFile?: (file: File) => Promise<string>; // 컀μ€ν
νμΌ μ
λ‘λ ν¨μ
|
|
375
373
|
s3Upload?: {
|
|
376
374
|
apiEndpoint: string;
|
|
377
375
|
env: "development" | "production";
|
|
378
376
|
path: string;
|
|
377
|
+
fileNameTransform?: (nameWithoutExt: string, file: File) => string; // νμ₯μ μ μΈν μ΄λ¦ λ³ν
|
|
378
|
+
appendUUID?: boolean; // UUID μλ μΆκ° (νμ₯μ μ)
|
|
379
|
+
preserveExtension?: boolean; // νμ₯μ μλ λΆμ΄κΈ° (κΈ°λ³Έ: true)
|
|
379
380
|
};
|
|
380
|
-
allowVideoUpload?: boolean;
|
|
381
|
-
allowAudioUpload?: boolean;
|
|
382
|
-
allowFileUpload?: boolean;
|
|
383
|
-
tables?: {
|
|
384
|
-
splitCells?: boolean;
|
|
385
|
-
cellBackgroundColor?: boolean;
|
|
386
|
-
cellTextColor?: boolean;
|
|
387
|
-
headers?: boolean;
|
|
388
|
-
};
|
|
389
|
-
heading?: { levels?: (1 | 2 | 3 | 4 | 5 | 6)[] };
|
|
390
|
-
defaultStyles?: boolean;
|
|
391
|
-
disableExtensions?: string[];
|
|
392
|
-
tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
|
|
393
|
-
trailingBlock?: boolean;
|
|
394
|
-
|
|
395
|
-
// === View Options ===
|
|
396
|
-
editable?: boolean;
|
|
397
|
-
theme?:
|
|
398
|
-
| "light"
|
|
399
|
-
| "dark"
|
|
400
|
-
| Partial<Record<string, unknown>>
|
|
401
|
-
| {
|
|
402
|
-
light: Partial<Record<string, unknown>>;
|
|
403
|
-
dark: Partial<Record<string, unknown>>;
|
|
404
|
-
};
|
|
405
|
-
formattingToolbar?: boolean;
|
|
406
|
-
linkToolbar?: boolean;
|
|
407
|
-
sideMenu?: boolean;
|
|
408
|
-
sideMenuAddButton?: boolean;
|
|
409
|
-
emojiPicker?: boolean;
|
|
410
|
-
filePanel?: boolean;
|
|
411
|
-
tableHandles?: boolean;
|
|
412
|
-
onSelectionChange?: () => void;
|
|
413
|
-
className?: string;
|
|
414
|
-
|
|
415
|
-
// === Callbacks ===
|
|
416
|
-
onContentChange?: (content: DefaultPartialBlock[]) => void;
|
|
417
|
-
}
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
---
|
|
421
|
-
|
|
422
|
-
## π‘ μ¬μ© μμ
|
|
423
381
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
382
|
+
// === μ½λ°± ===
|
|
383
|
+
onContentChange?: (blocks: DefaultPartialBlock[]) => void; // μ½ν
μΈ λ³κ²½ μ νΈμΆ
|
|
384
|
+
onSelectionChange?: () => void; // μ ν μμ λ³κ²½ μ νΈμΆ
|
|
385
|
+
|
|
386
|
+
// κΈ°λ₯ μ€μ
|
|
387
|
+
tables?: TableConfig; // ν
μ΄λΈ κΈ°λ₯ μ€μ (splitCells, cellBackgroundColor λ±)
|
|
388
|
+
heading?: { levels?: (1 | 2 | 3 | 4 | 5 | 6)[] }; // ν€λ© λ 벨 μ€μ (κΈ°λ³Έ: [1,2,3,4,5,6])
|
|
389
|
+
defaultStyles?: boolean; // κΈ°λ³Έ μ€νμΌ νμ±ν (κΈ°λ³Έ: true)
|
|
390
|
+
disableExtensions?: string[]; // λΉνμ±νν νμ₯ κΈ°λ₯ λͺ©λ‘
|
|
391
|
+
tabBehavior?: "prefer-navigate-ui" | "prefer-indent"; // ν ν€ λμ (κΈ°λ³Έ: "prefer-navigate-ui")
|
|
392
|
+
trailingBlock?: boolean; // λ§μ§λ§μ λΉ λΈλ‘ μλ μΆκ° (κΈ°λ³Έ: true)
|
|
393
|
+
|
|
394
|
+
// === UI μ€μ ===
|
|
395
|
+
editable?: boolean; // νΈμ§ κ°λ₯ μ¬λΆ (κΈ°λ³Έ: true)
|
|
396
|
+
theme?: "light" | "dark" | ThemeObject; // μλν° ν
λ§ (κΈ°λ³Έ: "light")
|
|
397
|
+
formattingToolbar?: boolean; // μμ ν΄λ° νμ (κΈ°λ³Έ: true)
|
|
398
|
+
linkToolbar?: boolean; // λ§ν¬ ν΄λ° νμ (κΈ°λ³Έ: true)
|
|
399
|
+
sideMenu?: boolean; // μ¬μ΄λ λ©λ΄ νμ (κΈ°λ³Έ: true)
|
|
400
|
+
sideMenuAddButton?: boolean; // μ¬μ΄λ λ©λ΄ + λ²νΌ νμ (κΈ°λ³Έ: false, λλκ·Έ νΈλ€λ§ νμ)
|
|
401
|
+
emojiPicker?: boolean; // μ΄λͺ¨μ§ μ νκΈ° νμ (κΈ°λ³Έ: true)
|
|
402
|
+
filePanel?: boolean; // νμΌ ν¨λ νμ (κΈ°λ³Έ: true)
|
|
403
|
+
tableHandles?: boolean; // ν
μ΄λΈ νΈλ€ νμ (κΈ°λ³Έ: true)
|
|
404
|
+
className?: string; // 컨ν
μ΄λ CSS ν΄λμ€
|
|
405
|
+
|
|
406
|
+
// λ―Έλμ΄ μ
λ‘λ νμ© μ¬λΆ (κΈ°λ³Έ: λͺ¨λ λΉνμ±)
|
|
407
|
+
allowVideoUpload?: boolean; // λΉλμ€ μ
λ‘λ νμ© (κΈ°λ³Έ: false)
|
|
408
|
+
allowAudioUpload?: boolean; // μ€λμ€ μ
λ‘λ νμ© (κΈ°λ³Έ: false)
|
|
409
|
+
allowFileUpload?: boolean; // μΌλ° νμΌ μ
λ‘λ νμ© (κΈ°λ³Έ: false)
|
|
436
410
|
}
|
|
437
411
|
```
|
|
438
412
|
|
|
439
|
-
|
|
413
|
+
</details>
|
|
440
414
|
|
|
441
|
-
|
|
442
|
-
// λ°©λ² 1: λΈλ‘ λ°°μ΄
|
|
443
|
-
<LumirEditor
|
|
444
|
-
initialContent={[
|
|
445
|
-
{
|
|
446
|
-
type: "heading",
|
|
447
|
-
props: { level: 1 },
|
|
448
|
-
content: [{ type: "text", text: "μ λͺ©μ
λλ€", styles: {} }],
|
|
449
|
-
},
|
|
450
|
-
{
|
|
451
|
-
type: "paragraph",
|
|
452
|
-
content: [{ type: "text", text: "λ³Έλ¬Έ λ΄μ©...", styles: {} }],
|
|
453
|
-
},
|
|
454
|
-
]}
|
|
455
|
-
/>
|
|
415
|
+
---
|
|
456
416
|
|
|
457
|
-
|
|
458
|
-
<LumirEditor
|
|
459
|
-
initialContent='[{"type":"paragraph","content":[{"type":"text","text":"Hello World","styles":{}}]}]'
|
|
460
|
-
/>
|
|
461
|
-
```
|
|
417
|
+
## μ¬μ© μμ
|
|
462
418
|
|
|
463
419
|
### μ½κΈ° μ μ© λͺ¨λ
|
|
464
420
|
|
|
@@ -477,46 +433,6 @@ function BasicEditor() {
|
|
|
477
433
|
<LumirEditor theme="dark" className="bg-gray-900 rounded-lg" />
|
|
478
434
|
```
|
|
479
435
|
|
|
480
|
-
### S3 μ΄λ―Έμ§ μ
λ‘λ
|
|
481
|
-
|
|
482
|
-
```tsx
|
|
483
|
-
<LumirEditor
|
|
484
|
-
s3Upload={{
|
|
485
|
-
apiEndpoint: "/api/s3/presigned",
|
|
486
|
-
env: process.env.NODE_ENV as "development" | "production",
|
|
487
|
-
path: "articles/images",
|
|
488
|
-
}}
|
|
489
|
-
onContentChange={(blocks) => {
|
|
490
|
-
// μ μ₯ λ‘μ§
|
|
491
|
-
saveToDatabase(JSON.stringify(blocks));
|
|
492
|
-
}}
|
|
493
|
-
/>
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
### λ°μν λμμΈ
|
|
497
|
-
|
|
498
|
-
```tsx
|
|
499
|
-
<div className="w-full h-64 md:h-96 lg:h-[600px]">
|
|
500
|
-
<LumirEditor className="h-full rounded-md md:rounded-lg shadow-sm md:shadow-md" />
|
|
501
|
-
</div>
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
### ν
μ΄λΈ μ€μ 컀μ€ν°λ§μ΄μ§
|
|
505
|
-
|
|
506
|
-
```tsx
|
|
507
|
-
<LumirEditor
|
|
508
|
-
tables={{
|
|
509
|
-
splitCells: true,
|
|
510
|
-
cellBackgroundColor: true,
|
|
511
|
-
cellTextColor: false, // μ
ν
μ€νΈ μμ λΉνμ±
|
|
512
|
-
headers: true,
|
|
513
|
-
}}
|
|
514
|
-
heading={{
|
|
515
|
-
levels: [1, 2, 3], // H4-H6 λΉνμ±
|
|
516
|
-
}}
|
|
517
|
-
/>
|
|
518
|
-
```
|
|
519
|
-
|
|
520
436
|
### μ½ν
μΈ μ μ₯ λ° λΆλ¬μ€κΈ°
|
|
521
437
|
|
|
522
438
|
```tsx
|
|
@@ -524,72 +440,31 @@ import { useState, useEffect } from "react";
|
|
|
524
440
|
import { LumirEditor, ContentUtils } from "@lumir-company/editor";
|
|
525
441
|
|
|
526
442
|
function EditorWithSave() {
|
|
527
|
-
const [content, setContent] = useState
|
|
443
|
+
const [content, setContent] = useState("");
|
|
528
444
|
|
|
529
|
-
//
|
|
445
|
+
// λΆλ¬μ€κΈ°
|
|
530
446
|
useEffect(() => {
|
|
531
|
-
const saved = localStorage.getItem("
|
|
447
|
+
const saved = localStorage.getItem("content");
|
|
532
448
|
if (saved && ContentUtils.isValidJSONString(saved)) {
|
|
533
449
|
setContent(saved);
|
|
534
450
|
}
|
|
535
451
|
}, []);
|
|
536
452
|
|
|
537
|
-
//
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
localStorage.setItem("
|
|
453
|
+
// μ μ₯
|
|
454
|
+
const handleChange = (blocks) => {
|
|
455
|
+
const json = JSON.stringify(blocks);
|
|
456
|
+
localStorage.setItem("content", json);
|
|
541
457
|
};
|
|
542
458
|
|
|
543
459
|
return (
|
|
544
|
-
<LumirEditor
|
|
545
|
-
initialContent={content}
|
|
546
|
-
onContentChange={handleContentChange}
|
|
547
|
-
/>
|
|
460
|
+
<LumirEditor initialContent={content} onContentChange={handleChange} />
|
|
548
461
|
);
|
|
549
462
|
}
|
|
550
463
|
```
|
|
551
464
|
|
|
552
465
|
---
|
|
553
466
|
|
|
554
|
-
##
|
|
555
|
-
|
|
556
|
-
### 기본 CSS ꡬ쑰
|
|
557
|
-
|
|
558
|
-
```css
|
|
559
|
-
/* λ©μΈ 컨ν
μ΄λ - μ¬λμ λ©λ΄ μ€λ²νλ‘μ° νμ© */
|
|
560
|
-
.lumirEditor {
|
|
561
|
-
width: 100%;
|
|
562
|
-
height: 100%;
|
|
563
|
-
min-width: 200px;
|
|
564
|
-
overflow: visible; /* μ¬λμ λ©λ΄κ° 컨ν
μ΄λλ₯Ό λμ΄ νμλλλ‘ */
|
|
565
|
-
background-color: #ffffff;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/* μλν° λ΄λΆ μ½ν
μΈ μμ μ€ν¬λ‘€ */
|
|
569
|
-
.lumirEditor .bn-container {
|
|
570
|
-
overflow: auto;
|
|
571
|
-
max-height: 100%;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/* μ¬λμ λ©λ΄ z-index 보μ₯ */
|
|
575
|
-
.bn-suggestion-menu,
|
|
576
|
-
.bn-slash-menu,
|
|
577
|
-
.mantine-Menu-dropdown,
|
|
578
|
-
.mantine-Popover-dropdown {
|
|
579
|
-
z-index: 9999 !important;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/* μλν° λ΄μ© μμ */
|
|
583
|
-
.lumirEditor .bn-editor {
|
|
584
|
-
font-family: "Pretendard", "Noto Sans KR", -apple-system, sans-serif;
|
|
585
|
-
padding: 5px 10px 0 25px;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/* λ¬Έλ¨ λΈλ‘ */
|
|
589
|
-
.lumirEditor [data-content-type="paragraph"] {
|
|
590
|
-
font-size: 14px;
|
|
591
|
-
}
|
|
592
|
-
```
|
|
467
|
+
## μ€νμΌλ§
|
|
593
468
|
|
|
594
469
|
### Tailwind CSSμ ν¨κ» μ¬μ©
|
|
595
470
|
|
|
@@ -605,14 +480,14 @@ import { LumirEditor, cn } from "@lumir-company/editor";
|
|
|
605
480
|
/>;
|
|
606
481
|
```
|
|
607
482
|
|
|
608
|
-
### 컀μ€ν
μ€νμΌ
|
|
483
|
+
### 컀μ€ν
μ€νμΌ
|
|
609
484
|
|
|
610
485
|
```css
|
|
611
486
|
/* globals.css */
|
|
612
487
|
.my-editor .bn-editor {
|
|
613
|
-
padding
|
|
614
|
-
padding-right: 20px;
|
|
488
|
+
padding: 20px 30px;
|
|
615
489
|
font-size: 16px;
|
|
490
|
+
line-height: 1.6;
|
|
616
491
|
}
|
|
617
492
|
|
|
618
493
|
.my-editor [data-content-type="heading"] {
|
|
@@ -627,37 +502,37 @@ import { LumirEditor, cn } from "@lumir-company/editor";
|
|
|
627
502
|
|
|
628
503
|
---
|
|
629
504
|
|
|
630
|
-
##
|
|
505
|
+
## νΈλ¬λΈμν
|
|
631
506
|
|
|
632
507
|
### νμ 체ν¬λ¦¬μ€νΈ
|
|
633
508
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
| Next.js SSR λΉνμ±ν | `dynamic(..., { ssr: false })` μ¬μ© |
|
|
639
|
-
| React λ²μ | 18.0.0 μ΄μ νμ |
|
|
509
|
+
- [ ] CSS μν¬νΈ: `import "@lumir-company/editor/style.css"`
|
|
510
|
+
- [ ] 컨ν
μ΄λ λμ΄ μ€μ : λΆλͺ¨ μμμ λμ΄ μ§μ νμ
|
|
511
|
+
- [ ] Next.js: `dynamic(..., { ssr: false })` μ¬μ©
|
|
512
|
+
- [ ] React λ²μ : 18.0.0 μ΄μ
|
|
640
513
|
|
|
641
|
-
###
|
|
514
|
+
### μμ£Ό λ°μνλ λ¬Έμ
|
|
642
515
|
|
|
643
|
-
#### 1. μλν°κ°
|
|
516
|
+
#### 1. μλν°κ° 보μ΄μ§ μμ
|
|
644
517
|
|
|
645
518
|
```tsx
|
|
646
|
-
//
|
|
519
|
+
// μλͺ»λ¨
|
|
647
520
|
<LumirEditor />;
|
|
648
521
|
|
|
649
|
-
//
|
|
522
|
+
// μ¬λ°λ¦
|
|
650
523
|
import "@lumir-company/editor/style.css";
|
|
651
|
-
<
|
|
524
|
+
<div className="h-[400px]">
|
|
525
|
+
<LumirEditor />
|
|
526
|
+
</div>;
|
|
652
527
|
```
|
|
653
528
|
|
|
654
|
-
#### 2. Next.js
|
|
529
|
+
#### 2. Next.js Hydration μ€λ₯
|
|
655
530
|
|
|
656
531
|
```tsx
|
|
657
|
-
//
|
|
532
|
+
// μλͺ»λ¨
|
|
658
533
|
import { LumirEditor } from "@lumir-company/editor";
|
|
659
534
|
|
|
660
|
-
//
|
|
535
|
+
// μ¬λ°λ¦
|
|
661
536
|
const LumirEditor = dynamic(
|
|
662
537
|
() =>
|
|
663
538
|
import("@lumir-company/editor").then((m) => ({ default: m.LumirEditor })),
|
|
@@ -665,78 +540,95 @@ const LumirEditor = dynamic(
|
|
|
665
540
|
);
|
|
666
541
|
```
|
|
667
542
|
|
|
668
|
-
#### 3.
|
|
543
|
+
#### 3. μ΄λ―Έμ§ μ
λ‘λ μ€ν¨
|
|
669
544
|
|
|
670
545
|
```tsx
|
|
671
|
-
//
|
|
672
|
-
<LumirEditor
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
546
|
+
// uploadFile λλ s3Upload μ€ νλλ λ°λμ μ€μ !
|
|
547
|
+
<LumirEditor
|
|
548
|
+
s3Upload={{
|
|
549
|
+
apiEndpoint: "/api/s3/presigned",
|
|
550
|
+
env: "development",
|
|
551
|
+
path: "images",
|
|
552
|
+
}}
|
|
553
|
+
/>
|
|
678
554
|
```
|
|
679
555
|
|
|
680
|
-
#### 4. μ΄λ―Έμ§ μ
λ‘λ
|
|
556
|
+
#### 4. μ¬λ¬ μ΄λ―Έμ§ μ
λ‘λ μ μ€λ³΅ λ¬Έμ
|
|
681
557
|
|
|
682
558
|
```tsx
|
|
683
|
-
//
|
|
559
|
+
// ν΄κ²°: appendUUID μ¬μ©
|
|
684
560
|
<LumirEditor
|
|
685
|
-
uploadFile={async (file) => {
|
|
686
|
-
// μ
λ‘λ λ‘μ§
|
|
687
|
-
return imageUrl;
|
|
688
|
-
}}
|
|
689
|
-
// λλ
|
|
690
561
|
s3Upload={{
|
|
691
562
|
apiEndpoint: "/api/s3/presigned",
|
|
692
|
-
env: "
|
|
563
|
+
env: "production",
|
|
693
564
|
path: "images",
|
|
565
|
+
appendUUID: true, // κ³ μ ν νμΌλͺ
보μ₯
|
|
694
566
|
}}
|
|
695
567
|
/>
|
|
696
568
|
```
|
|
697
569
|
|
|
698
|
-
|
|
570
|
+
---
|
|
699
571
|
|
|
700
|
-
|
|
701
|
-
2. **ν° μ½ν
μΈ μ²λ¦¬**: μ΄κΈ° μ½ν
μΈ κ° ν΄ κ²½μ° lazy loading κ³ λ €
|
|
702
|
-
3. **μ΄λ―Έμ§ μ΅μ ν**: μ
λ‘λ μ ν΄λΌμ΄μΈνΈμμ μ΄λ―Έμ§ 리μ¬μ΄μ§ κΆμ₯
|
|
572
|
+
## μ νΈλ¦¬ν° API
|
|
703
573
|
|
|
704
|
-
|
|
574
|
+
### ContentUtils
|
|
705
575
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
β β βββ LumirEditor.tsx # λ©μΈ μλν° μ»΄ν¬λνΈ
|
|
718
|
-
β βββ types/
|
|
719
|
-
β β βββ editor.ts # μλν° νμ
μ μ
|
|
720
|
-
β β βββ index.ts # νμ
export
|
|
721
|
-
β βββ utils/
|
|
722
|
-
β β βββ cn.ts # className μ νΈλ¦¬ν°
|
|
723
|
-
β β βββ s3-uploader.ts # S3 μ
λ‘λ
|
|
724
|
-
β βββ index.ts # λ©μΈ export
|
|
725
|
-
β βββ style.css # μμ€ μ€νμΌ
|
|
726
|
-
βββ examples/
|
|
727
|
-
βββ tailwind-integration.md # Tailwind ν΅ν© κ°μ΄λ
|
|
576
|
+
```tsx
|
|
577
|
+
import { ContentUtils } from "@lumir-company/editor";
|
|
578
|
+
|
|
579
|
+
// JSON κ²μ¦
|
|
580
|
+
ContentUtils.isValidJSONString('[{"type":"paragraph"}]'); // true
|
|
581
|
+
|
|
582
|
+
// JSON νμ±
|
|
583
|
+
const blocks = ContentUtils.parseJSONContent(jsonString);
|
|
584
|
+
|
|
585
|
+
// κΈ°λ³Έ λΈλ‘ μμ±
|
|
586
|
+
const emptyBlock = ContentUtils.createDefaultBlock();
|
|
728
587
|
```
|
|
729
588
|
|
|
730
|
-
|
|
589
|
+
### createS3Uploader
|
|
731
590
|
|
|
732
|
-
|
|
591
|
+
```tsx
|
|
592
|
+
import { createS3Uploader } from "@lumir-company/editor";
|
|
733
593
|
|
|
734
|
-
|
|
594
|
+
const uploader = createS3Uploader({
|
|
595
|
+
apiEndpoint: "/api/s3/presigned",
|
|
596
|
+
env: "production",
|
|
597
|
+
path: "uploads",
|
|
598
|
+
appendUUID: true,
|
|
599
|
+
});
|
|
735
600
|
|
|
736
|
-
|
|
601
|
+
// μ§μ μ¬μ©
|
|
602
|
+
const url = await uploader(imageFile);
|
|
603
|
+
```
|
|
737
604
|
|
|
738
|
-
##
|
|
605
|
+
## κ΄λ ¨ λ§ν¬
|
|
739
606
|
|
|
740
|
-
- [GitHub Repository](https://github.com/lumir-company/editor)
|
|
741
607
|
- [npm Package](https://www.npmjs.com/package/@lumir-company/editor)
|
|
742
608
|
- [BlockNote Documentation](https://www.blocknotejs.org/)
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## λ³κ²½ λ‘κ·Έ
|
|
613
|
+
|
|
614
|
+
### v0.4.1
|
|
615
|
+
|
|
616
|
+
- `preserveExtension` prop μΆκ° - νμ₯μ μλ λΆμ΄κΈ° μ μ΄ (κΈ°λ³Έ: true)
|
|
617
|
+
- **μ€μ**: νμΌλͺ
λ³ν μ νμ₯μ μμΉ μμ (νμ₯μκ° νμ 맨 λ€μ μ€λλ‘)
|
|
618
|
+
- **Breaking Change**: `fileNameTransform` νλΌλ―Έν° λ³κ²½ - μ΄μ νμ₯μ μ μΈν νμΌλͺ
λ§ μ λ¬λ¨
|
|
619
|
+
- μ΄μ : `fileNameTransform: (originalName, file) => ...` β originalNameμ νμ₯μ ν¬ν¨
|
|
620
|
+
- λ³κ²½: `fileNameTransform: (nameWithoutExt, file) => ...` β nameWithoutExtμ νμ₯μ μ μΈ
|
|
621
|
+
- νμ₯μ μ κ±° μ¬μ© μ¬λ‘ λ¬Έμν
|
|
622
|
+
- README μμ λ° μ€λͺ
κ°μ
|
|
623
|
+
|
|
624
|
+
### v0.4.0
|
|
625
|
+
|
|
626
|
+
- νμΌλͺ
λ³ν μ½λ°± (`fileNameTransform`) μΆκ°
|
|
627
|
+
- UUID μλ μΆκ° μ΅μ
(`appendUUID`) μΆκ°
|
|
628
|
+
- μ¬λ¬ μ΄λ―Έμ§ λμ μ
λ‘λ μ μ€λ³΅ λ¬Έμ ν΄κ²°
|
|
629
|
+
- λ¬Έμ λν κ°μ
|
|
630
|
+
|
|
631
|
+
### v0.3.3
|
|
632
|
+
|
|
633
|
+
- μλν° μ¬μμ± λ°©μ§ μ΅μ ν
|
|
634
|
+
- νμ
μ μ κ°μ
|