erl-mathtextx-editor 0.1.9 → 0.1.10

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 (27) hide show
  1. package/README.md +262 -107
  2. package/dist/{CellPropertiesDialogImpl-DBgs-7H9.js → CellPropertiesDialogImpl-Cl0pxbeQ.js} +1 -1
  3. package/dist/{ContentViewer-CsFSAN_B.js → ContentViewer-RijJ5nlJ.js} +15 -14
  4. package/dist/{ImageInsertDialog-B24KHrgt.js → ImageInsertDialog-BVBl1y36.js} +27 -25
  5. package/dist/{InsertTableDialogImpl-B6_PRu5m.js → InsertTableDialogImpl-Cx3ShX7u.js} +1 -1
  6. package/dist/{LinkDialogImpl-BTA8u_qQ.js → LinkDialogImpl-gMjoZVma.js} +1 -1
  7. package/dist/MathTextXEditor.d.ts +1 -1
  8. package/dist/{TablePropertiesDialogImpl-CuRRWS4H.js → TablePropertiesDialogImpl-CrTTV3Zr.js} +1 -1
  9. package/dist/{TableTemplatesDialogImpl-CU8seEdV.js → TableTemplatesDialogImpl-DTcom8H5.js} +2 -2
  10. package/dist/assets/erl-mathtextx-editor.css +1 -1
  11. package/dist/assets/viewer.css +1 -1
  12. package/dist/components/ErrorBoundary.d.ts +18 -0
  13. package/dist/components/ImageEditDialog.d.ts +0 -1
  14. package/dist/components/TableMenu.d.ts +4 -1
  15. package/dist/erl-mathtextx-editor.js +2 -2
  16. package/dist/erl-mathtextx-editor.umd.cjs +345 -115
  17. package/dist/{index-UCSefQk0.js → index-CakccgVO.js} +3801 -3201
  18. package/dist/{index-CB1g0gXh.js → index-Djb9MY7m.js} +1 -1
  19. package/dist/index-QMz8TDH0.js +16549 -0
  20. package/dist/toolbar/MainToolbar.d.ts +1 -0
  21. package/dist/types/index.d.ts +2 -0
  22. package/dist/utils/docxImporter.d.ts +31 -0
  23. package/dist/utils/pasteHandler.d.ts +14 -3
  24. package/dist/{viewer-deps-CjbAqdti.js → viewer-deps-BDYoL2Ts.js} +5794 -3489
  25. package/dist/viewer.js +1 -1
  26. package/package.json +2 -1
  27. package/dist/extensions/TableAlignPlugin.d.ts +0 -7
package/README.md CHANGED
@@ -13,16 +13,15 @@ Embeddable visual math editor widget untuk CMS dan platform edukasi. User tidak
13
13
 
14
14
  - 🎯 **Visual Math Keyboard** — Klik simbol, operator, template formula
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
17
+ - 📋 **100+ Formula Templates** — Algebra, calculus, trigonometry, chemistry, matrix
18
+ - 🖼️ **Free-form Image Drag** — Drag gambar ke posisi bebas (pixel-perfect)
19
+ - 📄 **DOCX Import** — Import file .docx via mammoth.js (toolbar + drag-drop)
20
+ - 🗂️ **Google Docs Paste** — Paste dari Google Docs (equations + document) auto-cleaned
21
+ - 👁️ **Content Viewer** — Read-only renderer dengan KaTeX + DOMPurify
22
+ - 🎨 **Table Editor** — 6 templates, column resize, cell merge/split
23
+ - 🔒 **XSS Protection** — DOMPurify sanitization di paste + serializer
24
+ - 🛡️ **Error Boundary** — Anti white-screen crash protection
26
25
 
27
26
  ---
28
27
 
@@ -36,7 +35,7 @@ npm install erl-mathtextx-editor
36
35
 
37
36
  ## 🚀 Quick Start
38
37
 
39
- ### Basic Usage
38
+ ### 1. Basic Editor
40
39
 
41
40
  ```tsx
42
41
  import { MathTextXEditor } from 'erl-mathtextx-editor'
@@ -52,14 +51,19 @@ function App() {
52
51
  }
53
52
  ```
