erl-mathtextx-editor 0.1.9 → 0.2.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 (29) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +294 -109
  3. package/dist/{CellPropertiesDialogImpl-DBgs-7H9.js → CellPropertiesDialogImpl-5BptFKaE.js} +1 -1
  4. package/dist/{ContentViewer-CsFSAN_B.js → ContentViewer-RijJ5nlJ.js} +15 -14
  5. package/dist/{ImageInsertDialog-B24KHrgt.js → ImageInsertDialog-Cc7wpIjM.js} +27 -25
  6. package/dist/{InsertTableDialogImpl-B6_PRu5m.js → InsertTableDialogImpl-BJFXRnQX.js} +1 -1
  7. package/dist/{LinkDialogImpl-BTA8u_qQ.js → LinkDialogImpl-Cu032Nc7.js} +1 -1
  8. package/dist/MathTextXEditor.d.ts +1 -1
  9. package/dist/{TablePropertiesDialogImpl-CuRRWS4H.js → TablePropertiesDialogImpl-YBNdKM7k.js} +1 -1
  10. package/dist/{TableTemplatesDialogImpl-CU8seEdV.js → TableTemplatesDialogImpl-P54y5q-u.js} +2 -2
  11. package/dist/assets/erl-mathtextx-editor.css +1 -1
  12. package/dist/assets/viewer.css +1 -1
  13. package/dist/components/ErrorBoundary.d.ts +18 -0
  14. package/dist/components/ImageEditDialog.d.ts +0 -1
  15. package/dist/components/TableMenu.d.ts +4 -1
  16. package/dist/erl-mathtextx-editor.js +2 -2
  17. package/dist/erl-mathtextx-editor.umd.cjs +345 -115
  18. package/dist/index-C9scFI1r.js +16549 -0
  19. package/dist/{index-UCSefQk0.js → index-D0Rzm7Tg.js} +4338 -3839
  20. package/dist/{index-CB1g0gXh.js → index-DJrUW7HG.js} +1 -1
  21. package/dist/toolbar/MainToolbar.d.ts +2 -0
  22. package/dist/toolbar/MathToolbar.d.ts +3 -1
  23. package/dist/types/index.d.ts +3 -1
  24. package/dist/utils/docxImporter.d.ts +31 -0
  25. package/dist/utils/pasteHandler.d.ts +14 -3
  26. package/dist/{viewer-deps-CjbAqdti.js → viewer-deps-BDYoL2Ts.js} +5794 -3489
  27. package/dist/viewer.js +1 -1
  28. package/package.json +2 -1
  29. package/dist/extensions/TableAlignPlugin.d.ts +0 -7
package/CHANGELOG.md CHANGED
@@ -5,6 +5,38 @@ All notable changes to `erl-mathtextx-editor` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2026-05-13
9
+
10
+ ### Added
11
+
12
+ - **🏆 Olimpiade Toolbar Mode** — Toolbar khusus untuk olimpiade/kompetisi matematika
13
+ - Mode switch 3 opsi: Basic / Advanced / Olimpiade
14
+ - Toolbar minimal: Undo/Redo, Bold/Italic/Underline, Paragraph/H1/H2, Lists, Math Formula, Block Math
15
+ - Math Toolbar khusus olimpiade: Basic, Relation, Set (extended), Greek, Structure
16
+ - Block Math button untuk insert display-style math node langsung
17
+ - **Block Math Direct Insert** — Prop `onInsertBlockMath` untuk insert block math tanpa dialog
18
+ - **Empty Inline Math Visibility** — Empty math fields sekarang terlihat sebagai kotak dashed abu-abu
19
+ - `min-width: 50px`, `min-height: 26px` untuk empty math inline
20
+
21
+ ### Fixed
22
+
23
+ - **Math Node Serialization** — Perbaikan kritis: DOMPurify strip `latex` attribute
24
+ - `addAttributes` sekarang render sebagai `data-latex` / `data-display` (bukan `latex` / `display`)
25
+ - `data-display` ditambahkan ke `ALLOWED_ATTR` di DOMPurify
26
+ - Math nodes persist saat tab switch editor ↔ viewer
27
+ - **MathTypeDialog Enter Conflict** — Enter tidak lagi submit dialog
28
+ - Solusi: `\sqrt` + Enter → MathLive auto-convert ke √, dialog tetap terbuka
29
+ - Submit hanya via klik tombol "Sisipkan"
30
+ - **ESLint Cleanup** — Fixed `let` → `const` di MathFieldView, fixed regex escape di MathTypeDialog
31
+ - **Edit Button Overlap** — Tombol Edit pada math node hanya muncul saat hover, hilang saat focus
32
+
33
+ ### Changed
34
+
35
+ - **Toolbar Cleanup** — Advanced mode jadi lebih ringkas
36
+ - Image editing (Edit, Wrap) dipindah ke right-click context menu
37
+ - Table operation buttons dipindah ke Table Operations dropdown
38
+ - **Ctrl+M** — Insert inline math langsung (tanpa dialog MathType)
39
+
8
40
  ## [0.1.9] - 2026-04-01
