erl-mathtextx-editor 0.1.2

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/dist/CellPropertiesDialogImpl-CgWcr4bD.js +101 -0
  4. package/dist/ContentViewer-DcvxXP9t.js +43 -0
  5. package/dist/ImageInsertDialog-Di0MXcC2.js +222 -0
  6. package/dist/InsertTableDialogImpl-CKirXRqE.js +100 -0
  7. package/dist/LinkDialogImpl-BSz0F-xG.js +136 -0
  8. package/dist/MathTextXEditor.d.ts +8 -0
  9. package/dist/TableTemplatesDialogImpl-DrdqLQLH.js +64 -0
  10. package/dist/TemplatePanel-BE_UfzYM.js +710 -0
  11. package/dist/assets/erl-mathtextx-editor.css +1 -0
  12. package/dist/assets/viewer.css +1 -0
  13. package/dist/components/CellPropertiesDialog.d.ts +4 -0
  14. package/dist/components/CellPropertiesDialogImpl.d.ts +14 -0
  15. package/dist/components/GraphComponent.d.ts +3 -0
  16. package/dist/components/ImageContextMenu.d.ts +18 -0
  17. package/dist/components/ImageEditDialog.d.ts +25 -0
  18. package/dist/components/ImageInsertDialog.d.ts +14 -0
  19. package/dist/components/ImageNodeView.d.ts +3 -0
  20. package/dist/components/InsertTableDialog.d.ts +4 -0
  21. package/dist/components/InsertTableDialogImpl.d.ts +7 -0
  22. package/dist/components/LinkDialog.d.ts +4 -0
  23. package/dist/components/LinkDialogImpl.d.ts +18 -0
  24. package/dist/components/TableMenu.d.ts +21 -0
  25. package/dist/components/TableTemplatesDialog.d.ts +4 -0
  26. package/dist/components/TableTemplatesDialogImpl.d.ts +15 -0
  27. package/dist/components/WordCount.d.ts +6 -0
  28. package/dist/components/__tests__/WordCount.test.d.ts +1 -0
  29. package/dist/components/tableTemplateStyles.d.ts +2 -0
  30. package/dist/components/useDialogA11y.d.ts +6 -0
  31. package/dist/components/wordCountUtils.d.ts +3 -0
  32. package/dist/core/__tests__/serializer.test.d.ts +1 -0
  33. package/dist/core/extensions.d.ts +9 -0
  34. package/dist/core/serializer.d.ts +24 -0
  35. package/dist/erl-mathtextx-editor.js +30 -0
  36. package/dist/erl-mathtextx-editor.umd.cjs +3508 -0
  37. package/dist/extensions/GraphExtension.d.ts +9 -0
  38. package/dist/index-CLGg8QXp.js +8700 -0
  39. package/dist/index-DYxMVo98.js +4599 -0
  40. package/dist/index.d.ts +33 -0
  41. package/dist/math/MathNodes.d.ts +11 -0
  42. package/dist/math/MathTypeDialog.d.ts +8 -0
  43. package/dist/math/MatrixGridSelector.d.ts +5 -0
  44. package/dist/math/SymbolPalette.d.ts +5 -0
  45. package/dist/math/TemplatePanel.d.ts +5 -0
  46. package/dist/mathlive-BwNIIOwE.js +14488 -0
  47. package/dist/templates/index.d.ts +17 -0
  48. package/dist/tiptap-BhJ7Fu9S.js +17927 -0
  49. package/dist/toolbar/MainToolbar.d.ts +14 -0
  50. package/dist/toolbar/MathToolbar.d.ts +7 -0
  51. package/dist/types/index.d.ts +73 -0
  52. package/dist/utils/exportUtils.d.ts +19 -0
  53. package/dist/utils/pasteHandler.d.ts +17 -0
  54. package/dist/viewer/ContentViewer.d.ts +29 -0
  55. package/dist/viewer/index.d.ts +2 -0
  56. package/dist/viewer/styles.d.ts +0 -0
  57. package/dist/viewer-deps-xNPNdmbe.js +12141 -0
  58. package/dist/viewer-styles.d.ts +1 -0
  59. package/dist/viewer-styles.js +1 -0
  60. package/dist/viewer.d.ts +2 -0
  61. package/dist/viewer.js +4 -0
  62. package/package.json +132 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MathTextX Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # erl-mathtextx-editor