54
53
 
55
- ### With Save Handler
54
+ ### 2. Editor dengan Save Handler
56
55
 
57
56
  ```tsx
58
- import { MathTextXEditor } from 'erl-mathtextx-editor'
57
+ import { useRef } from 'react'
58
+ import { MathTextXEditor, getHTML } from 'erl-mathtextx-editor'
59
59
  import 'erl-mathtextx-editor/styles'
60
60
 
61
61
  function QuestionForm() {
62
- const handleSave = (html) => {
62
+ const editorRef = useRef<HTMLDivElement>(null)
63
+
64
+ const handleSave = () => {
65
+ if (!editorRef.current) return
66
+ const html = getHTML(editorRef.current)
63
67
  fetch('/api/questions', {
64
68
  method: 'POST',
65
69
  headers: { 'Content-Type': 'application/json' },
@@ -67,32 +71,210 @@ function QuestionForm() {
67
71
  })
68
72
  }
69
73
 
74
+ return (
75
+ <div>
76
+ <MathTextXEditor ref={editorRef} placeholder="Tulis pertanyaan..." onSave={handleSave} />
77
+ <button onClick={handleSave}>Simpan</button>
78
+ </div>
79
+ )
80
+ }
81
+ ```
82
+
83
+ ### 3. Edit Existing Content
84
+
85
+ ```tsx
86
+ import { MathTextXEditor } from 'erl-mathtextx-editor'
87
+ import 'erl-mathtextx-editor/styles'
88
+
89
+ function EditQuestion({ existingHtml }: { existingHtml: string }) {
70
90
  return (
71
91
  <MathTextXEditor
72
- onChange={(html) => console.log(html)}
73
- onSave={handleSave}
74
- placeholder="Tulis pertanyaan..."
92
+ content={existingHtml}
93
+ onChange={(html) => console.log('Updated:', html)}
75
94
  />
76
95
  )
77
96
  }
78
97
  ```
79
98
 
80
- ### Content Viewer (Read-Only)
99
+ ### 4. Read-Only Mode (Viewer)
81
100
 
82
101
  ```tsx
83
102
  import { ContentViewer } from 'erl-mathtextx-editor/viewer'
84
103
  import 'erl-mathtextx-editor/viewer/styles'
85
104
 
86
- function QuestionCard({ questionHtml }) {
105
+ function ExamQuestion({ questionHtml }: { questionHtml: string }) {
106
+ return <ContentViewer content={questionHtml} />
107
+ }
108
+ ```
109
+
110
+ ### 5. Multi-Instance (Soal + Pilihan Jawaban)
111
+
112
+ ```tsx
113
+ import { useState } from 'react'
114
+ import { MathTextXEditor } from 'erl-mathtextx-editor'
115
+ import 'erl-mathtextx-editor/styles'
116
+
117
+ function MultipleChoiceForm() {
118
+ const [question, setQuestion] = useState('')
119
+ const [options, setOptions] = useState(
120
+ ['A', 'B', 'C', 'D'].map((id) => ({ id, content: '' }))
121
+ )
122
+
87
123
  return (
88
- <div className="question-card">
89
- <h3>Soal 1</h3>
90
- <ContentViewer content={questionHtml} />
124
+ <div>
125
+ <label>Pertanyaan:</label>
126
+ <MathTextXEditor
127
+ content={question}
128
+ onChange={setQuestion}
129
+ placeholder="Tulis pertanyaan..."
130
+ minHeight="150px"
131
+ />
132
+ {options.map((opt) => (
133
+ <div key={opt.id}>
134
+ <label>Opsi {opt.id}:</label>
135
+ <MathTextXEditor
136
+ content={opt.content}
137
+ onChange={(html) => setOptions((prev) => prev.map((o) => o.id === opt.id ? { ...o, content: html } : o))}
138
+ placeholder={`Jawaban ${opt.id}...`}
139
+ minHeight="60px"
140
+ />
141
+ </div>
142
+ ))}
91
143
  </div>
92
144
  )
93
145
  }
94
146
  ```
95
147
 
