erl-mathtextx-editor 0.1.8 → 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.
- package/CHANGELOG.md +157 -0
- package/README.md +262 -107
- package/dist/{CellPropertiesDialogImpl-DBgs-7H9.js → CellPropertiesDialogImpl-Cl0pxbeQ.js} +1 -1
- package/dist/{ContentViewer-CsFSAN_B.js → ContentViewer-RijJ5nlJ.js} +15 -14
- package/dist/{ImageInsertDialog-B24KHrgt.js → ImageInsertDialog-BVBl1y36.js} +27 -25
- package/dist/{InsertTableDialogImpl-B6_PRu5m.js → InsertTableDialogImpl-Cx3ShX7u.js} +1 -1
- package/dist/{LinkDialogImpl-BTA8u_qQ.js → LinkDialogImpl-gMjoZVma.js} +1 -1
- package/dist/MathTextXEditor.d.ts +1 -1
- package/dist/{TablePropertiesDialogImpl-CuRRWS4H.js → TablePropertiesDialogImpl-CrTTV3Zr.js} +1 -1
- package/dist/{TableTemplatesDialogImpl-CU8seEdV.js → TableTemplatesDialogImpl-DTcom8H5.js} +2 -2
- package/dist/assets/erl-mathtextx-editor.css +1 -1
- package/dist/assets/viewer.css +1 -1
- package/dist/components/ErrorBoundary.d.ts +18 -0
- package/dist/components/ImageEditDialog.d.ts +0 -1
- package/dist/components/TableMenu.d.ts +4 -1
- package/dist/erl-mathtextx-editor.js +2 -2
- package/dist/erl-mathtextx-editor.umd.cjs +345 -115
- package/dist/{index-UCSefQk0.js → index-CakccgVO.js} +3801 -3201
- package/dist/{index-CB1g0gXh.js → index-Djb9MY7m.js} +1 -1
- package/dist/index-QMz8TDH0.js +16549 -0
- package/dist/toolbar/MainToolbar.d.ts +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/utils/docxImporter.d.ts +31 -0
- package/dist/utils/pasteHandler.d.ts +14 -3
- package/dist/{viewer-deps-CjbAqdti.js → viewer-deps-BDYoL2Ts.js} +5794 -3489
- package/dist/viewer.js +1 -1
- package/package.json +6 -2
- package/dist/extensions/TableAlignPlugin.d.ts +0 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `erl-mathtextx-editor` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.9] - 2026-04-01
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **README Not Showing on NPM** — Added `README.md`, `CHANGELOG.md`, and `LICENSE` to `files` array in package.json
|
|
12
|
+
- **TypeScript Errors** — Added proper type annotations to `CustomTableView` class parameters
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **Package Files** — Expanded `files` field from `["dist"]` to `["dist", "README.md", "CHANGELOG.md", "LICENSE"]`
|
|
16
|
+
- **Version Bump** — 0.1.8 → 0.1.9 (previous version had missing README on npm)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [0.1.8] - 2026-04-01
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **Documentation Consistency** — Updated all .md files to reflect current version
|
|
24
|
+
- **CONTRIBUTING.md** — Fixed duplicate "Development Workflow" section and numbering issues
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- **Version Bump** — 0.1.7 → 0.1.8 (previous version already published)
|
|
28
|
+
- **Documentation Sync** — All markdown files now reference v0.1.8 consistently
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## [0.1.7] - 2026-03-31
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **Code Block Syntax Highlighting** — Added `@tiptap/extension-code-block-lowlight` with lowlight for syntax highlighting in code blocks
|
|
36
|
+
- Supports 100+ programming languages
|
|
37
|
+
- Dark theme with syntax colors (keywords, strings, numbers, comments, functions, etc.)
|
|
38
|
+
- Works in both editor and viewer
|
|
39
|
+
- **Table Column Resize** — TipTap built-in `resizable: true` for column width adjustment
|
|
40
|
+
- **Cell Selection Highlight** — Light blue background with dashed border when selecting multiple cells for merge
|
|
41
|
+
- **Collapsible Details/Summary** — CSS styles ready for TipTap v3 details extension
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- **Table Selection Visibility** — Fixed `.selectedCell` CSS selector to work with TipTap's default table structure
|
|
45
|
+
- **DOM Nesting Warning** — Removed invalid `<div>` inside `<tbody>` structure
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- **Removed Duplicate Dependencies** — Removed 16 packages already included in `@tiptap/starter-kit`:
|
|
49
|
+
- `@tiptap/extension-bold`, `bullet-list`, `code-block`, `document`
|
|
50
|
+
- `@tiptap/extension-dropcursor`, `gapcursor`, `hard-break`, `heading`
|
|
51
|
+
- `@tiptap/extension-history`, `horizontal-rule`, `italic`, `list-item`
|
|
52
|
+
- `@tiptap/extension-ordered-list`, `paragraph`, `strike`, `text`
|
|
53
|
+
- **Code Organization** — Extracted inline extensions to separate files:
|
|
54
|
+
- `IndentBackspace` → `src/extensions/IndentBackspace.ts`
|
|
55
|
+
- `SlashGraph` → `src/extensions/SlashGraph.ts`
|
|
56
|
+
|
|
57
|
+
### Internal
|
|
58
|
+
- Cleaner dependency tree with reduced bundle overhead
|
|
59
|
+
- Better code maintainability with separated extension files
|
|
60
|
+
|
|
61
|
+
## [0.1.6] - 2026-03-31
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
- **Backspace Join Behavior** — Backspace at start of paragraph now properly joins with previous paragraph when no indentation exists
|
|
65
|
+
- **Tab Key Dual Behavior** — Tab now inserts 4 spaces when cursor is in the middle of a paragraph, and indents paragraph when cursor is at the start
|
|
66
|
+
- **Tab Character Rendering** — Added `white-space: pre-wrap` to editor and viewer CSS for consistent whitespace display
|
|
67
|
+
- **Indent Extension Conflict** — Created CustomIndent extension to override base package Tab keyboard shortcuts that were conflicting with custom handlers
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- **CustomIndent Extension** — New extension that extends `@weiruo/tiptap-extension-indent` and removes its Tab keyboard shortcuts to prevent conflicts
|
|
71
|
+
- **Improved IndentBackspace** — Now checks actual indent level before attempting outdent, allowing default joinBackward behavior when no indentation exists
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- Enhanced Tab key behavior for better UX (similar to Notion/modern editors)
|
|
75
|
+
- Improved paragraph joining behavior to match expected editor behavior
|
|
76
|
+
|
|
77
|
+
## [0.1.5] - 2026-03-26
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
- **Custom CSS Tooltips** - Replace native browser tooltips
|
|
81
|
+
- Instant appearance (no delay)
|
|
82
|
+
- Dark-themed with smooth fade-in animation
|
|
83
|
+
- Arrow indicator pointing to button
|
|
84
|
+
- Consistent styling across all browsers
|
|
85
|
+
- **Tactile Button Feedback** - Physical feedback on click
|
|
86
|
+
- Toolbar buttons: scale(0.96) on click
|
|
87
|
+
- Math buttons: scale(0.96) on click
|
|
88
|
+
- Dialog buttons: scale(0.98) on click
|
|
89
|
+
- Provides satisfying micro-interaction
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
- Use `data-tooltip` attribute instead of native `title`
|
|
93
|
+
- Enhanced button `:active` states across all components
|
|
94
|
+
|
|
95
|
+
## [0.1.4] - 2026-03-26
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
- **Export MathTypeDialog** - Now available for standalone import
|
|
99
|
+
- `import { MathTypeDialog } from 'erl-mathtextx-editor'`
|
|
100
|
+
- Export both component and `MathTypeDialogProps` type
|
|
101
|
+
- **Export TemplatePanel** - Now available for standalone import
|
|
102
|
+
- `import { TemplatePanel } from 'erl-mathtextx-editor'`
|
|
103
|
+
- Export both component and `TemplatePanelProps` type
|
|
104
|
+
|
|
105
|
+
### Changed
|
|
106
|
+
- **Package naming consistency** - All documentation now uses `erl-mathtextx-editor` (not `@mathtextx/editor`)
|
|
107
|
+
- **Static imports** - MathTypeDialog and TemplatePanel now statically imported in MathTextXEditor.tsx
|
|
108
|
+
- **Removed Suspense wrappers** - No longer needed for static imports
|
|
109
|
+
- **Build warnings fixed** - Eliminated dynamic/static import conflict warnings
|
|
110
|
+
|
|
111
|
+
### Fixed
|
|
112
|
+
- Build warnings about dual import patterns
|
|
113
|
+
- Documentation inconsistencies across README, docs, and integration guides
|
|
114
|
+
|
|
115
|
+
### Internal
|
|
116
|
+
- ImageInsertDialog remains internal (not exported)
|
|
117
|
+
- Optimized bundle structure
|
|
118
|
+
|
|
119
|
+
## [0.1.3] - 2026-03-19
|
|
120
|
+
|
|
121
|
+
### Published
|
|
122
|
+
- Initial release to npm registry
|
|
123
|
+
- Package: `erl-mathtextx-editor@0.1.3`
|
|
124
|
+
|
|
125
|
+
## [0.1.0] - 2026-03-03
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
|
|
129
|
+
- **MathTextXEditor** — React component utama untuk rich text + math editing
|
|
130
|
+
- TipTap/ProseMirror editor engine
|
|
131
|
+
- MathLive visual math input (inline & block)
|
|
132
|
+
- Toolbar: bold, italic, underline, strike, heading, lists, table, image, link, code, blockquote
|
|
133
|
+
- Math toolbar: fraction, sqrt, power, subscript, sum, integral, limit, matrix, symbols
|
|
134
|
+
- Symbol palette dengan 100+ simbol matematika
|
|
135
|
+
- Graph extension (function plotting via Function Plot)
|
|
136
|
+
- Keyboard shortcut: Ctrl+S untuk save
|
|
137
|
+
- Slash command: `/graph` untuk insert grafik
|
|
138
|
+
|
|
139
|
+
- **ContentViewer** — Komponen read-only untuk menampilkan konten di frontend quiz/ujian
|
|
140
|
+
- XSS sanitization via DOMPurify
|
|
141
|
+
- Auto-render rumus matematika via KaTeX
|
|
142
|
+
- Standalone CSS (`content-style.css`) untuk styling konsisten
|
|
143
|
+
|
|
144
|
+
- **Template Library** — 60+ preset rumus matematika
|
|
145
|
+
- Kategori: Algebra, Geometri, Trigonometri, Kalkulus, Statistik, Fisika, Kimia
|
|
146
|
+
- Filter by level: SD, SMP, SMA
|
|
147
|
+
- Utility functions: `getTemplatesByLevel()`, `getTemplatesByCategory()`, `getTemplateCategories()`
|
|
148
|
+
|
|
149
|
+
- **Serializer Utilities**
|
|
150
|
+
- `getHTML()` — export konten sebagai HTML
|
|
151
|
+
- `toCompatibleHTML()` — konversi ke format CKEditor-compatible
|
|
152
|
+
- `sanitizeCKEditorHTML()` — bersihkan HTML dari CKEditor
|
|
153
|
+
|
|
154
|
+
- **Build Output**
|
|
155
|
+
- ES Module (`mathtextx-editor.js`)
|
|
156
|
+
- UMD (`mathtextx-editor.umd.cjs`)
|
|
157
|
+
- TypeScript declarations (`.d.ts`)
|
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
|
-
- 🧮 **
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- 🔒 **XSS Protection** — DOMPurify sanitization
|
|
24
|
-
-
|
|
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
|
|
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
|
-
###
|
|
54
|
+
### 2. Editor dengan Save Handler
|
|
56
55
|
|
|
57
56
|
```tsx
|
|
58
|
-
import {
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
###
|
|
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
|
|
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
|
|
89
|
-
<
|
|
90
|
-
<
|
|
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
|
-
| `
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
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
|
-
##
|
|
374
|
+
## ⚠️ Troubleshooting
|
|
172
375
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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-
|
|
3
|
+
import { u as B } from "./index-CakccgVO.js";
|
|
4
4
|
const M = z.memo(({
|
|
5
5
|
isOpen: s,
|
|
6
6
|
initialData: i = {},
|