erl-mathtextx-editor 0.2.3 → 0.2.5
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/CHANGELOG.md +14 -36
- package/README.md +453 -448
- package/dist/{CellPropertiesDialogImpl-DZbHddu7.js → CellPropertiesDialogImpl-JG2__RK-.js} +1 -1
- package/dist/{ImageInsertDialog-a8M3Bu9J.js → ImageInsertDialog-BmtubShO.js} +1 -1
- package/dist/{InsertTableDialogImpl-DW6XdEGa.js → InsertTableDialogImpl-B9TOH3MR.js} +1 -1
- package/dist/{LinkDialogImpl-BnwDPNXW.js → LinkDialogImpl-BYg4TdMx.js} +1 -1
- package/dist/{TablePropertiesDialogImpl-0oWYiW79.js → TablePropertiesDialogImpl-BEfLAF3p.js} +1 -1
- package/dist/{TableTemplatesDialogImpl-C4nHsvmY.js → TableTemplatesDialogImpl-aZLEIlHx.js} +1 -1
- package/dist/erl-mathtextx-editor.js +1 -1
- package/dist/erl-mathtextx-editor.umd.cjs +154 -154
- package/dist/{index-CkhZlaGj.js → index-BDLqhZzn.js} +3445 -3428
- package/dist/{index-DY8l0shJ.js → index-BixqDyZL.js} +1 -1
- package/dist/{index-BlrG3sms.js → index-C94Hi5lI.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,448 +1,453 @@
|
|
|
1
|
-
# erl-mathtextx-editor
|
|
2
|
-
|
|
3
|
-
**Visual Math Editor Component — Zero LaTeX Required**
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/erl-mathtextx-editor)
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
|
|
8
|
-
Embeddable visual math editor widget untuk CMS dan platform edukasi. User tidak perlu tahu LaTeX — semua input matematika dilakukan secara visual.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## ✨ Fitur Utama
|
|
13
|
-
|
|
14
|
-
- 🎯 **Visual Math Input** — Insert math langsung inline tanpa dialog (Ctrl+M)
|
|
15
|
-
- 📝 **Rich Text Editor** — Bold, italic, tables, lists, links, images
|
|
16
|
-
- 🧮 **Equation Editor** — Dialog MathType-style dengan tab, grid, KaTeX preview (Edit existing math)
|
|
17
|
-
- 📋 **100+ Formula Templates** — Algebra, calculus, trigonometry, chemistry, matrix
|
|
18
|
-
- 🏆 **Mode Olimpiade** — Toolbar khusus untuk kompetisi matematika dengan simbol set, Greek letters, NT/Combo
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
|
209
|
-
|
|
210
|
-
| **
|
|
211
|
-
| **
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
|
319
|
-
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
327
|
-
| `
|
|
328
|
-
| `
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
|
395
|
-
|
|
396
|
-
| `Ctrl+
|
|
397
|
-
| `Ctrl+
|
|
398
|
-
| `Ctrl+
|
|
399
|
-
| `Ctrl+
|
|
400
|
-
| `
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
|
411
|
-
|
|
412
|
-
| `
|
|
413
|
-
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
- **
|
|
435
|
-
- **
|
|
436
|
-
- **
|
|
437
|
-
- **
|
|
438
|
-
- **
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
1
|
+
# erl-mathtextx-editor
|
|
2
|
+
|
|
3
|
+
**Visual Math Editor Component — Zero LaTeX Required**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/erl-mathtextx-editor)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Embeddable visual math editor widget untuk CMS dan platform edukasi. User tidak perlu tahu LaTeX — semua input matematika dilakukan secara visual.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ✨ Fitur Utama
|
|
13
|
+
|
|
14
|
+
- 🎯 **Visual Math Input** — Insert math langsung inline tanpa dialog (Ctrl+M)
|
|
15
|
+
- 📝 **Rich Text Editor** — Bold, italic, tables, lists, links, images
|
|
16
|
+
- 🧮 **Equation Editor** — Dialog MathType-style dengan tab, grid, KaTeX preview (Edit existing math)
|
|
17
|
+
- 📋 **100+ Formula Templates** — Algebra, calculus, trigonometry, chemistry, matrix
|
|
18
|
+
- 🏆 **Mode Olimpiade** — Toolbar khusus untuk kompetisi matematika dengan simbol set, Greek letters, NT/Combo
|
|
19
|
+
- 🖼️ **Free-form Image Drag** — Drag gambar ke posisi bebas (pixel-perfect)
|
|
20
|
+
- 📄 **DOCX Import** — Import file .docx via mammoth.js (toolbar + drag-drop)
|
|
21
|
+
- 🗂️ **Google Docs Paste** — Paste dari Google Docs (equations + document) auto-cleaned
|
|
22
|
+
- 👁️ **Content Viewer** — Read-only renderer dengan KaTeX + DOMPurify
|
|
23
|
+
- 🎨 **Table Editor** — 6 templates, column resize, cell merge/split
|
|
24
|
+
- 🔒 **XSS Protection** — DOMPurify sanitization di paste + serializer
|
|
25
|
+
- 🛡️ **Error Boundary** — Anti white-screen crash protection
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 📦 Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install erl-mathtextx-editor
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Quick Start
|
|
38
|
+
|
|
39
|
+
### 1. Basic Editor
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { MathTextXEditor } from 'erl-mathtextx-editor'
|
|
43
|
+
import 'erl-mathtextx-editor/styles'
|
|
44
|
+
|
|
45
|
+
function App() {
|
|
46
|
+
return (
|
|
47
|
+
<MathTextXEditor
|
|
48
|
+
onChange={(html) => console.log(html)}
|
|
49
|
+
placeholder="Tulis soal di sini..."
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Editor dengan Save Handler
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { useRef } from 'react'
|
|
59
|
+
import { MathTextXEditor, getHTML } from 'erl-mathtextx-editor'
|
|
60
|
+
import 'erl-mathtextx-editor/styles'
|
|
61
|
+
|
|
62
|
+
function QuestionForm() {
|
|
63
|
+
const editorRef = useRef<HTMLDivElement>(null)
|
|
64
|
+
|
|
65
|
+
const handleSave = () => {
|
|
66
|
+
if (!editorRef.current) return
|
|
67
|
+
const html = getHTML(editorRef.current)
|
|
68
|
+
fetch('/api/questions', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({ content: html }),
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div>
|
|
77
|
+
<MathTextXEditor ref={editorRef} placeholder="Tulis pertanyaan..." onSave={handleSave} />
|
|
78
|
+
<button onClick={handleSave}>Simpan</button>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Edit Existing Content
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { MathTextXEditor } from 'erl-mathtextx-editor'
|
|
88
|
+
import 'erl-mathtextx-editor/styles'
|
|
89
|
+
|
|
90
|
+
function EditQuestion({ existingHtml }: { existingHtml: string }) {
|
|
91
|
+
return (
|
|
92
|
+
<MathTextXEditor
|
|
93
|
+
content={existingHtml}
|
|
94
|
+
onChange={(html) => console.log('Updated:', html)}
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 4. Read-Only Mode (Viewer)
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { ContentViewer } from 'erl-mathtextx-editor/viewer'
|
|
104
|
+
import 'erl-mathtextx-editor/viewer/styles'
|
|
105
|
+
|
|
106
|
+
function ExamQuestion({ questionHtml }: { questionHtml: string }) {
|
|
107
|
+
return <ContentViewer content={questionHtml} />
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 5. Multi-Instance (Soal + Pilihan Jawaban)
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { useState } from 'react'
|
|
115
|
+
import { MathTextXEditor } from 'erl-mathtextx-editor'
|
|
116
|
+
import 'erl-mathtextx-editor/styles'
|
|
117
|
+
|
|
118
|
+
function MultipleChoiceForm() {
|
|
119
|
+
const [question, setQuestion] = useState('')
|
|
120
|
+
const [options, setOptions] = useState(
|
|
121
|
+
['A', 'B', 'C', 'D'].map((id) => ({ id, content: '' }))
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div>
|
|
126
|
+
<label>Pertanyaan:</label>
|
|
127
|
+
<MathTextXEditor
|
|
128
|
+
content={question}
|
|
129
|
+
onChange={setQuestion}
|
|
130
|
+
placeholder="Tulis pertanyaan..."
|
|
131
|
+
minHeight="150px"
|
|
132
|
+
/>
|
|
133
|
+
{options.map((opt) => (
|
|
134
|
+
<div key={opt.id}>
|
|
135
|
+
<label>Opsi {opt.id}:</label>
|
|
136
|
+
<MathTextXEditor
|
|
137
|
+
content={opt.content}
|
|
138
|
+
onChange={(html) => setOptions((prev) => prev.map((o) => o.id === opt.id ? { ...o, content: html } : o))}
|
|
139
|
+
placeholder={`Jawaban ${opt.id}...`}
|
|
140
|
+
minHeight="60px"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 6. Next.js (App Router)
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
'use client'
|
|
153
|
+
import dynamic from 'next/dynamic'
|
|
154
|
+
import 'erl-mathtextx-editor/styles'
|
|
155
|
+
|
|
156
|
+
const MathTextXEditor = dynamic(
|
|
157
|
+
() => import('erl-mathtextx-editor').then((mod) => mod.MathTextXEditor),
|
|
158
|
+
{ ssr: false }
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
export default function EditorPage() {
|
|
162
|
+
return (
|
|
163
|
+
<MathTextXEditor
|
|
164
|
+
placeholder="Tulis soal matematika..."
|
|
165
|
+
onChange={(html) => console.log(html)}
|
|
166
|
+
minHeight="300px"
|
|
167
|
+
/>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 7. Vite + React
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { MathTextXEditor } from 'erl-mathtextx-editor'
|
|
176
|
+
import 'erl-mathtextx-editor/styles'
|
|
177
|
+
|
|
178
|
+
function App() {
|
|
179
|
+
return (
|
|
180
|
+
<MathTextXEditor
|
|
181
|
+
placeholder="Tulis soal..."
|
|
182
|
+
onChange={(html) => console.log(html)}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
export default App
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 🎛️ Toolbar Modes
|
|
192
|
+
|
|
193
|
+
Editor menyediakan 3 preset toolbar yang bisa dipilih via prop `toolbarMode`:
|
|
194
|
+
|
|
195
|
+
### Basic
|
|
196
|
+
Toolbar standar dengan tombol format teks dasar, insert media, dan math formula.
|
|
197
|
+
|
|
198
|
+
### Advanced
|
|
199
|
+
Toolbar lengkap dengan font family, text color, alignment, table operations, superscript/subscript, chemistry formula, dan formatting lanjutan.
|
|
200
|
+
|
|
201
|
+
### Olimpiade
|
|
202
|
+
Toolbar minimal untuk kompetisi matematika — hanya tombol esensial:
|
|
203
|
+
|
|
204
|
+
| Grup | Tombol |
|
|
205
|
+
|---|---|
|
|
206
|
+
| **Undo/Redo** | Undo, Redo |
|
|
207
|
+
| **Format** | Bold, Italic, Underline |
|
|
208
|
+
| **Structure** | Paragraph, H1, H2 |
|
|
209
|
+
| **Lists** | Bullet List, Ordered List, Outdent, Indent |
|
|
210
|
+
| **Math** | Math Formula (inline), Block Math |
|
|
211
|
+
| **Actions** | Remove Format |
|
|
212
|
+
|
|
213
|
+
**Math Toolbar** di mode Olimpiade menampilkan section khusus: Basic, Relation, Set, Greek, Structure — tanpa section Calc yang kurang relevan untuk olimpiade.
|
|
214
|
+
|
|
215
|
+
### Custom Mode
|
|
216
|
+
Jika preset tidak sesuai, Anda bisa atur sendiri via `internalToolbarMode` state atau kontrol toolbar secara manual menggunakan komponen terpisah (`MainToolbar`, `MathToolbar`, `MathTypeDialog`).
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 🧰 Image Upload
|
|
221
|
+
|
|
222
|
+
Editor mendukung upload gambar dari: **Insert dialog**, **drag-drop**, **paste dari clipboard**, dan **DOCX import**. Semuanya melalui satu callback `onImageUpload`.
|
|
223
|
+
|
|
224
|
+
### Basic Upload
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { MathTextXEditor } from 'erl-mathtextx-editor'
|
|
228
|
+
import 'erl-mathtextx-editor/styles'
|
|
229
|
+
|
|
230
|
+
function EditorWithUpload() {
|
|
231
|
+
const handleImageUpload = async (file: File): Promise<string> => {
|
|
232
|
+
const formData = new FormData()
|
|
233
|
+
formData.append('image', file)
|
|
234
|
+
const res = await fetch('/api/upload', { method: 'POST', body: formData })
|
|
235
|
+
if (!res.ok) throw new Error('Upload failed: ' + res.statusText)
|
|
236
|
+
const data = await res.json()
|
|
237
|
+
return data.url // Expected: { "url": "https://cdn.example.com/img.jpg" }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<MathTextXEditor
|
|
242
|
+
placeholder="Tulis soal..."
|
|
243
|
+
onImageUpload={handleImageUpload}
|
|
244
|
+
/>
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Re-upload Gambar dari Paste (Google Docs / Website)
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
import { MathTextXEditor } from 'erl-mathtextx-editor'
|
|
253
|
+
import 'erl-mathtextx-editor/styles'
|
|
254
|
+
|
|
255
|
+
function EditorWithPasteReupload() {
|
|
256
|
+
const handleImageUpload = async (file: File): Promise<string> => {
|
|
257
|
+
const formData = new FormData()
|
|
258
|
+
formData.append('image', file)
|
|
259
|
+
const res = await fetch('/api/upload', { method: 'POST', body: formData })
|
|
260
|
+
return (await res.json()).url
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const handleBeforePasteHTML = async (html: string): Promise<string> => {
|
|
264
|
+
// Download + re-upload external images from pasted HTML
|
|
265
|
+
const imgRegex = /<img\s+[^>]*src="([^"]+)"[^>]*>/gi
|
|
266
|
+
const replacements: Array<[string, string]> = []
|
|
267
|
+
let match
|
|
268
|
+
|
|
269
|
+
while ((match = imgRegex.exec(html)) !== null) {
|
|
270
|
+
const src = match[1]
|
|
271
|
+
if (src.startsWith('data:') || src.includes('cdn.example.com')) continue
|
|
272
|
+
try {
|
|
273
|
+
const blob = await (await fetch(src)).blob()
|
|
274
|
+
const file = new File([blob], 'image.' + (blob.type.split('/')[1] || 'jpg'))
|
|
275
|
+
replacements.push([src, await handleImageUpload(file)])
|
|
276
|
+
} catch { console.warn('Skip image:', src) }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let result = html
|
|
280
|
+
for (const [oldSrc, newSrc] of replacements) result = result.replaceAll(oldSrc, newSrc)
|
|
281
|
+
return result
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<MathTextXEditor
|
|
286
|
+
placeholder="Tulis soal..."
|
|
287
|
+
onImageUpload={handleImageUpload}
|
|
288
|
+
onBeforePasteHTML={handleBeforePasteHTML}
|
|
289
|
+
/>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Base64 Fallback (Tanpa Server)
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
const handleImageUpload = async (file: File): Promise<string> => {
|
|
298
|
+
return new Promise((resolve, reject) => {
|
|
299
|
+
const reader = new FileReader()
|
|
300
|
+
reader.onload = () => resolve(reader.result as string)
|
|
301
|
+
reader.onerror = reject
|
|
302
|
+
reader.readAsDataURL(file)
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
// ⚠️ Base64 hanya cocok untuk gambar < 100KB. Untuk produksi, gunakan upload ke server.
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 📋 Props API
|
|
311
|
+
|
|
312
|
+
### MathTextXEditor
|
|
313
|
+
|
|
314
|
+
| Prop | Type | Default | Description |
|
|
315
|
+
|------|------|---------|-------------|
|
|
316
|
+
| `content` | `string` | `''` | Initial HTML content |
|
|
317
|
+
| `onChange` | `(html: string) => void` | — | Callback on content change (debounced 150ms) |
|
|
318
|
+
| `onSave` | `(html: string) => void` | — | Callback on Ctrl+S |
|
|
319
|
+
| `onImageUpload` | `(file: File) => Promise<string>` | — | Custom image upload handler |
|
|
320
|
+
| `onBeforePasteHTML` | `(html: string) => Promise<string>` | — | Transform pasted HTML (re-upload images) |
|
|
321
|
+
| `placeholder` | `string` | `'Tulis soal...'` | Placeholder text |
|
|
322
|
+
| `minHeight` | `string` | `'200px'` | Minimum editor height |
|
|
323
|
+
| `maxHeight` | `string` | — | Maximum editor height |
|
|
324
|
+
| `autoFocus` | `boolean` | `false` | Auto-focus editor on mount |
|
|
325
|
+
| `toolbarMode` | `'basic' \| 'advanced' \| 'olimpiade'` | `'basic'` | Toolbar preset |
|
|
326
|
+
| `onInsertBlockMath` | `() => void` | — | Callback to insert a block-level math node directly |
|
|
327
|
+
| `editable` | `boolean` | `true` | Set false for read-only mode |
|
|
328
|
+
| `className` | `string` | — | Additional CSS class |
|
|
329
|
+
|
|
330
|
+
### ContentViewer
|
|
331
|
+
|
|
332
|
+
| Prop | Type | Default | Description |
|
|
333
|
+
|------|------|---------|-------------|
|
|
334
|
+
| `content` | `string` | — | HTML content to render (required) |
|
|
335
|
+
| `className` | `string` | — | Additional CSS class |
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 📦 Exports
|
|
340
|
+
|
|
341
|
+
### Main Package (`erl-mathtextx-editor`)
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
import {
|
|
345
|
+
MathTextXEditor, // Main editor component
|
|
346
|
+
ContentViewer, // Read-only renderer
|
|
347
|
+
MathTypeDialog, // Standalone equation editor dialog
|
|
348
|
+
TemplatePanel, // Standalone formula template panel
|
|
349
|
+
MainToolbar, // Standalone text formatting toolbar
|
|
350
|
+
MathToolbar, // Standalone math symbols toolbar
|
|
351
|
+
SymbolPalette, // Standalone symbol picker
|
|
352
|
+
WordCount, // Status bar word/char counter
|
|
353
|
+
LinkDialog, // Standalone link insert/edit dialog
|
|
354
|
+
ImageEditDialog, // Standalone image edit dialog
|
|
355
|
+
InsertTableDialog, // Standalone table insert dialog
|
|
356
|
+
TableMenu, // Standalone table context menu
|
|
357
|
+
CellPropertiesDialog, // Standalone cell properties dialog
|
|
358
|
+
TablePropertiesDialog, // Standalone table properties dialog
|
|
359
|
+
TableTemplatesDialog, // Standalone table template picker
|
|
360
|
+
MathInlineNode, // TipTap inline math extension
|
|
361
|
+
MathBlockNode, // TipTap block math extension
|
|
362
|
+
getHTML, // Serialize editor → HTML string
|
|
363
|
+
getJSON, // Serialize editor → JSON
|
|
364
|
+
sanitizeCKEditorHTML, // Clean CKEditor HTML for compatibility
|
|
365
|
+
toCompatibleHTML, // Convert to CKEditor-compatible format
|
|
366
|
+
createExtensions, // Create TipTap extensions programmatically
|
|
367
|
+
mathTemplates, // Template definitions
|
|
368
|
+
getTemplatesByLevel, // Filter templates by education level
|
|
369
|
+
getTemplatesByCategory,// Filter templates by category
|
|
370
|
+
getTemplateCategories, // Get all template categories
|
|
371
|
+
countWords, // Word count utility
|
|
372
|
+
countCharacters, // Character count utility
|
|
373
|
+
getTemplateStyles, // Table template CSS generator
|
|
374
|
+
} from 'erl-mathtextx-editor'
|
|
375
|
+
|
|
376
|
+
import 'erl-mathtextx-editor/styles'
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Viewer Only (`erl-mathtextx-editor/viewer`)
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
import { ContentViewer } from 'erl-mathtextx-editor/viewer'
|
|
383
|
+
import 'erl-mathtextx-editor/viewer/styles'
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## ⌨️ Keyboard Shortcuts
|
|
389
|
+
|
|
390
|
+
| Shortcut | Action |
|
|
391
|
+
|----------|--------|
|
|
392
|
+
| `Ctrl+B` | Bold |
|
|
393
|
+
| `Ctrl+I` | Italic |
|
|
394
|
+
| `Ctrl+U` | Underline |
|
|
395
|
+
| `Ctrl+K` | Insert/edit link |
|
|
396
|
+
| `Ctrl+M` | Insert inline math directly (without dialog) |
|
|
397
|
+
| `Ctrl+Shift+T` | Insert table |
|
|
398
|
+
| `Ctrl+S` | Save document |
|
|
399
|
+
| `Shift+Ctrl+V` | Paste as plain text |
|
|
400
|
+
| `Esc` (equation editor dialog) | Close dialog |
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## ⚠️ Troubleshooting
|
|
405
|
+
|
|
406
|
+
| Error | Solution |
|
|
407
|
+
|---|---|
|
|
408
|
+
| `Can't resolve 'erl-mathtextx-editor'` | `npm install erl-mathtextx-editor` |
|
|
409
|
+
| `Can't resolve 'erl-mathtextx-editor/styles'` | Version ≥ 0.1.3. Alternatif: `import 'erl-mathtextx-editor/dist/assets/erl-mathtextx-editor.css'` |
|
|
410
|
+
| `MathTextXEditor is not a function` | Pastikan React ≥ 18 (`npm ls react`) |
|
|
411
|
+
| `window is not defined` (Next.js) | Gunakan `dynamic()` dengan `{ ssr: false }` |
|
|
412
|
+
| `Unexpected token 'export'` (CRA) | Webpack config: `resolve.mainFields: ['main', 'module']` |
|
|
413
|
+
| MathLive fonts error | Set `(window as any).MATHLIVE_FONTS_PATH = '/fonts'` + copy font ke `public/fonts/` |
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## ✅ Verified Import Paths
|
|
418
|
+
|
|
419
|
+
| Import | Resolves to |
|
|
420
|
+
|---|---|
|
|
421
|
+
| `erl-mathtextx-editor` | `dist/erl-mathtextx-editor.js` |
|
|
422
|
+
| `erl-mathtextx-editor/styles` | `dist/assets/erl-mathtextx-editor.css` |
|
|
423
|
+
| `erl-mathtextx-editor/viewer` | `dist/viewer.js` |
|
|
424
|
+
| `erl-mathtextx-editor/viewer/styles` | `dist/viewer-styles.js` |
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 🛠️ Tech Stack
|
|
429
|
+
|
|
430
|
+
- **UI Framework:** React 18+
|
|
431
|
+
- **Editor Engine:** TipTap / ProseMirror
|
|
432
|
+
- **Math Input:** MathLive (WYSIWYG math)
|
|
433
|
+
- **Math Rendering:** KaTeX
|
|
434
|
+
- **DOCX Import:** mammoth.js
|
|
435
|
+
- **XSS Protection:** DOMPurify
|
|
436
|
+
- **Graph Plotting:** Function Plot
|
|
437
|
+
- **Syntax Highlight:** lowlight (100+ languages)
|
|
438
|
+
- **Build:** Vite (Library Mode)
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## 📄 License
|
|
443
|
+
|
|
444
|
+
[MIT](https://github.com/erlangga/richtext-editor-research/blob/main/LICENSE) © Erlangga Team
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## 🔗 Links
|
|
449
|
+
|
|
450
|
+
- **NPM:** [erl-mathtextx-editor](https://www.npmjs.com/package/erl-mathtextx-editor)
|
|
451
|
+
- **Source:** [GitHub Repository](https://github.com/erlangga/richtext-editor-research)
|
|
452
|
+
- **Issues:** [Report Bug](https://github.com/erlangga/richtext-editor-research/issues)
|
|
453
|
+
- **📘 Embed Tutorial:** [docs/EMBED_TUTORIAL.md](https://github.com/erlangga/richtext-editor-research/blob/main/docs/EMBED_TUTORIAL.md)
|