148
+ ### 6. Next.js (App Router)
149
+
150
+ ```tsx
151
+ 'use client'
152
+ import dynamic from 'next/dynamic'
153
+ import 'erl-mathtextx-editor/styles'
154
+
155
+ const MathTextXEditor = dynamic(
156
+ () => import('erl-mathtextx-editor').then((mod) => mod.MathTextXEditor),
157
+ { ssr: false }
158
+ )
159
+
160
+ export default function EditorPage() {
161
+ return (
162
+ <MathTextXEditor
163
+ placeholder="Tulis soal matematika..."
164
+ onChange={(html) => console.log(html)}
165
+ minHeight="300px"
166
+ />
167
+ )
168
+ }
169
+ ```
170
+
171
+ ### 7. Vite + React
172
+
173
+ ```tsx
174
+ import { MathTextXEditor } from 'erl-mathtextx-editor'
175
+ import 'erl-mathtextx-editor/styles'
176
+
177
+ function App() {
178
+ return (
179
+ <MathTextXEditor
180
+ placeholder="Tulis soal..."
181
+ onChange={(html) => console.log(html)}
182
+ />
183
+ )
184
+ }
185
+ export default App
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 🧰 Image Upload
191
+
192
+ Editor mendukung upload gambar dari: **Insert dialog**, **drag-drop**, **paste dari clipboard**, dan **DOCX import**. Semuanya melalui satu callback `onImageUpload`.
193
+
194
+ ### Basic Upload
195
+
196
+ ```tsx
197
+ import { MathTextXEditor } from 'erl-mathtextx-editor'
198
+ import 'erl-mathtextx-editor/styles'
199
+
200
+ function EditorWithUpload() {
201
+ const handleImageUpload = async (file: File): Promise<string> => {
202
+ const formData = new FormData()
203
+ formData.append('image', file)
204
+ const res = await fetch('/api/upload', { method: 'POST', body: formData })
205
+ if (!res.ok) throw new Error('Upload failed: ' + res.statusText)
206
+ const data = await res.json()
207
+ return data.url // Expected: { "url": "https://cdn.example.com/img.jpg" }
208
+ }
209
+
210
+ return (
211
+ <MathTextXEditor
212
+ placeholder="Tulis soal..."
213
+ onImageUpload={handleImageUpload}
214
+ />
215
+ )
216
+ }
217
+ ```
218
+
219
+ ### Re-upload Gambar dari Paste (Google Docs / Website)
220
+
221
+ ```tsx
222
+ import { MathTextXEditor } from 'erl-mathtextx-editor'
223
+ import 'erl-mathtextx-editor/styles'
224
+
225
+ function EditorWithPasteReupload() {
226
+ const handleImageUpload = async (file: File): Promise<string> => {
227
+ const formData = new FormData()
228
+ formData.append('image', file)
229
+ const res = await fetch('/api/upload', { method: 'POST', body: formData })
230
+ return (await res.json()).url
231
+ }
232
+
233
+ const handleBeforePasteHTML = async (html: string): Promise<string> => {
234
+ // Download + re-upload external images from pasted HTML
235
+ const imgRegex = /<img\s+[^>]*src="([^"]+)"[^>]*>/gi
236
+ const replacements: Array<[string, string]> = []
237
+ let match
238
+
239
+ while ((match = imgRegex.exec(html)) !== null) {
240
+ const src = match[1]
241
+ if (src.startsWith('data:') || src.includes('cdn.example.com')) continue
242
+ try {
243
+ const blob = await (await fetch(src)).blob()
244
+ const file = new File([blob], 'image.' + (blob.type.split('/')[1] || 'jpg'))
245
+ replacements.push([src, await handleImageUpload(file)])
246
+ } catch { console.warn('Skip image:', src) }
247
+ }
248
+
249
+ let result = html
250
+ for (const [oldSrc, newSrc] of replacements) result = result.replaceAll(oldSrc, newSrc)
251
+ return result
252
+ }
253
+
254
+ return (
255
+ <MathTextXEditor
256
+ placeholder="Tulis soal..."
257
+ onImageUpload={handleImageUpload}
258
+ onBeforePasteHTML={handleBeforePasteHTML}
259
+ />
260
+ )
261
+ }
262
+ ```
263
+
264
+ ### Base64 Fallback (Tanpa Server)
265
+
266
+ ```tsx
267
+ const handleImageUpload = async (file: File): Promise<string> => {
268
+ return new Promise((resolve, reject) => {
269
+ const reader = new FileReader()
270
+ reader.onload = () => resolve(reader.result as string)
271
+ reader.onerror = reject
272
+ reader.readAsDataURL(file)
273
+ })
274
+ }
275
+ // ⚠️ Base64 hanya cocok untuk gambar < 100KB. Untuk produksi, gunakan upload ke server.
276
+ ```
277
+
96
278
  ---
97
279
 
98
280
  ## 📋 Props API
@@ -102,15 +284,17 @@ function QuestionCard({ questionHtml }) {
102
284
  | Prop | Type | Default | Description |
103
285
  |------|------|---------|-------------|
104
286
  | `content` | `string` | `''` | Initial HTML content |
105
- | `onChange` | `(html: string) => void` | — | Callback on content change |
287
+ | `onChange` | `(html: string) => void` | — | Callback on content change (debounced 150ms) |
106
288
  | `onSave` | `(html: string) => void` | — | Callback on Ctrl+S |
107
289
  | `onImageUpload` | `(file: File) => Promise<string>` | — | Custom image upload handler |
290
+ | `onBeforePasteHTML` | `(html: string) => Promise<string>` | — | Transform pasted HTML (re-upload images) |
108
291
  | `placeholder` | `string` | `'Tulis soal...'` | Placeholder text |
109
292
  | `minHeight` | `string` | `'200px'` | Minimum editor height |
110
293
  | `maxHeight` | `string` | — | Maximum editor height |
111
294
  | `autoFocus` | `boolean` | `false` | Auto-focus editor on mount |
112
295
  | `toolbarMode` | `'basic' \| 'advanced'` | `'basic'` | Toolbar preset |
113
- | `educationLevel` | `'sd' \| 'smp' \| 'sma' \| 'all'` | `'all'` | Filter math templates by level |
296
+ | `editable` | `boolean` | `true` | Set false for read-only mode |
297
+ | `className` | `string` | — | Additional CSS class |
114
298
 
115
299
  ### ContentViewer
116
300
 
@@ -127,17 +311,35 @@ function QuestionCard({ questionHtml }) {
127
311
 
128
312
  ```tsx