9
41
 
10
42
  ### Fixed
package/README.md CHANGED
@@ -11,18 +11,18 @@ Embeddable visual math editor widget untuk CMS dan platform edukasi. User tidak
11
11
 
12
12
  ## ✨ Fitur Utama
13
13
 
14
- - 🎯 **Visual Math Keyboard** — Klik simbol, operator, template formula
14
+ - 🎯 **Visual Math Input** — Insert math langsung inline tanpa dialog (Ctrl+M)
15
15
  - 📝 **Rich Text Editor** — Bold, italic, tables, lists, links, images
16
- - 🧮 **100+ Formula Templates** — Algebra, calculus, trigonometry, chemistry
17
- - 👁️ **Content Viewer** — Read-only renderer dengan KaTeX
18
- - 🎨 **Table Editor** — 6 professional table templates, column resize, cell selection
19
- - 🖥️ **Code Blocks** — Syntax highlighting untuk berbagai bahasa pemrograman
20
- - 📋 **Collapsible Sections** — Details/summary untuk konten tersembunyi
21
- - ⌨️ **Keyboard Shortcuts** — Ctrl+K (link), Ctrl+Shift+T (table), Ctrl+S (save)
22
- - 📊 **Graph Plotting** — Function plotting via Function Plot
23
- - 🔒 **XSS Protection** — DOMPurify sanitization
24
- - 💻 **Code Blocks** — Syntax highlighting with 100+ languages
25
- - 📋 **Collapsible Sections** — Details/summary for hidden content
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
26
 
27
27
  ---
28
28
 
@@ -36,7 +36,7 @@ npm install erl-mathtextx-editor
36
36
 
37
37
  ## 🚀 Quick Start
38
38
 
39
- ### Basic Usage
39
+ ### 1. Basic Editor
40
40
 
41
41
  ```tsx
42
42
  import { MathTextXEditor } from 'erl-mathtextx-editor'
@@ -52,14 +52,19 @@ function App() {
52
52
  }
53
53
  ```
54
54
 
55
- ### With Save Handler
55
+ ### 2. Editor dengan Save Handler
56
56
 
57
57
  ```tsx
58
- import { MathTextXEditor } from 'erl-mathtextx-editor'
58
+ import { useRef } from 'react'
59
+ import { MathTextXEditor, getHTML } from 'erl-mathtextx-editor'
59
60
  import 'erl-mathtextx-editor/styles'
60
61
 
61
62
  function QuestionForm() {
62
- const handleSave = (html) => {
63
+ const editorRef = useRef<HTMLDivElement>(null)
64
+
65
+ const handleSave = () => {
66
+ if (!editorRef.current) return
67
+ const html = getHTML(editorRef.current)
63
68
  fetch('/api/questions', {
64
69
  method: 'POST',
65
70
  headers: { 'Content-Type': 'application/json' },
@@ -67,32 +72,239 @@ function QuestionForm() {
67
72
  })
68
73
  }
69
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 }) {
70
91
  return (
71
92
  <MathTextXEditor
72
- onChange={(html) => console.log(html)}
73
- onSave={handleSave}
74
- placeholder="Tulis pertanyaan..."
93
+ content={existingHtml}
94
+ onChange={(html) => console.log('Updated:', html)}
75
95
  />
76
96
  )
77
97
  }
78
98
  ```
79
99
 
80
- ### Content Viewer (Read-Only)
100
+ ### 4. Read-Only Mode (Viewer)
81
101
 
82
102
  ```tsx
83
103
  import { ContentViewer } from 'erl-mathtextx-editor/viewer'
84
104
  import 'erl-mathtextx-editor/viewer/styles'
85
105
 
86
- function QuestionCard({ questionHtml }) {
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
+
87
124
  return (
88
- <div className="question-card">
89
- <h3>Soal 1</h3>
90
- <ContentViewer content={questionHtml} />
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
+ ))}
91
144
  </div>
92
145
  )
93
146
  }
94
147
  ```
95
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
+
96
308
  ---
97
309
 
98
310
  ## 📋 Props API