2
+
3
+ **Visual Math Editor Component — Zero LaTeX Required**
4
+
5
+ [![npm version](https://badge.fury.io/js/erl-mathtextx-editor.svg)](https://www.npmjs.com/package/erl-mathtextx-editor)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 Keyboard** — Klik simbol, operator, template formula
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
19
+ - ⌨️ **Keyboard Shortcuts** — Ctrl+K (link), Ctrl+Shift+T (table), Ctrl+S (save)
20
+
21
+ ---
22
+
23
+ ## 📦 Installation
24
+
25
+ ```bash
26
+ npm install erl-mathtextx-editor
27
+ ```
28
+
29
+ ---
30
+
31
+ ## 🚀 Quick Start
32
+
33
+ ### Basic Usage
34
+
35
+ ```tsx
36
+ import { MathTextXEditor } from 'erl-mathtextx-editor'
37
+ import 'erl-mathtextx-editor/styles'
38
+
39
+ function App() {
40
+ return (
41
+ <MathTextXEditor
42
+ onChange={(html) => console.log(html)}
43
+ placeholder="Tulis soal di sini..."
44
+ />
45
+ )
46
+ }
47
+ ```
48
+
49
+ ### With Save Handler
50
+
51
+ ```tsx
52
+ import { MathTextXEditor, getHTML } from 'erl-mathtextx-editor'
53
+ import 'erl-mathtextx-editor/styles'
54
+
55
+ function QuestionForm() {
56
+ const handleSave = (html) => {
57
+ // Send to your API
58
+ fetch('/api/questions', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({ content: html }),
62
+ })
63
+ }
64
+
65
+ return (
66
+ <MathTextXEditor
67
+ onChange={(html) => console.log(html)}
68
+ onSave={handleSave}
69
+ placeholder="Tulis pertanyaan..."
70
+ />
71
+ )
72
+ }
73
+ ```
74
+
75
+ ### Content Viewer (Read-Only)
76
+
77
+ ```tsx
78
+ import { ContentViewer } from 'erl-mathtextx-editor/viewer'
79
+ import 'erl-mathtextx-editor/styles'
80
+
81
+ function QuestionCard({ questionHtml }) {
82
+ return (
83
+ <div className="question-card">
84
+ <h3>Soal 1</h3>
85
+ <ContentViewer content={questionHtml} />
86
+ </div>
87
+ )
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 📖 Documentation
94
+
95
+ - [Quick Start Guide](https://github.com/erlangga/richtext-editor-research/blob/main/QUICK_START.md)
96
+ - [Integration Tutorial](https://github.com/erlangga/richtext-editor-research/blob/main/INTEGRATION_TUTORIAL.md)
97
+
98
+ ---
99
+
100
+ ## 🛠️ Tech Stack
101
+
102
+ - **UI Framework:** React 18+
103
+ - **Editor Engine:** TipTap / ProseMirror
104
+ - **Math Input:** MathLive (WYSIWYG math)
105
+ - **Math Rendering:** KaTeX
106
+ - **XSS Protection:** DOMPurify
107
+
108
+ ---
109
+
110
+ ## 📋 Props API
111
+
112
+ ### MathTextXEditor
113
+
114
+ | Prop | Type | Default | Description |
115
+ |------|------|---------|-------------|
116
+ | `content` | `string` | `''` | Initial HTML content |
117
+ | `onChange` | `(html: string) => void` | — | Callback on content change |
118
+ | `onSave` | `(html: string) => void` | — | Callback on Ctrl+S |
119
+ | `placeholder` | `string` | `'Tulis soal...'` | Placeholder text |
120
+ | `minHeight` | `string` | `'200px'` | Minimum editor height |
121
+ | `maxHeight` | `string` | — | Maximum editor height |
122
+ | `onImageUpload` | `(file: File) => Promise<string>` | — | Custom image upload handler |
123
+
124
+ ### ContentViewer
125
+
126
+ | Prop | Type | Default | Description |
127
+ |------|------|---------|-------------|
128
+ | `content` | `string` | — | HTML content to render (required) |
129
+ | `className` | `string` | — | Additional CSS class |
130
+
131
+ ---
132
+
133
+ ## ⌨️ Keyboard Shortcuts
134
+
135
+ | Shortcut | Action |
136
+ |----------|--------|
137
+ | `Ctrl+K` | Insert/edit link |
138
+ | `Ctrl+Shift+T` | Insert table |
139
+ | `Ctrl+S` | Save document |
140
+ | `Ctrl+B` | Bold |
141
+ | `Ctrl+I` | Italic |
142
+ | `Ctrl+U` | Underline |
143
+
144
+ ---
145
+
146
+ ## 🎯 Example: Multiple Choice Question Form
147
+
148
+ ```tsx
149
+ import { useState } from 'react'
150
+ import { MathTextXEditor } from 'erl-mathtextx-editor'
151
+ import 'erl-mathtextx-editor/styles'
152
+
153
+ export default function QuestionForm() {
154
+ const [question, setQuestion] = useState('')
155
+ const [options, setOptions] = useState([
156
+ { id: 'A', content: '', isCorrect: false },
157
+ { id: 'B', content: '', isCorrect: false },
158
+ { id: 'C', content: '', isCorrect: false },
159
+ { id: 'D', content: '', isCorrect: false },
160
+ ])
161
+
162
+ const handleSubmit = async () => {
163
+ await fetch('/api/questions', {
164
+ method: 'POST',
165
+ headers: { 'Content-Type': 'application/json' },
166
+ body: JSON.stringify({ question, options }),
167
+ })
168
+ }
169
+
170
+ return (
171
+ <div>
172
+ <h2>Buat Soal Pilihan Ganda</h2>
173
+
174
+ {/* Question */}
175
+ <MathTextXEditor
176
+ content={question}
177
+ onChange={setQuestion}
178
+ placeholder="Tulis pertanyaan..."
179
+ minHeight="150px"
180
+ />
181
+
182
+ {/* Options */}
183
+ {options.map((option) => (
184
+ <div key={option.id}>
185
+ <label>
186
+ <input
187
+ type="radio"
188
+ name="correct"
189
+ checked={option.isCorrect}
190
+ onChange={() => {
191
+ setOptions(prev =>
192
+ prev.map(o => ({
193
+ ...o,
194
+ isCorrect: o.id === option.id
195
+ }))
196
+ )
197
+ }}
198
+ />
199
+ Opsi {option.id}
200
+ </label>
201
+ <MathTextXEditor
202
+ content={option.content}
203
+ onChange={(html) => {
204
+ setOptions(prev =>
205
+ prev.map(o =>
206
+ o.id === option.id ? { ...o, content: html } : o
207
+ )
208
+ )
209
+ }}
210
+ placeholder={`Jawaban ${option.id}...`}
211
+ minHeight="80px"
212
+ />
213
+ </div>
214
+ ))}
215
+
216
+ <button onClick={handleSubmit}>Simpan Soal</button>
217
+ </div>
218
+ )
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 🔗 Links
225
+
226
+ - **Website:** [erl-mathtextx-editor](https://github.com/erlangga/richtext-editor-research)
227
+ - **NPM:** [erl-mathtextx-editor](https://www.npmjs.com/package/erl-mathtextx-editor)
228
+ - **Issues:** [Report Bug](https://github.com/erlangga/richtext-editor-research/issues)
229
+
230
+ ---
231
+
232
+ ## 📄 License
233
+
234
+ [MIT](https://github.com/erlangga/richtext-editor-research/blob/main/LICENSE) © Erlangga Team
235
+
236
+ ---
237
+
238
+ ## 🎉 Ready to Use?
239
+
240
+ ```bash
241
+ npm install erl-mathtextx-editor
242
+ ```
243
+
244
+ Then import and use:
245
+
246
+ ```tsx
247
+ import { MathTextXEditor } from 'erl-mathtextx-editor'
248
+ import 'erl-mathtextx-editor/styles'
249
+ ```
250
+
251
+ Happy coding! 🚀
@@ -0,0 +1,101 @@
1
+ import { jsx as t, jsxs as l } from "react/jsx-runtime";
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-DYxMVo98.js";
4
+ const M = z.memo(({
5
+ isOpen: s,
6
+ initialData: i = {},
7
+ onSave: f,
8
+ onClose: o
9
+ }) => {
10
+ const N = H(), C = A(null), p = b((e) => e ? e.endsWith("%") ? { value: e.replace("%", ""), unit: "%" } : e.endsWith("px") ? { value: e.replace("px", ""), unit: "px" } : { value: e.replace(/[^\d.]/g, ""), unit: "px" } : { value: "", unit: "%" }, []), k = p(i.width), [m, v] = d(k.value), [g, x] = d(k.unit), [h, u] = d(i.backgroundColor || ""), [n, r] = d(i.horizontalAlign || "left"), [a, c] = d(i.verticalAlign || "top");
11
+ V(() => {
12
+ if (s) {
13
+ const e = p(i.width);
14
+ v(e.value), x(e.unit), u(i.backgroundColor || ""), r(i.horizontalAlign || "left"), c(i.verticalAlign || "top");
15
+ }
16
+ }, [p, i, s]), B({ isOpen: s, dialogRef: C, onClose: o });
17
+ const w = b((e) => {
18
+ e.preventDefault(), f({
19
+ width: m ? `${m}${g}` : void 0,
20
+ backgroundColor: h || void 0,
21
+ horizontalAlign: n,
22
+ verticalAlign: a
23
+ }), o();
24
+ }, [m, g, h, n, a, f, o]), y = b(() => {
25
+ v(""), x("%"), u(""), r("left"), c("top");
26
+ }, []);
27
+ return s ? /* @__PURE__ */ t("div", { className: "mtx-modal-overlay", onClick: o, children: /* @__PURE__ */ l(
28
+ "div",
29
+ {
30
+ ref: C,
31
+ className: "mtx-modal-dialog mtx-cell-properties-dialog",
32
+ onClick: (e) => e.stopPropagation(),
33
+ role: "dialog",
34
+ "aria-modal": "true",
35
+ "aria-labelledby": N,
36
+ tabIndex: -1,
37
+ children: [
38
+ /* @__PURE__ */ l("div", { className: "mtx-modal-header", children: [
39
+ /* @__PURE__ */ t("h3", { className: "mtx-modal-title", id: N, children: "Cell Properties" }),
40
+ /* @__PURE__ */ t("button", { className: "mtx-modal-close", onClick: o, title: "Close", "aria-label": "Close dialog", children: /* @__PURE__ */ t("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ t("path", { d: "M4 4l8 8m0-8l-8 8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) })
41
+ ] }),
42
+ /* @__PURE__ */ l("form", { onSubmit: w, children: [
43
+ /* @__PURE__ */ l("div", { className: "mtx-modal-body", children: [
44
+ /* @__PURE__ */ l("div", { className: "mtx-form-group", children: [
45
+ /* @__PURE__ */ t("label", { className: "mtx-form-label", htmlFor: "cell-width", children: "Width" }),
46
+ /* @__PURE__ */ l("div", { className: "mtx-width-input-wrapper", children: [
47
+ /* @__PURE__ */ t(
48
+ "input",
49
+ {
50
+ id: "cell-width",
51
+ type: "text",
52
+ className: "mtx-form-input mtx-width-input",
53
+ value: m,
54
+ onChange: (e) => v(e.target.value.replace(/[^\d.]/g, "")),
55
+ placeholder: "Auto"
56
+ }
57
+ ),
58
+ /* @__PURE__ */ l("select", { className: "mtx-form-select mtx-width-unit", value: g, onChange: (e) => x(e.target.value), children: [
59
+ /* @__PURE__ */ t("option", { value: "px", children: "px" }),
60
+ /* @__PURE__ */ t("option", { value: "%", children: "%" })
61
+ ] })
62
+ ] })
63
+ ] }),
64
+ /* @__PURE__ */ l("div", { className: "mtx-form-group", children: [
65
+ /* @__PURE__ */ t("label", { className: "mtx-form-label", htmlFor: "cell-bg-color", children: "Background Color" }),
66
+ /* @__PURE__ */ l("div", { className: "mtx-color-input-wrapper", children: [
67
+ /* @__PURE__ */ t("input", { id: "cell-bg-color", type: "color", className: "mtx-color-picker-input", value: h || "#ffffff", onChange: (e) => u(e.target.value) }),
68
+ /* @__PURE__ */ t("input", { type: "text", className: "mtx-form-input mtx-color-text-input", value: h, onChange: (e) => u(e.target.value), placeholder: "Kosongkan untuk default" })
69
+ ] })
70
+ ] }),
71
+ /* @__PURE__ */ l("div", { className: "mtx-form-group", children: [
72
+ /* @__PURE__ */ t("label", { className: "mtx-form-label", children: "Horizontal Align" }),
73
+ /* @__PURE__ */ l("div", { className: "mtx-align-buttons", children: [
74
+ /* @__PURE__ */ t("button", { type: "button", className: `mtx-align-btn ${n === "left" ? "is-active" : ""}`, onClick: () => r("left"), title: "Left", children: /* @__PURE__ */ t("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ t("path", { d: "M2 3h12v1.5H2V3zm0 4h8v1.5H2V7zm0 4h12v1.5H2v-1.5zM2 11h6v1.5H2V11z" }) }) }),
75
+ /* @__PURE__ */ t("button", { type: "button", className: `mtx-align-btn ${n === "center" ? "is-active" : ""}`, onClick: () => r("center"), title: "Center", children: /* @__PURE__ */ t("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ t("path", { d: "M2 3h12v1.5H2V3zm3 4h6v1.5H5V7zm-3 4h12v1.5H2v-1.5zM5 11h6v1.5H5V11z" }) }) }),
76
+ /* @__PURE__ */ t("button", { type: "button", className: `mtx-align-btn ${n === "right" ? "is-active" : ""}`, onClick: () => r("right"), title: "Right", children: /* @__PURE__ */ t("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ t("path", { d: "M2 3h12v1.5H2V3zm6 4h6v1.5H8V7zM2 11h12v1.5H2v-1.5zm6-4h6v1.5H8V7z" }) }) })
77
+ ] })
78
+ ] }),
79
+ /* @__PURE__ */ l("div", { className: "mtx-form-group", children: [
80
+ /* @__PURE__ */ t("label", { className: "mtx-form-label", children: "Vertical Align" }),
81
+ /* @__PURE__ */ l("div", { className: "mtx-align-buttons", children: [
82
+ /* @__PURE__ */ t("button", { type: "button", className: `mtx-align-btn ${a === "top" ? "is-active" : ""}`, onClick: () => c("top"), title: "Top", children: /* @__PURE__ */ t("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ t("path", { d: "M8 2l4 4h-3v8H7V6H4L8 2z", transform: "rotate(180 8 8)" }) }) }),
83
+ /* @__PURE__ */ t("button", { type: "button", className: `mtx-align-btn ${a === "middle" ? "is-active" : ""}`, onClick: () => c("middle"), title: "Middle", children: /* @__PURE__ */ t("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ t("rect", { x: "4", y: "6", width: "8", height: "4" }) }) }),
84
+ /* @__PURE__ */ t("button", { type: "button", className: `mtx-align-btn ${a === "bottom" ? "is-active" : ""}`, onClick: () => c("bottom"), title: "Bottom", children: /* @__PURE__ */ t("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ t("path", { d: "M8 2l4 4h-3v8H7V6H4L8 2z" }) }) })
85
+ ] })
86
+ ] })
87
+ ] }),
88
+ /* @__PURE__ */ l("div", { className: "mtx-modal-footer", children: [
89
+ /* @__PURE__ */ t("button", { type: "button", className: "mtx-btn mtx-btn-secondary", onClick: y, children: "Reset" }),
90
+ /* @__PURE__ */ t("button", { type: "button", className: "mtx-btn mtx-btn-secondary", onClick: o, children: "Cancel" }),
91
+ /* @__PURE__ */ t("button", { type: "submit", className: "mtx-btn mtx-btn-primary", children: "Apply" })
92
+ ] })
93
+ ] })
94
+ ]
95
+ }
96
+ ) }) : null;
97
+ });
98
+ M.displayName = "CellPropertiesDialogImpl";
99
+ export {
100
+ M as CellPropertiesDialogImpl
101
+ };
@@ -0,0 +1,43 @@
1
+ import { jsx as r } 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-xNPNdmbe.js";
4
+ function c(t) {
5
+ const e = t.getAttribute("data-latex") || t.getAttribute("latex");
6
+ if (!e) return;
7
+ const a = t.classList.contains("mtx-math-block");
8
+ try {
9
+ l.render(e, t, {
10
+ throwOnError: !1,
11
+ displayMode: a,
12
+ output: "htmlAndMathml"
13
+ });
14
+ } catch {
15
+ t.textContent = a ? `$$${e}$$` : `$${e}$`;
16
+ }
17
+ }
18
+ function p({ content: t, className: e }) {
19
+ const a = o(null), n = i(
20
+ () => s.sanitize(t, {
21
+ ADD_TAGS: ["math-field", "span", "div"],
22
+ ADD_ATTR: ["data-latex", "latex", "data-type", "data-mathml"],
23
+ FORBID_TAGS: ["script", "style", "iframe", "object", "embed"]
24
+ }),
25
+ [t]
26
+ );
27
+ return m(() => {
28
+ if (!a.current) return;
29
+ a.current.querySelectorAll(
30
+ '.mtx-math-inline, .mtx-math-block, [data-type="math-inline"], [data-type="math-block"]'
31
+ ).forEach(c);
32
+ }, [n]), /* @__PURE__ */ r(
33
+ "div",
34
+ {
35
+ ref: a,
36
+ className: `mtx-content-viewer${e ? ` ${e}` : ""}`,
37
+ dangerouslySetInnerHTML: { __html: n }
38
+ }
39
+ );
40
+ }
41
+ export {
42
+ p as C
43
+ };
@@ -0,0 +1,222 @@
1
+ import { jsxs as l, Fragment as E, jsx as e } from "react/jsx-runtime";
2
+ import { useId as I, useRef as P, useState as d, useCallback as s } from "react";
3
+ import { u as W } from "./index-DYxMVo98.js";
4
+ function O({
5
+ isOpen: f,
6
+ onClose: x,
7
+ onInsert: g,
8
+ onImageUpload: p
9
+ }) {
10
+ const k = I(), N = P(null), [u, y] = d(p ? "upload" : "url"), [r, U] = d(""), [m, w] = d(""), [F, h] = d(!1), [G, b] = d(!1), [v, o] = d(null), [D, i] = d(""), R = P(null), C = s(() => {
11
+ U(""), w(""), o(null), i(""), b(!1), h(!1);
12
+ }, []), n = s(() => {
13
+ C(), x();
14
+ }, [x, C]);
15
+ W({ isOpen: f, dialogRef: N, onClose: n });
16
+ const _ = s(
17
+ async (a) => {
18
+ if (!a.type.startsWith("image/")) {
19
+ i("File harus berupa gambar (jpg, png, gif, webp)");
20
+ return;
21
+ }
22
+ if (a.size > 10 * 1024 * 1024) {
23
+ i("Ukuran file maksimal 10MB");
24
+ return;
25
+ }
26
+ i("");
27
+ const c = URL.createObjectURL(a);
28
+ if (o(c), p) {
29
+ b(!0);
30
+ try {
31
+ const t = await p(a);
32
+ g(t, m || a.name), n();
33
+ } catch {
34
+ i("Upload gagal. Silakan coba lagi."), o(null);
35
+ } finally {
36
+ b(!1);
37
+ }
38
+ } else {
39
+ const t = new FileReader();
40
+ t.onload = () => {
41
+ const T = t.result;
42
+ g(T, m || a.name), n();
43
+ }, t.readAsDataURL(a);
44
+ }
45
+ },
46
+ [p, g, m, n]
47
+ ), M = s(
48
+ (a) => {
49
+ var t;
50
+ const c = (t = a.target.files) == null ? void 0 : t[0];
51
+ c && _(c);
52
+ },
53
+ [_]
54
+ ), S = s((a) => {
55
+ a.preventDefault(), a.stopPropagation(), h(!0);
56
+ }, []), j = s((a) => {
57
+ a.preventDefault(), a.stopPropagation(), h(!1);
58
+ }, []), A = s(
59
+ (a) => {
60
+ var t;
61
+ a.preventDefault(), a.stopPropagation(), h(!1);
62
+ const c = (t = a.dataTransfer.files) == null ? void 0 : t[0];
63
+ c && _(c);
64
+ },
65
+ [_]
66
+ ), L = s(() => {
67
+ if (!r.trim()) {
68
+ i("Masukkan URL gambar");
69
+ return;
70
+ }
71
+ try {
72
+ new URL(r);
73
+ } catch {
74
+ i("URL tidak valid");
75
+ return;
76
+ }
77
+ g(r.trim(), m || void 0), n();
78
+ }, [r, m, g, n]), B = s(() => {
79
+ if (r.trim())
80
+ try {
81
+ new URL(r), o(r.trim()), i("");
82
+ } catch {
83
+ i("URL tidak valid");
84
+ }
85
+ }, [r]);
86
+ return f ? /* @__PURE__ */ l(E, { children: [
87
+ /* @__PURE__ */ e("div", { className: "mtx-dialog-overlay", onClick: n }),
88
+ /* @__PURE__ */ l("div", { className: "mtx-image-dialog", ref: N, role: "dialog", "aria-modal": "true", "aria-labelledby": k, tabIndex: -1, children: [
89
+ /* @__PURE__ */ l("div", { className: "mtx-image-dialog__header", children: [
90
+ /* @__PURE__ */ e("h3", { id: k, children: "Sisipkan Gambar" }),
91
+ /* @__PURE__ */ e(
92
+ "button",
93
+ {
94
+ className: "mtx-image-dialog__close",
95
+ onClick: n,
96
+ "aria-label": "Tutup",
97
+ children: "✕"
98
+ }
99
+ )
100
+ ] }),
101
+ /* @__PURE__ */ l("div", { className: "mtx-image-dialog__tabs", children: [
102
+ /* @__PURE__ */ e(
103
+ "button",
104
+ {
105
+ className: `mtx-image-dialog__tab ${u === "upload" ? "is-active" : ""}`,
106
+ onClick: () => {
107
+ y("upload"), i(""), o(null);
108
+ },
109
+ children: "📁 Upload File"
110
+ }
111
+ ),
112
+ /* @__PURE__ */ e(
113
+ "button",
114
+ {
115
+ className: `mtx-image-dialog__tab ${u === "url" ? "is-active" : ""}`,
116
+ onClick: () => {
117
+ y("url"), i(""), o(null);
118
+ },
119
+ children: "🔗 URL"
120
+ }
121
+ )
122
+ ] }),
123
+ /* @__PURE__ */ l("div", { className: "mtx-image-dialog__body", children: [
124
+ u === "upload" && /* @__PURE__ */ l(
125
+ "div",
126
+ {
127
+ className: `mtx-image-dialog__dropzone ${F ? "is-dragging" : ""}`,
128
+ onDragOver: S,
129
+ onDragLeave: j,
130
+ onDrop: A,
131
+ onClick: () => {
132
+ var a;
133
+ return (a = R.current) == null ? void 0 : a.click();
134
+ },
135
+ children: [
136
+ /* @__PURE__ */ e(
137
+ "input",
138
+ {
139
+ ref: R,
140
+ type: "file",
141
+ accept: "image/*",
142
+ onChange: M,
143
+ style: { display: "none" }
144
+ }
145
+ ),
146
+ G ? /* @__PURE__ */ l("div", { className: "mtx-image-dialog__uploading", children: [
147
+ /* @__PURE__ */ e("div", { className: "mtx-spinner" }),
148
+ /* @__PURE__ */ e("p", { children: "Mengupload gambar..." })
149
+ ] }) : v ? /* @__PURE__ */ e("div", { className: "mtx-image-dialog__preview", children: /* @__PURE__ */ e("img", { src: v, alt: "Preview" }) }) : /* @__PURE__ */ l("div", { className: "mtx-image-dialog__placeholder", children: [
150
+ /* @__PURE__ */ l("svg", { width: "48", height: "48", viewBox: "0 0 48 48", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
151
+ /* @__PURE__ */ e("rect", { x: "4", y: "8", width: "40", height: "32", rx: "4" }),
152
+ /* @__PURE__ */ e("circle", { cx: "16", cy: "20", r: "4" }),
153
+ /* @__PURE__ */ e("path", { d: "M4 36l10-10 6 6 8-10 16 14" })
154
+ ] }),
155
+ /* @__PURE__ */ l("p", { children: [
156
+ /* @__PURE__ */ e("strong", { children: "Klik untuk pilih file" }),
157
+ " atau drag & drop ke sini"
158
+ ] }),
159
+ /* @__PURE__ */ e("span", { children: "JPG, PNG, GIF, WebP — Maks 10MB" })
160
+ ] })
161
+ ]
162
+ }
163
+ ),
164
+ u === "url" && /* @__PURE__ */ l("div", { className: "mtx-image-dialog__url-form", children: [
165
+ /* @__PURE__ */ e("label", { children: "URL Gambar" }),
166
+ /* @__PURE__ */ e("div", { className: "mtx-image-dialog__url-row", children: /* @__PURE__ */ e(
167
+ "input",
168
+ {
169
+ type: "url",
170
+ value: r,
171
+ onChange: (a) => U(a.target.value),
172
+ placeholder: "https://example.com/gambar.jpg",
173
+ onKeyDown: (a) => {
174
+ a.key === "Enter" && L();
175
+ },
176
+ onBlur: B,
177
+ autoFocus: !0
178
+ }
179
+ ) }),
180
+ v && /* @__PURE__ */ e("div", { className: "mtx-image-dialog__url-preview", children: /* @__PURE__ */ e(
181
+ "img",
182
+ {
183
+ src: v,
184
+ alt: "Preview",
185
+ onError: () => {
186
+ o(null), i("Gambar tidak dapat dimuat dari URL ini");
187
+ }
188
+ }
189
+ ) })
190
+ ] }),
191
+ /* @__PURE__ */ l("div", { className: "mtx-image-dialog__alt", children: [
192
+ /* @__PURE__ */ e("label", { children: "Teks Alternatif (opsional)" }),
193
+ /* @__PURE__ */ e(
194
+ "input",
195
+ {
196
+ type: "text",
197
+ value: m,
198
+ onChange: (a) => w(a.target.value),
199
+ placeholder: "Deskripsi gambar untuk aksesibilitas"
200
+ }
201
+ )
202
+ ] }),
203
+ D && /* @__PURE__ */ e("div", { className: "mtx-image-dialog__error", children: D })
204
+ ] }),
205
+ /* @__PURE__ */ l("div", { className: "mtx-image-dialog__footer", children: [
206
+ /* @__PURE__ */ e("button", { className: "mtx-image-dialog__btn mtx-image-dialog__btn--cancel", onClick: n, children: "Batal" }),
207
+ u === "url" && /* @__PURE__ */ e(
208
+ "button",
209
+ {
210
+ className: "mtx-image-dialog__btn mtx-image-dialog__btn--insert",
211
+ onClick: L,
212
+ disabled: !r.trim(),
213
+ children: "Sisipkan"
214
+ }
215
+ )
216
+ ] })
217
+ ] })
218
+ ] }) : null;
219
+ }
220
+ export {
221
+ O as ImageInsertDialog
222
+ };