129
313
  import {
130
- MathTextXEditor,
131
- ContentViewer,
132
- getHTML,
133
- toCompatibleHTML,
134
- sanitizeCKEditorHTML,
135
- MathTypeDialog,
136
- TemplatePanel,
137
- mathTemplates,
138
- getTemplatesByLevel,
139
- getTemplatesByCategory,
140
- getTemplateCategories,
314
+ MathTextXEditor, // Main editor component
315
+ ContentViewer, // Read-only renderer
316
+ MathTypeDialog, // Standalone equation editor dialog
317
+ TemplatePanel, // Standalone formula template panel
318
+ MainToolbar, // Standalone text formatting toolbar
319
+ MathToolbar, // Standalone math symbols toolbar
320
+ SymbolPalette, // Standalone symbol picker
321
+ WordCount, // Status bar word/char counter
322
+ LinkDialog, // Standalone link insert/edit dialog
323
+ ImageEditDialog, // Standalone image edit dialog
324
+ InsertTableDialog, // Standalone table insert dialog
325
+ TableMenu, // Standalone table context menu
326
+ CellPropertiesDialog, // Standalone cell properties dialog
327
+ TablePropertiesDialog, // Standalone table properties dialog
328
+ TableTemplatesDialog, // Standalone table template picker
329
+ MathInlineNode, // TipTap inline math extension
330
+ MathBlockNode, // TipTap block math extension
331
+ getHTML, // Serialize editor → HTML string
332
+ getJSON, // Serialize editor → JSON
333
+ sanitizeCKEditorHTML, // Clean CKEditor HTML for compatibility
334
+ toCompatibleHTML, // Convert to CKEditor-compatible format
335
+ createExtensions, // Create TipTap extensions programmatically
336
+ mathTemplates, // Template definitions
337
+ getTemplatesByLevel, // Filter templates by education level
338
+ getTemplatesByCategory,// Filter templates by category
339
+ getTemplateCategories, // Get all template categories
340
+ countWords, // Word count utility
341
+ countCharacters, // Character count utility
342
+ getTemplateStyles, // Table template CSS generator
141
343
  } from 'erl-mathtextx-editor'