@@ -102,15 +314,18 @@ function QuestionCard({ questionHtml }) {
102
314
  | Prop | Type | Default | Description |
103
315
  |------|------|---------|-------------|
104
316
  | `content` | `string` | `''` | Initial HTML content |
105
- | `onChange` | `(html: string) => void` | — | Callback on content change |
317
+ | `onChange` | `(html: string) => void` | — | Callback on content change (debounced 150ms) |
106
318
  | `onSave` | `(html: string) => void` | — | Callback on Ctrl+S |
107
319
  | `onImageUpload` | `(file: File) => Promise<string>` | — | Custom image upload handler |
320
+ | `onBeforePasteHTML` | `(html: string) => Promise<string>` | — | Transform pasted HTML (re-upload images) |
108
321
  | `placeholder` | `string` | `'Tulis soal...'` | Placeholder text |
109
322
  | `minHeight` | `string` | `'200px'` | Minimum editor height |
110
323
  | `maxHeight` | `string` | — | Maximum editor height |
111
324
  | `autoFocus` | `boolean` | `false` | Auto-focus editor on mount |
112
- | `toolbarMode` | `'basic' \| 'advanced'` | `'basic'` | Toolbar preset |
113
- | `educationLevel` | `'sd' \| 'smp' \| 'sma' \| 'all'` | `'all'` | Filter math templates by level |
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 |
114
329
 
115
330
  ### ContentViewer
116
331
 
@@ -127,17 +342,35 @@ function QuestionCard({ questionHtml }) {
127
342
 
128
343
  ```tsx
129
344
  import {
130
- MathTextXEditor,
131
- ContentViewer,
132
- getHTML,
133
- toCompatibleHTML,
134
- sanitizeCKEditorHTML,
135
- MathTypeDialog,
136
- TemplatePanel,
137
- mathTemplates,
138
- getTemplatesByLevel,
139
- getTemplatesByCategory,
140
- getTemplateCategories,
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
141
374
  } from 'erl-mathtextx-editor'
142
375
 
143
376
  import 'erl-mathtextx-editor/styles'
@@ -160,87 +393,35 @@ import 'erl-mathtextx-editor/viewer/styles'
160
393
  | `Ctrl+I` | Italic |
161
394
  | `Ctrl+U` | Underline |
162
395
  | `Ctrl+K` | Insert/edit link |
396
+ | `Ctrl+M` | Insert inline math directly (without dialog) |
163
397
  | `Ctrl+Shift+T` | Insert table |
164
398
  | `Ctrl+S` | Save document |
165
- | `Tab` | Indent paragraph (at start) / Insert 4 spaces (in middle) |
166
- | `Shift+Tab` | Outdent paragraph |
167
- | `Backspace` | Outdent (if indented) / Join with previous paragraph |
399
+ | `Shift+Ctrl+V` | Paste as plain text |
400
+ | `Esc` (equation editor dialog) | Close dialog |
168
401
 
169
402
  ---
170
403
 
171
- ## 🎯 Example: Multiple Choice Question Form
172
-
173
- ```tsx
174
- import { useState } from 'react'
175
- import { MathTextXEditor } from 'erl-mathtextx-editor'
176
- import 'erl-mathtextx-editor/styles'
404
+ ## ⚠️ Troubleshooting
177
405
 
178
- export default function QuestionForm() {
179
- const [question, setQuestion] = useState('')
180
- const [options, setOptions] = useState([
181
- { id: 'A', content: '', isCorrect: false },
182
- { id: 'B', content: '', isCorrect: false },
183
- { id: 'C', content: '', isCorrect: false },
184
- { id: 'D', content: '', isCorrect: false },
185
- ])
186
-
187
- const handleSubmit = async () => {
188
- await fetch('/api/questions', {
189
- method: 'POST',
190
- headers: { 'Content-Type': 'application/json' },
191
- body: JSON.stringify({ question, options }),
192
- })
193
- }
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/` |
194
414
 
195
- return (
196
- <div>
197
- <h2>Buat Soal Pilihan Ganda</h2>
415
+ ---
198
416
 
199
- <MathTextXEditor
200
- content={question}
201
- onChange={setQuestion}
202
- placeholder="Tulis pertanyaan..."
203
- minHeight="150px"
204
- />
417
+ ## ✅ Verified Import Paths
205
418
 
206
- {options.map((option) => (
207
- <div key={option.id}>
208
- <label>
209
- <input
210
- type="radio"
211
- name="correct"
212
- checked={option.isCorrect}
213
- onChange={() => {
214
- setOptions(prev =>
215
- prev.map(o => ({
216
- ...o,
217
- isCorrect: o.id === option.id
218
- }))
219
- )
220
- }}
221
- />
222
- Opsi {option.id}
223
- </label>
224
- <MathTextXEditor
225
- content={option.content}
226
- onChange={(html) => {
227
- setOptions(prev =>
228
- prev.map(o =>
229
- o.id === option.id ? { ...o, content: html } : o
230
- )
231
- )
232
- }}
233
- placeholder={`Jawaban ${option.id}...`}
234
- minHeight="80px"
235
- />
236
- </div>
237
- ))}
238
-
239
- <button onClick={handleSubmit}>Simpan Soal</button>
240
- </div>
241
- )
242
- }
243
- ```
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` |
244
425
 
245
426
  ---
246
427
 
@@ -250,8 +431,11 @@ export default function QuestionForm() {
250
431
  - **Editor Engine:** TipTap / ProseMirror
251
432
  - **Math Input:** MathLive (WYSIWYG math)
252
433
  - **Math Rendering:** KaTeX
434
+ - **DOCX Import:** mammoth.js
253
435
  - **XSS Protection:** DOMPurify
254
436
  - **Graph Plotting:** Function Plot
437
+ - **Syntax Highlight:** lowlight (100+ languages)
438
+ - **Build:** Vite (Library Mode)
255
439
 
256
440
  ---
257
441
 
@@ -266,3 +450,4 @@ export default function QuestionForm() {
266
450
  - **NPM:** [erl-mathtextx-editor](https://www.npmjs.com/package/erl-mathtextx-editor)
267
451
  - **Source:** [GitHub Repository](https://github.com/erlangga/richtext-editor-research)
268
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)
@@ -1,6 +1,6 @@
1
1
  import { jsx as t, jsxs as l } from "react/jsx-runtime";
2
2
  import z, { useId as H, useRef as A, useCallback as b, useState as d, useEffect as V } from "react";
3
- import { u as B } from "./index-UCSefQk0.js";
3
+ import { u as B } from "./index-D0Rzm7Tg.js";
4
4
  const M = z.memo(({
5
5
  isOpen: s,
6
6
  initialData: i = {},
@@ -1,22 +1,23 @@
1
- import { jsx as o } from "react/jsx-runtime";
2
- import { useRef as r, useMemo as i, useEffect as m } from "react";
3
- import { p as s, k as l } from "./viewer-deps-CjbAqdti.js";
1
+ import { jsx as a } from "react/jsx-runtime";
2
+ import { useRef as o, useMemo as i, useEffect as m } from "react";
3
+ import { p as s, k as l } from "./viewer-deps-BDYoL2Ts.js";
4
4
  function c(t) {
5
+ if (t.querySelector(".katex")) return;
5
6
  const e = t.getAttribute("data-latex") || t.getAttribute("latex");
6
7
  if (!e) return;
7
- const n = t.classList.contains("mtx-math-block");
8
+ const r = t.classList.contains("mtx-math-block");
8
9
  try {
9
10
  l.render(e, t, {
10
11
  throwOnError: !1,
11
- displayMode: n,
12
+ displayMode: r,
12
13
  output: "htmlAndMathml"
13
14
  });
14
15
  } catch {
15
- t.textContent = n ? `$$${e}$$` : `$${e}$`;
16
+ t.textContent = r ? `$$${e}$$` : `$${e}$`;
16
17
  }
17
18
  }
18
- function p({ content: t, className: e }) {
19
- const n = r(null), a = i(
19
+ function x({ content: t, className: e }) {
20
+ const r = o(null), n = i(
20
21
  () => s.sanitize(t, {
21
22
  ADD_TAGS: ["math-field", "span", "div"],
22
23
  ADD_ATTR: ["data-latex", "latex", "data-type", "data-mathml"],
@@ -26,19 +27,19 @@ function p({ content: t, className: e }) {
26
27
  [t]
27
28
  );
28
29
  return m(() => {
29
- if (!n.current) return;
30
- n.current.querySelectorAll(
30
+ if (!r.current) return;
31
+ r.current.querySelectorAll(
31
32
  '.mtx-math-inline, .mtx-math-block, [data-type="math-inline"], [data-type="math-block"]'
32
33
  ).forEach(c);
33
- }, [a]), /* @__PURE__ */ o(
34
+ }, [n]), /* @__PURE__ */ a(
34
35
  "div",
35
36
  {
36
- ref: n,
37
+ ref: r,
37
38
  className: `mtx-content-viewer${e ? ` ${e}` : ""}`,
38
- dangerouslySetInnerHTML: { __html: a }
39
+ dangerouslySetInnerHTML: { __html: n }
39
40
  }
40
41
  );
41
42
  }
42
43
  export {
43
- p as C
44
+ x as C
44
45
  };