142
344
 
143
345
  import 'erl-mathtextx-editor/styles'
@@ -160,87 +362,36 @@ import 'erl-mathtextx-editor/viewer/styles'
160
362
  | `Ctrl+I` | Italic |
161
363
  | `Ctrl+U` | Underline |
162
364
  | `Ctrl+K` | Insert/edit link |
365
+ | `Ctrl+M` | Insert inline math |
163
366
  | `Ctrl+Shift+T` | Insert table |
164
367
  | `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 |
368
+ | `Shift+Ctrl+V` | Paste as plain text |
369
+ | `Enter` (equation editor) | Insert formula |
370
+ | `Esc` (equation editor) | Close dialog |
168
371
 
169
372
  ---
170
373
 
171
- ## 🎯 Example: Multiple Choice Question Form
374
+ ## ⚠️ Troubleshooting
172
375
 
173
- ```tsx
174
- import { useState } from 'react'
175
- import { MathTextXEditor } from 'erl-mathtextx-editor'
176
- import 'erl-mathtextx-editor/styles'
376
+ | Error | Solution |
377
+ |---|---|
378
+ | `Can't resolve 'erl-mathtextx-editor'` | `npm install erl-mathtextx-editor` |
379
+ | `Can't resolve 'erl-mathtextx-editor/styles'` | Version ≥ 0.1.3. Alternatif: `import 'erl-mathtextx-editor/dist/assets/erl-mathtextx-editor.css'` |
380
+ | `MathTextXEditor is not a function` | Pastikan React ≥ 18 (`npm ls react`) |
381
+ | `window is not defined` (Next.js) | Gunakan `dynamic()` dengan `{ ssr: false }` |
382
+ | `Unexpected token 'export'` (CRA) | Webpack config: `resolve.mainFields: ['main', 'module']` |
383
+ | MathLive fonts error | Set `(window as any).MATHLIVE_FONTS_PATH = '/fonts'` + copy font ke `public/fonts/` |
177
384
 
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
- }
194
-
195
- return (
196
- <div>
197
- <h2>Buat Soal Pilihan Ganda</h2>
198
-
199
- <MathTextXEditor
200
- content={question}
201
- onChange={setQuestion}
202
- placeholder="Tulis pertanyaan..."
203
- minHeight="150px"
204
- />
385
+ ---
205
386
 
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
- ))}
387
+ ## Verified Import Paths
238
388
 
239
- <button onClick={handleSubmit}>Simpan Soal</button>
240
- </div>
241
- )
242
- }
243
- ```
389
+ | Import | Resolves to |
390
+ |---|---|
391
+ | `erl-mathtextx-editor` | `dist/erl-mathtextx-editor.js` |
392
+ | `erl-mathtextx-editor/styles` | `dist/assets/erl-mathtextx-editor.css` |
393
+ | `erl-mathtextx-editor/viewer` | `dist/viewer.js` |
394
+ | `erl-mathtextx-editor/viewer/styles` | `dist/viewer-styles.js` |
244
395
 
245
396
  ---
246
397
 
@@ -250,8 +401,11 @@ export default function QuestionForm() {
250
401
  - **Editor Engine:** TipTap / ProseMirror
251
402
  - **Math Input:** MathLive (WYSIWYG math)
252
403
  - **Math Rendering:** KaTeX
404
+ - **DOCX Import:** mammoth.js
253
405
  - **XSS Protection:** DOMPurify
254
406
  - **Graph Plotting:** Function Plot
407
+ - **Syntax Highlight:** lowlight (100+ languages)
408
+ - **Build:** Vite (Library Mode)
255
409
 
256
410
  ---
257
411
 
@@ -266,3 +420,4 @@ export default function QuestionForm() {
266
420
  - **NPM:** [erl-mathtextx-editor](https://www.npmjs.com/package/erl-mathtextx-editor)
267
421
  - **Source:** [GitHub Repository](https://github.com/erlangga/richtext-editor-research)
268
422
  - **Issues:** [Report Bug](https://github.com/erlangga/richtext-editor-research/issues)
423
+ - **📘 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-CakccgVO.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
  };
@@ -1,17 +1,17 @@
1
1
  import { jsxs as i, Fragment as J, jsx as e } from "react/jsx-runtime";
2
2
  import { useId as V, useRef as y, useState as d, useCallback as s } from "react";
3
- import { u as q } from "./index-UCSefQk0.js";
3
+ import { u as q } from "./index-CakccgVO.js";
4
4
  function Z({
5
5
  isOpen: U,
6
- onClose: N,
6
+ onClose: w,
7
7
  onInsert: u,
8
8
  onImageUpload: v
9
9
  }) {
10
- const w = V(), R = y(null), [p, L] = d(v ? "upload" : "url"), [r, D] = d(""), [g, C] = d(""), [B, h] = d(!1), [T, k] = d(!1), [f, o] = d(null), [P, l] = d(""), F = y(null), m = y(null), j = s(() => {
11
- m.current && (URL.revokeObjectURL(m.current), m.current = null), D(""), C(""), o(null), l(""), k(!1), h(!1);
10
+ const N = V(), R = y(null), [p, L] = d(v ? "upload" : "url"), [r, D] = d(""), [g, C] = d(""), [B, h] = d(!1), [T, k] = d(!1), [f, c] = d(null), [P, l] = d(""), F = y(null), m = y(null), j = s(() => {
11
+ m.current && (URL.revokeObjectURL(m.current), m.current = null), D(""), C(""), c(null), l(""), k(!1), h(!1);
12
12
  }, []), t = s(() => {
13
- j(), N();
14
- }, [N, j]);
13
+ j(), w();
14
+ }, [w, j]);
15
15
  q({ isOpen: U, dialogRef: R, onClose: t });
16
16
  const A = async (a) => {
17
17
  if (!["image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp"].includes(a.type))
@@ -26,7 +26,7 @@ function Z({
26
26
  "image/webp": [[82, 73, 70, 70]]
27
27
  };
28
28
  try {
29
- const c = await a.slice(0, 8).arrayBuffer(), x = new Uint8Array(c), S = _[a.type];
29
+ const o = await a.slice(0, 8).arrayBuffer(), x = new Uint8Array(o), S = _[a.type];
30
30
  if (!S)
31
31
  return { valid: !1, error: "Format gambar tidak dikenali" };
32
32
  if (!S.some(
@@ -46,22 +46,24 @@ function Z({
46
46
  }
47
47
  l(""), m.current && URL.revokeObjectURL(m.current);
48
48
  const _ = URL.createObjectURL(a);
49
- if (m.current = _, o(_), v) {
49
+ if (m.current = _, c(_), v) {
50
50
  k(!0);
51
51
  try {
52
- const c = await v(a);
53
- u(c, g || a.name), t();
52
+ const o = await v(a);
53
+ if (!o)
54
+ throw new Error("Upload gagal: URL tidak valid");
55
+ u(o, g || a.name), t();
54
56
  } catch {
55
- l("Upload gagal. Silakan coba lagi."), o(null);
57
+ l("Upload gagal. Silakan coba lagi."), c(null);
56
58
  } finally {
57
59
  k(!1);
58
60
  }
59
61
  } else {
60
- const c = new FileReader();
61
- c.onload = () => {
62
- const x = c.result;
62
+ const o = new FileReader();
63
+ o.onload = () => {
64
+ const x = o.result;
63
65
  u(x, g || a.name), t();
64
- }, c.readAsDataURL(a);
66
+ }, o.readAsDataURL(a);
65
67
  }
66
68
  },
67
69
  [v, u, g, t]
@@ -73,9 +75,9 @@ function Z({
73
75
  [b]
74
76
  ), I = s((a) => {
75
77
  a.preventDefault(), a.stopPropagation(), h(!0);
76
- }, []), O = s((a) => {
78
+ }, []), E = s((a) => {
77
79
  a.preventDefault(), a.stopPropagation(), h(!1);
78
- }, []), E = s(
80
+ }, []), O = s(
79
81
  (a) => {
80
82
  a.preventDefault(), a.stopPropagation(), h(!1);
81
83
  const n = a.dataTransfer.files?.[0];
@@ -97,16 +99,16 @@ function Z({
97
99
  }, [r, g, u, t]), K = s(() => {
98
100
  if (r.trim())
99
101
  try {
100
- new URL(r), o(r.trim()), l("");
102
+ new URL(r), c(r.trim()), l("");
101
103
  } catch {
102
104
  l("URL tidak valid");
103
105
  }
104
106
  }, [r]);
105
107
  return U ? /* @__PURE__ */ i(J, { children: [
106
108
  /* @__PURE__ */ e("div", { className: "mtx-dialog-overlay", onClick: t }),
107
- /* @__PURE__ */ i("div", { className: "mtx-image-dialog", ref: R, role: "dialog", "aria-modal": "true", "aria-labelledby": w, tabIndex: -1, children: [
109
+ /* @__PURE__ */ i("div", { className: "mtx-image-dialog", ref: R, role: "dialog", "aria-modal": "true", "aria-labelledby": N, tabIndex: -1, children: [
108
110
  /* @__PURE__ */ i("div", { className: "mtx-image-dialog__header", children: [
109
- /* @__PURE__ */ e("h3", { id: w, children: "Sisipkan Gambar" }),
111
+ /* @__PURE__ */ e("h3", { id: N, children: "Sisipkan Gambar" }),
110
112
  /* @__PURE__ */ e(
111
113
  "button",
112
114
  {
@@ -123,7 +125,7 @@ function Z({
123
125
  {
124
126
  className: `mtx-image-dialog__tab ${p === "upload" ? "is-active" : ""}`,
125
127
  onClick: () => {
126
- L("upload"), l(""), o(null);
128
+ L("upload"), l(""), c(null);
127
129
  },
128
130
  children: "📁 Upload File"
129
131
  }
@@ -133,7 +135,7 @@ function Z({
133
135
  {
134
136
  className: `mtx-image-dialog__tab ${p === "url" ? "is-active" : ""}`,
135
137
  onClick: () => {
136
- L("url"), l(""), o(null);
138
+ L("url"), l(""), c(null);
137
139
  },
138
140
  children: "🔗 URL"
139
141
  }
@@ -145,8 +147,8 @@ function Z({
145
147
  {
146
148
  className: `mtx-image-dialog__dropzone ${B ? "is-dragging" : ""}`,
147
149
  onDragOver: I,
148
- onDragLeave: O,
149
- onDrop: E,
150
+ onDragLeave: E,
151
+ onDrop: O,
150
152
  onClick: () => F.current?.click(),
151
153
  children: [
152
154
  /* @__PURE__ */ e(
@@ -199,7 +201,7 @@ function Z({
199
201
  src: f,
200
202
  alt: "Preview",
201
203
  onError: () => {
202
- o(null), l("Gambar tidak dapat dimuat dari URL ini");
204
+ c(null), l("Gambar tidak dapat dimuat dari URL ini");
203
205
  }
204
206
  }
205
207
  ) })
@@ -1,6 +1,6 @@
1
1
  import { jsx as e, jsxs as t } from "react/jsx-runtime";
2
2
  import N, { useId as v, useRef as f, useState as c, useEffect as g, useCallback as w } from "react";
3
- import { u as y } from "./index-UCSefQk0.js";
3
+ import { u as y } from "./index-CakccgVO.js";
4
4
  const k = N.memo(({ isOpen: i, onInsert: d, onClose: m }) => {
5
5
  const b = v(), u = f(null), [l, n] = c(3), [a, s] = c(3), [o, h] = c(!0);
6
6
  g(() => {