@wiliarko/wysiwyg-editor 1.0.0 โ 1.0.4
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/INSTALLATION.md +316 -0
- package/README.md +277 -16
- package/package.json +4 -3
- package/wysiwyg.d.ts +86 -0
- package/wysiwyg.min.css +1 -1
- package/wysiwyg.min.js +10 -10
package/INSTALLATION.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Installation Guide โ @wiliarko/wysiwyg-editor
|
|
2
|
+
|
|
3
|
+
## NPM Installation
|
|
4
|
+
|
|
5
|
+
### 1. Install Package
|
|
6
|
+
```bash
|
|
7
|
+
npm install @wiliarko/wysiwyg-editor
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### 2. Add to HTML
|
|
11
|
+
```html
|
|
12
|
+
<link rel="stylesheet" href="node_modules/@wiliarko/wysiwyg-editor/wysiwyg.min.css">
|
|
13
|
+
<script src="node_modules/@wiliarko/wysiwyg-editor/wysiwyg.min.js"></script>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 3. Initialize
|
|
17
|
+
```html
|
|
18
|
+
<div id="my-editor"></div>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
const editor = WysiwygEditor.create('#my-editor', {
|
|
22
|
+
uploadUrl: '/api/upload-image', // optional
|
|
23
|
+
placeholder: 'Write something...',
|
|
24
|
+
minHeight: 300
|
|
25
|
+
});
|
|
26
|
+
</script>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Framework Integration
|
|
32
|
+
|
|
33
|
+
### Laravel
|
|
34
|
+
```bash
|
|
35
|
+
npm install @wiliarko/wysiwyg-editor
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**In Blade template:**
|
|
39
|
+
```blade
|
|
40
|
+
<link rel="stylesheet" href="{{ asset('node_modules/@wiliarko/wysiwyg-editor/wysiwyg.min.css') }}">
|
|
41
|
+
<script src="{{ asset('node_modules/@wiliarko/wysiwyg-editor/wysiwyg.min.js') }}"></script>
|
|
42
|
+
|
|
43
|
+
<div id="editor"></div>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
47
|
+
const editor = WysiwygEditor.create('#editor', {
|
|
48
|
+
uploadUrl: '{{ route("image.upload") }}'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
document.querySelector('form').addEventListener('submit', (e) => {
|
|
52
|
+
document.querySelector('[name="content"]').value = editor.getHTML();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Vue 3
|
|
59
|
+
```bash
|
|
60
|
+
npm install @wiliarko/wysiwyg-editor
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**In component:**
|
|
64
|
+
```vue
|
|
65
|
+
<template>
|
|
66
|
+
<div class="editor-container">
|
|
67
|
+
<div ref="editorElement"></div>
|
|
68
|
+
<p>{{ content }}</p>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<script setup>
|
|
73
|
+
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
74
|
+
|
|
75
|
+
const editorElement = ref(null)
|
|
76
|
+
const content = ref('')
|
|
77
|
+
let editor = null
|
|
78
|
+
|
|
79
|
+
onMounted(() => {
|
|
80
|
+
editor = WysiwygEditor.create(editorElement.value, {
|
|
81
|
+
uploadUrl: '/api/upload-image',
|
|
82
|
+
onChange: (html) => {
|
|
83
|
+
content.value = html
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
onBeforeUnmount(() => {
|
|
89
|
+
editor?.destroy()
|
|
90
|
+
})
|
|
91
|
+
</script>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### React
|
|
95
|
+
```bash
|
|
96
|
+
npm install @wiliarko/wysiwyg-editor
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**In component:**
|
|
100
|
+
```jsx
|
|
101
|
+
import { useEffect, useRef } from 'react'
|
|
102
|
+
|
|
103
|
+
export function Editor() {
|
|
104
|
+
const editorRef = useRef(null)
|
|
105
|
+
const editorInstance = useRef(null)
|
|
106
|
+
const [content, setContent] = React.useState('')
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (typeof window !== 'undefined' && window.WysiwygEditor) {
|
|
110
|
+
editorInstance.current = WysiwygEditor.create(editorRef.current, {
|
|
111
|
+
uploadUrl: '/api/upload-image',
|
|
112
|
+
onChange: (html) => setContent(html)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
return () => editorInstance.current?.destroy()
|
|
116
|
+
}
|
|
117
|
+
}, [])
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div>
|
|
121
|
+
<div ref={editorRef} style={{ minHeight: '400px' }} />
|
|
122
|
+
<p>Content: {content}</p>
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Next.js
|
|
129
|
+
```bash
|
|
130
|
+
npm install @wiliarko/wysiwyg-editor
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**In component:**
|
|
134
|
+
```jsx
|
|
135
|
+
import { useEffect, useRef } from 'react'
|
|
136
|
+
|
|
137
|
+
export default function Editor() {
|
|
138
|
+
const editorRef = useRef(null)
|
|
139
|
+
const editorInstance = useRef(null)
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
// Dynamic import to avoid SSR issues
|
|
143
|
+
if (typeof window !== 'undefined') {
|
|
144
|
+
require('@wiliarko/wysiwyg-editor')
|
|
145
|
+
|
|
146
|
+
editorInstance.current = window.WysiwygEditor.create(editorRef.current, {
|
|
147
|
+
uploadUrl: '/api/upload-image'
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return () => editorInstance.current?.destroy()
|
|
152
|
+
}, [])
|
|
153
|
+
|
|
154
|
+
return <div ref={editorRef} style={{ minHeight: '400px' }} />
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Nuxt 3
|
|
159
|
+
```bash
|
|
160
|
+
npm install @wiliarko/wysiwyg-editor
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**plugins/wysiwyg.client.ts:**
|
|
164
|
+
```typescript
|
|
165
|
+
export default defineNuxtPlugin(() => {
|
|
166
|
+
if (process.client) {
|
|
167
|
+
return {
|
|
168
|
+
provide: {
|
|
169
|
+
wysiwyg: window.WysiwygEditor
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**In component:**
|
|
177
|
+
```vue
|
|
178
|
+
<template>
|
|
179
|
+
<div ref="editor"></div>
|
|
180
|
+
</template>
|
|
181
|
+
|
|
182
|
+
<script setup>
|
|
183
|
+
const { $wysiwyg } = useNuxtApp()
|
|
184
|
+
const editor = ref(null)
|
|
185
|
+
|
|
186
|
+
onMounted(() => {
|
|
187
|
+
$wysiwyg.create(editor.value, {
|
|
188
|
+
uploadUrl: '/api/upload-image'
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
</script>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## CDN Installation
|
|
197
|
+
|
|
198
|
+
### From jsDelivr CDN
|
|
199
|
+
```html
|
|
200
|
+
<link rel="stylesheet"
|
|
201
|
+
href="https://cdn.jsdelivr.net/npm/@wiliarko/wysiwyg-editor@1.0.3/wysiwyg.min.css">
|
|
202
|
+
<script src="https://cdn.jsdelivr.net/npm/@wiliarko/wysiwyg-editor@1.0.3/wysiwyg.min.js"></script>
|
|
203
|
+
|
|
204
|
+
<div id="editor"></div>
|
|
205
|
+
|
|
206
|
+
<script>
|
|
207
|
+
const editor = WysiwygEditor.create('#editor')
|
|
208
|
+
</script>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### From unpkg CDN
|
|
212
|
+
```html
|
|
213
|
+
<link rel="stylesheet"
|
|
214
|
+
href="https://unpkg.com/@wiliarko/wysiwyg-editor@1.0.3/wysiwyg.min.css">
|
|
215
|
+
<script src="https://unpkg.com/@wiliarko/wysiwyg-editor@1.0.3/wysiwyg.min.js"></script>
|
|
216
|
+
|
|
217
|
+
<div id="editor"></div>
|
|
218
|
+
|
|
219
|
+
<script>
|
|
220
|
+
const editor = WysiwygEditor.create('#editor')
|
|
221
|
+
</script>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Configuration
|
|
227
|
+
|
|
228
|
+
### Basic Setup
|
|
229
|
+
```javascript
|
|
230
|
+
const editor = WysiwygEditor.create('#editor', {
|
|
231
|
+
placeholder: 'Start typing...',
|
|
232
|
+
minHeight: 300
|
|
233
|
+
})
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### With Upload
|
|
237
|
+
```javascript
|
|
238
|
+
const editor = WysiwygEditor.create('#editor', {
|
|
239
|
+
uploadUrl: '/api/upload',
|
|
240
|
+
minHeight: 400,
|
|
241
|
+
onChange: (html) => console.log(html)
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### With Input Sync
|
|
246
|
+
```html
|
|
247
|
+
<textarea id="content" name="content" hidden></textarea>
|
|
248
|
+
<div id="editor"></div>
|
|
249
|
+
|
|
250
|
+
<script>
|
|
251
|
+
const editor = WysiwygEditor.create('#editor', {
|
|
252
|
+
syncInput: '#content',
|
|
253
|
+
uploadUrl: '/api/upload'
|
|
254
|
+
})
|
|
255
|
+
</script>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Verify Installation
|
|
261
|
+
|
|
262
|
+
**Check in browser console:**
|
|
263
|
+
```javascript
|
|
264
|
+
console.log(window.WysiwygEditor) // Should show WysiwygEditor class
|
|
265
|
+
console.log(WysiwygEditor.getPlugins()) // Should list all plugins
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Troubleshooting
|
|
271
|
+
|
|
272
|
+
### "WysiwygEditor is not defined"
|
|
273
|
+
- Verify `<script src="wysiwyg.min.js">` is in HTML
|
|
274
|
+
- Check script loads before your initialization code
|
|
275
|
+
- Hard refresh (Ctrl+Shift+R)
|
|
276
|
+
|
|
277
|
+
### "CSS not loading / styles broken"
|
|
278
|
+
- Verify `<link>` tag for CSS
|
|
279
|
+
- Check correct path to `wysiwyg.min.css`
|
|
280
|
+
- Ensure CSS loads before JS
|
|
281
|
+
|
|
282
|
+
### "Upload not working"
|
|
283
|
+
- Set `uploadUrl` to your server endpoint
|
|
284
|
+
- Server must return `{ "ok": true, "url": "..." }`
|
|
285
|
+
- Check CORS if different origin
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Version Updates
|
|
290
|
+
|
|
291
|
+
### Check current version
|
|
292
|
+
```bash
|
|
293
|
+
npm list @wiliarko/wysiwyg-editor
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Update to latest
|
|
297
|
+
```bash
|
|
298
|
+
npm update @wiliarko/wysiwyg-editor
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Install specific version
|
|
302
|
+
```bash
|
|
303
|
+
npm install @wiliarko/wysiwyg-editor@1.0.3
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Support & Issues
|
|
309
|
+
|
|
310
|
+
- **GitHub:** [github.com/wiliarko/wysiwyg-editor](https://github.com/wiliarko/wysiwyg-editor)
|
|
311
|
+
- **NPM:** [@wiliarko/wysiwyg-editor](https://www.npmjs.com/package/@wiliarko/wysiwyg-editor)
|
|
312
|
+
- **Issues:** Report at GitHub Issues
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
**Happy editing! ๐**
|
package/README.md
CHANGED
|
@@ -71,22 +71,207 @@ editor.destroy();
|
|
|
71
71
|
}
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
##
|
|
74
|
+
## Image Alignment
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Images can be aligned left, right, center, or as block elements.
|
|
77
77
|
|
|
78
|
+
### In Editor
|
|
79
|
+
Click image โ toolbar appears โ choose alignment button
|
|
80
|
+
|
|
81
|
+
### In View
|
|
82
|
+
When you display the saved HTML content, image alignment styles are **automatically included** in `wysiwyg.min.css`.
|
|
83
|
+
|
|
84
|
+
Just load the CSS:
|
|
85
|
+
```html
|
|
86
|
+
<link rel="stylesheet" href="wysiwyg.min.css">
|
|
87
|
+
<!-- Images will display with correct alignment automatically -->
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Alignment Classes (Generated by Editor)
|
|
91
|
+
|
|
92
|
+
| Alignment | CSS Class | Behavior |
|
|
93
|
+
|-----------|-----------|----------|
|
|
94
|
+
| Default | (no class) | Inline with text |
|
|
95
|
+
| Left | `wy-img-align-left` | Float left, text wraps right |
|
|
96
|
+
| Right | `wy-img-align-right` | Float right, text wraps left |
|
|
97
|
+
| Center | `wy-img-align-center` | Centered, full width block |
|
|
98
|
+
| Block Left | `wy-img-align-block-left` | Block element, margin left 0 |
|
|
99
|
+
| Block Right | `wy-img-align-block-right` | Block element, margin right 0 |
|
|
100
|
+
|
|
101
|
+
### Example HTML Output
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<!-- User aligned image left in editor -->
|
|
105
|
+
<img src="/uploads/image.jpg" class="wy-img-align-left" alt="Description" />
|
|
106
|
+
<p>Text will wrap around this image...</p>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**CSS in `wysiwyg.min.css` automatically styles this correctly!**
|
|
110
|
+
|
|
111
|
+
### Quick Setup
|
|
112
|
+
|
|
113
|
+
**Option 1: URL Only (No Upload)**
|
|
114
|
+
```html
|
|
115
|
+
<!-- Users can only insert from URL, no file upload -->
|
|
116
|
+
<div data-wysiwyg
|
|
117
|
+
data-wysiwyg-height="400"
|
|
118
|
+
data-wysiwyg-input="#content"></div>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Option 2: With File Upload**
|
|
122
|
+
```html
|
|
123
|
+
<!-- File upload enabled -->
|
|
124
|
+
<div data-wysiwyg
|
|
125
|
+
data-wysiwyg-upload-url="/api/upload-image"
|
|
126
|
+
data-wysiwyg-height="400"
|
|
127
|
+
data-wysiwyg-input="#content"></div>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Upload Endpoint Format
|
|
131
|
+
|
|
132
|
+
Your server endpoint must:
|
|
133
|
+
1. Accept `POST` request with `multipart/form-data`
|
|
134
|
+
2. Field name: `image` (binary file)
|
|
135
|
+
3. Return JSON response
|
|
136
|
+
|
|
137
|
+
**Request:**
|
|
138
|
+
```
|
|
139
|
+
POST /api/upload-image
|
|
140
|
+
Content-Type: multipart/form-data
|
|
141
|
+
|
|
142
|
+
Field: "image" (File)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Response (Success):**
|
|
78
146
|
```json
|
|
79
147
|
{
|
|
80
|
-
"
|
|
81
|
-
"url": "https://yourserver.com/uploads/image.jpg"
|
|
148
|
+
"ok": true,
|
|
149
|
+
"url": "https://yourserver.com/uploads/image-xyz.jpg"
|
|
82
150
|
}
|
|
83
151
|
```
|
|
84
152
|
|
|
85
|
-
|
|
153
|
+
**Response (Error):**
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"ok": false,
|
|
157
|
+
"error": "File too large"
|
|
158
|
+
}
|
|
86
159
|
```
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
160
|
+
|
|
161
|
+
### Server Implementation
|
|
162
|
+
|
|
163
|
+
#### Node.js/Express
|
|
164
|
+
```javascript
|
|
165
|
+
const express = require('express');
|
|
166
|
+
const multer = require('multer');
|
|
167
|
+
const path = require('path');
|
|
168
|
+
|
|
169
|
+
const upload = multer({
|
|
170
|
+
dest: 'public/uploads/',
|
|
171
|
+
limits: { fileSize: 5 * 1024 * 1024 },
|
|
172
|
+
fileFilter: (req, file, cb) => {
|
|
173
|
+
if (file.mimetype.startsWith('image/')) {
|
|
174
|
+
cb(null, true);
|
|
175
|
+
} else {
|
|
176
|
+
cb(new Error('Only images allowed'));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
app.post('/api/upload-image', upload.single('image'), (req, res) => {
|
|
182
|
+
if (!req.file) {
|
|
183
|
+
return res.json({ ok: false, error: 'No file uploaded' });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const url = `/uploads/${req.file.filename}`;
|
|
187
|
+
res.json({ ok: true, url });
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### PHP (Laravel)
|
|
192
|
+
```php
|
|
193
|
+
// routes/api.php
|
|
194
|
+
Route::post('/upload-image', [ImageController::class, 'upload']);
|
|
195
|
+
|
|
196
|
+
// app/Http/Controllers/ImageController.php
|
|
197
|
+
class ImageController extends Controller
|
|
198
|
+
{
|
|
199
|
+
public function upload(Request $request)
|
|
200
|
+
{
|
|
201
|
+
$request->validate([
|
|
202
|
+
'image' => 'required|image|max:5120'
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
$path = $request->file('image')->store('uploads', 'public');
|
|
206
|
+
|
|
207
|
+
return response()->json([
|
|
208
|
+
'ok' => true,
|
|
209
|
+
'url' => asset('storage/' . $path)
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### PHP (Vanilla)
|
|
216
|
+
```php
|
|
217
|
+
<?php
|
|
218
|
+
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) {
|
|
219
|
+
$file = $_FILES['image'];
|
|
220
|
+
|
|
221
|
+
// Validate
|
|
222
|
+
if ($file['size'] > 5 * 1024 * 1024) {
|
|
223
|
+
echo json_encode(['ok' => false, 'error' => 'File too large']);
|
|
224
|
+
exit;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!strpos($file['type'], 'image/')) {
|
|
228
|
+
echo json_encode(['ok' => false, 'error' => 'Only images allowed']);
|
|
229
|
+
exit;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Save
|
|
233
|
+
$filename = uniqid() . '-' . basename($file['name']);
|
|
234
|
+
move_uploaded_file($file['tmp_name'], 'uploads/' . $filename);
|
|
235
|
+
|
|
236
|
+
echo json_encode([
|
|
237
|
+
'ok' => true,
|
|
238
|
+
'url' => '/uploads/' . $filename
|
|
239
|
+
]);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### Python/Django
|
|
244
|
+
```python
|
|
245
|
+
# urls.py
|
|
246
|
+
from django.urls import path
|
|
247
|
+
from . import views
|
|
248
|
+
|
|
249
|
+
urlpatterns = [
|
|
250
|
+
path('api/upload-image/', views.upload_image, name='upload_image'),
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
# views.py
|
|
254
|
+
from django.http import JsonResponse
|
|
255
|
+
from django.views.decorators.http import require_http_methods
|
|
256
|
+
|
|
257
|
+
@require_http_methods(["POST"])
|
|
258
|
+
def upload_image(request):
|
|
259
|
+
if 'image' not in request.FILES:
|
|
260
|
+
return JsonResponse({'ok': False, 'error': 'No file uploaded'})
|
|
261
|
+
|
|
262
|
+
image = request.FILES['image']
|
|
263
|
+
|
|
264
|
+
if image.size > 5 * 1024 * 1024:
|
|
265
|
+
return JsonResponse({'ok': False, 'error': 'File too large'})
|
|
266
|
+
|
|
267
|
+
# Save file
|
|
268
|
+
from django.core.files.storage import default_storage
|
|
269
|
+
path = default_storage.save(f'uploads/{image.name}', image)
|
|
270
|
+
|
|
271
|
+
return JsonResponse({
|
|
272
|
+
'ok': True,
|
|
273
|
+
'url': default_storage.url(path)
|
|
274
|
+
})
|
|
90
275
|
```
|
|
91
276
|
|
|
92
277
|
## Toolbar Plugins
|
|
@@ -250,16 +435,92 @@ WysiwygEditor.registerPlugin({
|
|
|
250
435
|
- **No dependencies**: Faster load, zero conflicts
|
|
251
436
|
- **contenteditable based**: Native browser support
|
|
252
437
|
|
|
253
|
-
##
|
|
438
|
+
## Troubleshooting
|
|
439
|
+
|
|
440
|
+
### Upload Returns 404
|
|
441
|
+
**Problem:** `POST /wysiwyg/upload-image 404 (Not Found)`
|
|
442
|
+
|
|
443
|
+
**Solution:** Set custom upload URL matching your endpoint
|
|
444
|
+
```html
|
|
445
|
+
<div data-wysiwyg
|
|
446
|
+
data-wysiwyg-upload-url="/your-api/upload"
|
|
447
|
+
style="height: 400px;"></div>
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Upload Returns 403 (CSRF/Auth)
|
|
451
|
+
**Problem:** Upload fails with 403 Forbidden
|
|
452
|
+
|
|
453
|
+
**Solution:** Add CSRF token (Laravel) or auth headers
|
|
454
|
+
```php
|
|
455
|
+
// Laravel: Add middleware or CSRF token
|
|
456
|
+
Route::post('/upload', [...])
|
|
457
|
+
->middleware('web'); // includes CSRF protection
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Image Size Limit
|
|
461
|
+
**Problem:** "File too large" error
|
|
462
|
+
|
|
463
|
+
**Editor limit:** 5MB (max file size)
|
|
464
|
+
**Configure in your server:** Allow at least 5MB
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
// Node.js
|
|
468
|
+
limits: { fileSize: 5 * 1024 * 1024 }
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### No Upload Button Appears
|
|
472
|
+
**Problem:** Upload icon not showing in toolbar
|
|
473
|
+
|
|
474
|
+
**Solution:** Make sure `uploadUrl` is set
|
|
475
|
+
```html
|
|
476
|
+
<!-- โ Wrong - no upload url -->
|
|
477
|
+
<div data-wysiwyg></div>
|
|
478
|
+
|
|
479
|
+
<!-- โ Correct - upload enabled -->
|
|
480
|
+
<div data-wysiwyg
|
|
481
|
+
data-wysiwyg-upload-url="/api/upload"></div>
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### "Cannot read properties of undefined"
|
|
485
|
+
**Problem:** `WysiwygEditor is not defined`
|
|
486
|
+
|
|
487
|
+
**Solution:** Ensure script is loaded before initialization
|
|
488
|
+
```html
|
|
489
|
+
<!-- โ Correct order -->
|
|
490
|
+
<link rel="stylesheet" href="wysiwyg.min.css">
|
|
491
|
+
<script src="wysiwyg.min.js"></script>
|
|
492
|
+
|
|
493
|
+
<script>
|
|
494
|
+
const editor = WysiwygEditor.create('#editor')
|
|
495
|
+
</script>
|
|
496
|
+
```
|
|
254
497
|
|
|
255
|
-
|
|
498
|
+
## Browser Support
|
|
499
|
+
|
|
500
|
+
- Chrome/Edge 60+
|
|
501
|
+
- Firefox 55+
|
|
502
|
+
- Safari 11+
|
|
503
|
+
- Mobile browsers (iOS Safari 12+, Chrome Mobile)
|
|
256
504
|
|
|
257
|
-
##
|
|
505
|
+
## Performance Tips
|
|
258
506
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
-
|
|
507
|
+
1. **Lazy load** for large pages
|
|
508
|
+
```javascript
|
|
509
|
+
if (document.querySelector('[data-wysiwyg]')) {
|
|
510
|
+
const script = document.createElement('script');
|
|
511
|
+
script.src = 'wysiwyg.min.js';
|
|
512
|
+
document.body.appendChild(script);
|
|
513
|
+
}
|
|
514
|
+
```
|
|
262
515
|
|
|
263
|
-
|
|
516
|
+
2. **Compress images** on upload
|
|
517
|
+
```javascript
|
|
518
|
+
// Server-side: Use ImageMagick, ffmpeg, Pillow, etc.
|
|
519
|
+
```
|
|
264
520
|
|
|
265
|
-
**
|
|
521
|
+
3. **Use CDN** for bundle
|
|
522
|
+
```html
|
|
523
|
+
<link rel="stylesheet"
|
|
524
|
+
href="https://cdn.example.com/wysiwyg.min.css">
|
|
525
|
+
<script src="https://cdn.example.com/wysiwyg.min.js"></script>
|
|
526
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wiliarko/wysiwyg-editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Custom rich-text editor with zero dependencies. Supports formatting, images, tables, mentions, emojis, and more.",
|
|
5
5
|
"main": "wysiwyg.min.js",
|
|
6
6
|
"module": "wysiwyg.min.js",
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"wysiwyg.min.js",
|
|
10
10
|
"wysiwyg.min.css",
|
|
11
|
-
"demo.html",
|
|
12
11
|
"README.md",
|
|
13
|
-
"
|
|
12
|
+
"INSTALLATION.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"wysiwyg.d.ts"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
17
|
"test": "echo \"No tests for dist package\""
|
package/wysiwyg.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WYSIWYG Editor Standalone
|
|
3
|
+
* TypeScript Definitions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface WysiwygEditorOptions {
|
|
7
|
+
/** Editor placeholder text */
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
/** Minimum height in pixels */
|
|
10
|
+
minHeight?: number;
|
|
11
|
+
/** Image upload endpoint URL */
|
|
12
|
+
uploadUrl?: string;
|
|
13
|
+
/** Custom upload function */
|
|
14
|
+
uploadFn?: (file: File, url: string, onProgress: (percent: number) => void) => Promise<string>;
|
|
15
|
+
/** Content change callback */
|
|
16
|
+
onChange?: (html: string) => void;
|
|
17
|
+
/** Hidden input selector to sync content */
|
|
18
|
+
syncInput?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WysiwygPlugin {
|
|
22
|
+
name: string;
|
|
23
|
+
title: string;
|
|
24
|
+
icon: string;
|
|
25
|
+
group?: string;
|
|
26
|
+
action: (editor: WysiwygEditor) => void;
|
|
27
|
+
isActive?: (editor: WysiwygEditor) => boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* WYSIWYG Editor Class
|
|
32
|
+
*/
|
|
33
|
+
export class WysiwygEditor {
|
|
34
|
+
constructor(element: HTMLElement | string, options?: WysiwygEditorOptions);
|
|
35
|
+
|
|
36
|
+
/** Get current editor content as HTML */
|
|
37
|
+
getHTML(): string;
|
|
38
|
+
|
|
39
|
+
/** Set editor content */
|
|
40
|
+
setHTML(html: string): void;
|
|
41
|
+
|
|
42
|
+
/** Focus on editor */
|
|
43
|
+
focus(): void;
|
|
44
|
+
|
|
45
|
+
/** Destroy editor and cleanup */
|
|
46
|
+
destroy(): void;
|
|
47
|
+
|
|
48
|
+
/** Toggle between WYSIWYG and HTML source mode */
|
|
49
|
+
toggleSource(): void;
|
|
50
|
+
|
|
51
|
+
/** Check if in HTML source mode */
|
|
52
|
+
isSourceMode(): boolean;
|
|
53
|
+
|
|
54
|
+
/** Open image insertion dialog */
|
|
55
|
+
openImageDialog(): void;
|
|
56
|
+
|
|
57
|
+
/** Get contenteditable element */
|
|
58
|
+
getArea(): HTMLElement;
|
|
59
|
+
|
|
60
|
+
/** Execute browser command */
|
|
61
|
+
exec(command: string, value?: string | null): void;
|
|
62
|
+
|
|
63
|
+
/** Query command state */
|
|
64
|
+
queryCommand(command: string): boolean;
|
|
65
|
+
|
|
66
|
+
/** Get current active block element */
|
|
67
|
+
getActiveBlock(): string;
|
|
68
|
+
|
|
69
|
+
/** Register a custom plugin (static) */
|
|
70
|
+
static registerPlugin(plugin: WysiwygPlugin): void;
|
|
71
|
+
|
|
72
|
+
/** Get all registered plugins (static) */
|
|
73
|
+
static getPlugins(): WysiwygPlugin[];
|
|
74
|
+
|
|
75
|
+
/** Create editor instance (static factory method) */
|
|
76
|
+
static create(element: HTMLElement | string, options?: WysiwygEditorOptions): WysiwygEditor;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Global namespace */
|
|
80
|
+
declare global {
|
|
81
|
+
interface Window {
|
|
82
|
+
WysiwygEditor: typeof WysiwygEditor;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default WysiwygEditor;
|
package/wysiwyg.min.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.wysiwyg-editor{--wy-border: #e5e7eb;--wy-border-focus: #6366f1;--wy-bg: #ffffff;--wy-bg-toolbar: #f9fafb;--wy-text: #111827;--wy-muted: #9ca3af;--wy-btn-hover: #f3f4f6;--wy-btn-active: #e0e7ff;--wy-btn-active-fg:#4338ca;--wy-radius: .75rem;--wy-sep: #e5e7eb;display:flex;flex-direction:column;border:1.5px solid var(--wy-border);border-radius:var(--wy-radius);background:var(--wy-bg);overflow:hidden;transition:border-color .15s ease,box-shadow .15s ease;font-family:inherit}.wysiwyg-editor:focus-within{border-color:var(--wy-border-focus);box-shadow:0 0 0 3px #6366f11f}.wysiwyg-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:2px;padding:6px 8px;background:var(--wy-bg-toolbar);border-bottom:1px solid var(--wy-border)}.wysiwyg-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--wy-text);cursor:pointer;transition:background .1s ease,color .1s ease;flex-shrink:0}.wysiwyg-btn:hover{background:var(--wy-btn-hover)}.wysiwyg-btn.is-active{background:var(--wy-btn-active);color:var(--wy-btn-active-fg)}.wysiwyg-btn span{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%}.wysiwyg-separator{width:1px;height:20px;background:var(--wy-sep);margin:0 4px;flex-shrink:0}.wysiwyg-area{flex:1;padding:14px 16px;outline:none;color:var(--wy-text);line-height:1.7;word-break:break-word;overflow-y:auto}.wysiwyg-area:empty:before{content:attr(data-placeholder);color:var(--wy-muted);pointer-events:none}.wysiwyg-area h1,.wysiwyg-area h2,.wysiwyg-area h3{font-weight:700;line-height:1.3;margin-top:1em;margin-bottom:.4em;color:var(--wy-text)}.wysiwyg-area h1{font-size:1.75em}.wysiwyg-area h2{font-size:1.35em}.wysiwyg-area h3{font-size:1.1em}.wysiwyg-area h1:first-child,.wysiwyg-area h2:first-child,.wysiwyg-area h3:first-child{margin-top:0}.wysiwyg-area p{margin:0 0 .75em}.wysiwyg-area p:last-child{margin-bottom:0}.wysiwyg-area strong{font-weight:700}.wysiwyg-area em{font-style:italic}.wysiwyg-area u{text-decoration:underline}.wysiwyg-area s{text-decoration:line-through}.wysiwyg-area ul,.wysiwyg-area ol{padding-left:1.5em;margin:0 0 .75em}.wysiwyg-area ul{list-style-type:disc}.wysiwyg-area ol{list-style-type:decimal}.wysiwyg-area li{margin-bottom:.25em}.wysiwyg-area blockquote{border-left:3px solid var(--wy-border-focus);margin:.75em 0;padding:.5em 1em;background:#6366f10d;border-radius:0 6px 6px 0;color:#4b5563}.wysiwyg-area a{color:var(--wy-btn-active-fg);text-decoration:underline}.wysiwyg-area a:hover{opacity:.8}.wysiwyg-source-area{flex:1;padding:14px 16px;outline:none;color:#a5b4fc;background:#1e1b4b;font-family:Fira Code,Cascadia Code,Consolas,Monaco,monospace;font-size:.8125rem;line-height:1.7;resize:none;border:none;width:100%;box-sizing:border-box;min-height:200px}.wysiwyg-source-area:focus{outline:none}.dark .wysiwyg-source-area{background:#0f0c29}@media(prefers-color-scheme:dark){.wysiwyg-editor{--wy-border: #374151;--wy-bg: #1f2937;--wy-bg-toolbar: #111827;--wy-text: #f9fafb;--wy-muted: #6b7280;--wy-btn-hover: #374151;--wy-btn-active: #312e81;--wy-btn-active-fg:#a5b4fc;--wy-sep: #374151}.wysiwyg-area blockquote{background:#6366f11a;color:#d1d5db}}.dark .wysiwyg-editor{--wy-border: #374151;--wy-bg: #1f2937;--wy-bg-toolbar: #111827;--wy-text: #f9fafb;--wy-muted: #6b7280;--wy-btn-hover: #374151;--wy-btn-active: #312e81;--wy-btn-active-fg:#a5b4fc;--wy-sep: #374151}.dark .wysiwyg-area blockquote{background:#6366f11a;color:#d1d5db}.wysiwyg-area img{max-width:100%;height:auto;border-radius:4px;cursor:pointer;vertical-align:bottom;transition:outline-color .1s ease}.wysiwyg-area img:hover{outline:2px solid rgb(99 102 241 / .35);outline-offset:2px}.wysiwyg-area img.wy-img-align-center{display:block;float:none;margin:.5em auto}.wysiwyg-area img.wy-img-align-left{float:left;margin:.25em 1.25em .5em 0}.wysiwyg-area img.wy-img-align-right{float:right;margin:.25em 0 .5em 1.25em}.wysiwyg-area img.wy-img-align-block-left{display:block;float:none;margin:.5em auto .5em 0}.wysiwyg-area img.wy-img-align-block-right{display:block;float:none;margin:.5em 0 .5em auto}.wy-img-uploading{display:inline-block;padding:4px 10px;border-radius:6px;background:#6366f11a;color:#4338ca;font-size:.8125em;font-style:italic}.wy-img-uploading--error{background:#f43f5e1a;color:#e11d48}.wy-img-toolbar{position:fixed;z-index:10000;display:none;align-items:center;gap:2px;padding:4px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 8px 24px #00000026}.wy-img-toolbar button{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:none;border-radius:6px;background:transparent;color:#111827;cursor:pointer}.wy-img-toolbar button:hover{background:#f3f4f6}.wy-img-toolbar button.is-active{background:#e0e7ff;color:#4338ca}.wy-img-toolbar__sep{width:1px;height:18px;background:#e5e7eb;margin:0 4px;flex-shrink:0}.wy-img-resize-box{position:fixed;z-index:9999;display:none;box-sizing:border-box;border:1.5px dashed #6366f1;pointer-events:none}.wy-img-resize-handle{position:absolute;width:10px;height:10px;background:#fff;border:2px solid #6366f1;border-radius:2px;pointer-events:all}.wy-img-resize-handle[data-dir=nw]{top:-6px;left:-6px;cursor:nwse-resize}.wy-img-resize-handle[data-dir=ne]{top:-6px;right:-6px;cursor:nesw-resize}.wy-img-resize-handle[data-dir=sw]{bottom:-6px;left:-6px;cursor:nesw-resize}.wy-img-resize-handle[data-dir=se]{bottom:-6px;right:-6px;cursor:nwse-resize}.wy-img-modal{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center}.wy-img-modal__backdrop{position:absolute;inset:0;background:#0f0f1480;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.wy-img-modal__box{position:relative;width:100%;max-width:440px;max-height:90vh;overflow-y:auto;margin:16px;background:#fff;border-radius:16px;box-shadow:0 20px 60px #0000004d}.wy-img-modal__head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e5e7eb}.wy-img-modal__title{font-weight:600;font-size:.9375rem;color:#111827}.wy-img-modal__close{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:8px;background:transparent;color:#9ca3af;cursor:pointer}.wy-img-modal__close:hover{background:#f3f4f6;color:#111827}.wy-img-modal__tabs{display:flex;gap:6px;padding:14px 20px 0}.wy-img-tab{flex:1;padding:8px 12px;border:none;border-radius:8px;background:#f9fafb;color:#6b7280;font-size:.8125rem;font-weight:500;cursor:pointer;transition:background .1s ease,color .1s ease}.wy-img-tab.is-active{background:#e0e7ff;color:#4338ca}.wy-img-modal__panel{padding:16px 20px 0}.wy-img-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:28px 16px;border:2px dashed #e5e7eb;border-radius:12px;cursor:pointer;text-align:center;transition:border-color .15s ease,background .15s ease}.wy-img-dropzone:hover,.wy-img-dropzone.is-dragover{border-color:#6366f1;background:#6366f10d}.wy-img-dropzone__text{margin:0;font-size:.8125rem;color:#111827}.wy-img-dropzone__text span{color:#6366f1;font-weight:600}.wy-img-dropzone__hint{margin:0;font-size:.6875rem;color:#9ca3af}.wy-img-file{position:absolute;width:1px;height:1px;overflow:hidden;opacity:0}.wy-img-preview{position:relative;display:block;width:max-content;max-width:100%;margin:12px auto 0}.wy-img-preview__img{display:block;max-height:180px;max-width:100%;border-radius:8px}.wy-img-preview__remove{position:absolute;top:-8px;right:-8px;width:22px;height:22px;border:none;border-radius:999px;background:#111827;color:#fff;font-size:14px;line-height:1;cursor:pointer}.wy-img-modal__progress{margin-top:12px;height:6px;border-radius:999px;background:#f3f4f6;overflow:hidden}.wy-img-modal__progress-bar{height:100%;width:0%;background:linear-gradient(90deg,#6366f1,#8b5cf6);transition:width .15s ease}.wy-img-url-label{display:block;font-size:.8125rem;font-weight:500;color:#111827}.wy-img-url-input,.wy-img-alt-input,.wy-img-width-input,.wy-img-align-select{display:block;width:100%;margin-top:6px;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;background:#fff;color:#111827;font-size:.8125rem;font-family:inherit;box-sizing:border-box}.wy-img-url-input:focus,.wy-img-alt-input:focus,.wy-img-width-input:focus,.wy-img-align-select:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px #6366f11f}.wy-img-url-preview img{max-height:120px;max-width:100%;border-radius:8px}.wy-img-modal__options{display:flex;gap:12px;padding:4px 20px 0}.wy-img-opt-label{flex:1;font-size:.8125rem;font-weight:500;color:#111827}.wy-img-modal__error{margin:12px 20px 0;padding:8px 12px;border-radius:8px;background:#f43f5e1a;color:#e11d48;font-size:.75rem}.wy-img-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px 20px}.wy-img-modal__cancel,.wy-img-modal__insert{padding:8px 18px;border:none;border-radius:8px;font-size:.8125rem;font-weight:600;cursor:pointer}.wy-img-modal__cancel{background:#f3f4f6;color:#111827}.wy-img-modal__cancel:hover{background:#e5e7eb}.wy-img-modal__insert{background:linear-gradient(90deg,#4f46e5,#7c3aed);color:#fff}.wy-img-modal__insert:disabled{opacity:.4;cursor:not-allowed}@media(prefers-color-scheme:dark){.wy-img-toolbar{background:#1f2937;border-color:#374151}.wy-img-toolbar button{color:#f9fafb}.wy-img-toolbar button:hover{background:#374151}.wy-img-toolbar button.is-active{background:#312e81;color:#a5b4fc}.wy-img-toolbar__sep{background:#374151}.wy-img-modal__box{background:#1f2937}.wy-img-modal__head{border-color:#374151}.wy-img-modal__title{color:#f9fafb}.wy-img-modal__close{color:#9ca3af}.wy-img-modal__close:hover{background:#374151;color:#f9fafb}.wy-img-tab{background:#111827;color:#9ca3af}.wy-img-tab.is-active{background:#312e81;color:#a5b4fc}.wy-img-dropzone{border-color:#374151}.wy-img-dropzone:hover,.wy-img-dropzone.is-dragover{border-color:#6366f1;background:#6366f11a}.wy-img-dropzone__text,.wy-img-url-label,.wy-img-opt-label{color:#f9fafb}.wy-img-url-input,.wy-img-alt-input,.wy-img-width-input,.wy-img-align-select{background:#111827;border-color:#374151;color:#f9fafb}.wy-img-modal__progress{background:#111827}.wy-img-modal__cancel{background:#374151;color:#f9fafb}.wy-img-modal__cancel:hover{background:#4b5563}}.dark .wy-img-toolbar{background:#1f2937;border-color:#374151}.dark .wy-img-toolbar button{color:#f9fafb}.dark .wy-img-toolbar button:hover{background:#374151}.dark .wy-img-toolbar button.is-active{background:#312e81;color:#a5b4fc}.dark .wy-img-toolbar__sep{background:#374151}.dark .wy-img-modal__box{background:#1f2937}.dark .wy-img-modal__head{border-color:#374151}.dark .wy-img-modal__title{color:#f9fafb}.dark .wy-img-modal__close{color:#9ca3af}.dark .wy-img-modal__close:hover{background:#374151;color:#f9fafb}.dark .wy-img-tab{background:#111827;color:#9ca3af}.dark .wy-img-tab.is-active{background:#312e81;color:#a5b4fc}.dark .wy-img-dropzone{border-color:#374151}.dark .wy-img-dropzone:hover,.dark .wy-img-dropzone.is-dragover{border-color:#6366f1;background:#6366f11a}.dark .wy-img-dropzone__text,.dark .wy-img-url-label,.dark .wy-img-opt-label{color:#f9fafb}.dark .wy-img-url-input,.dark .wy-img-alt-input,.dark .wy-img-width-input,.dark .wy-img-align-select{background:#111827;border-color:#374151;color:#f9fafb}.dark .wy-img-modal__progress{background:#111827}.dark .wy-img-modal__cancel{background:#374151;color:#f9fafb}.dark .wy-img-modal__cancel:hover{background:#4b5563}.wy-img-modal{position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center}.wy-img-modal__backdrop{position:absolute;inset:0;background:#0000008c;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.wy-img-modal__box{position:relative;z-index:1;width:min(540px,calc(100vw - 32px));max-height:calc(100vh - 64px);overflow-y:auto;background:#fff;border-radius:16px;box-shadow:0 24px 64px #00000040;padding:24px;display:flex;flex-direction:column;gap:16px}.wy-img-modal__head{display:flex;align-items:center;justify-content:space-between}.wy-img-modal__title{font-size:1rem;font-weight:700;color:#111827}.wy-img-modal__close{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border:none;background:#f3f4f6;border-radius:8px;cursor:pointer;color:#6b7280;transition:background .15s,color .15s}.wy-img-modal__close:hover{background:#e5e7eb;color:#111827}.wy-img-modal__tabs{display:flex;gap:4px;border-bottom:1px solid #e5e7eb;padding-bottom:0}.wy-img-tab{padding:8px 16px;border:none;background:none;font-size:.875rem;font-weight:500;color:#6b7280;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .15s,border-color .15s;border-radius:6px 6px 0 0}.wy-img-tab:hover{color:#374151}.wy-img-tab.is-active{color:#6366f1;border-bottom-color:#6366f1}.wy-img-dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:32px 24px;border:2px dashed #d1d5db;border-radius:12px;cursor:pointer;transition:border-color .15s,background .15s;text-align:center}.wy-img-dropzone input[type=file]{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.wy-img-dropzone:hover,.wy-img-dropzone.is-dragover{border-color:#6366f1;background:#eef2ff}.wy-img-dropzone__icon{color:#9ca3af}.wy-img-dropzone.is-dragover .wy-img-dropzone__icon{color:#6366f1}.wy-img-dropzone__text{font-size:.875rem;color:#6b7280;margin:0}.wy-img-dropzone__text span{color:#6366f1;font-weight:600;text-decoration:underline}.wy-img-dropzone__hint{font-size:.75rem;color:#9ca3af;margin:0}.wy-img-preview{position:relative;display:flex;justify-content:center}.wy-img-preview__img{max-height:200px;max-width:100%;border-radius:10px;object-fit:contain;border:1px solid #e5e7eb}.wy-img-preview__remove{position:absolute;top:-8px;right:-8px;width:24px;height:24px;border-radius:50%;border:none;background:#ef4444;color:#fff;font-size:14px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px #0003}.wy-img-preview__remove:hover{background:#dc2626}.wy-img-modal__progress{height:6px;background:#e5e7eb;border-radius:999px;overflow:hidden}.wy-img-modal__progress-bar{height:100%;background:linear-gradient(90deg,#6366f1,#8b5cf6);border-radius:999px;width:0%;transition:width .2s ease}.wy-img-url-label{display:flex;flex-direction:column;gap:6px;font-size:.8125rem;font-weight:500;color:#374151}.wy-img-url-input,.wy-img-alt-input{padding:9px 12px;border:1.5px solid #d1d5db;border-radius:8px;font-size:.875rem;color:#111827;outline:none;transition:border-color .15s,box-shadow .15s;width:100%;box-sizing:border-box}.wy-img-url-input:focus,.wy-img-alt-input:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f11f}.wy-img-modal__options{display:grid;grid-template-columns:1fr 1fr;gap:12px}.wy-img-opt-label{display:flex;flex-direction:column;gap:6px;font-size:.8125rem;font-weight:500;color:#374151}.wy-img-width-input,.wy-img-align-select{padding:9px 12px;border:1.5px solid #d1d5db;border-radius:8px;font-size:.875rem;color:#111827;outline:none;background:#fff;transition:border-color .15s;width:100%;box-sizing:border-box}.wy-img-width-input:focus,.wy-img-align-select:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f11f}.wy-img-modal__error{padding:10px 14px;background:#fef2f2;border:1px solid #fecaca;border-radius:8px;color:#dc2626;font-size:.8125rem;margin:0}.wy-img-modal__footer{display:flex;justify-content:flex-end;gap:10px;padding-top:4px}.wy-img-modal__cancel{padding:9px 20px;border:1.5px solid #d1d5db;border-radius:10px;background:#fff;color:#374151;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s}.wy-img-modal__cancel:hover{background:#f9fafb;border-color:#9ca3af}.wy-img-modal__insert{padding:9px 20px;border:none;border-radius:10px;background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;font-size:.875rem;font-weight:600;cursor:pointer;transition:opacity .15s,box-shadow .15s;box-shadow:0 2px 8px #6366f159}.wy-img-modal__insert:hover:not(:disabled){opacity:.9;box-shadow:0 4px 12px #6366f166}.wy-img-modal__insert:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}.wy-img-toolbar{position:fixed;z-index:9998;display:none;align-items:center;gap:2px;padding:4px 6px;background:#1f2937;border-radius:8px;box-shadow:0 4px 16px #0000004d}.wy-img-toolbar button{width:26px;height:26px;border:none;background:transparent;color:#d1d5db;border-radius:5px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .12s,color .12s}.wy-img-toolbar button:hover{background:#ffffff1f;color:#fff}.wy-img-toolbar button.is-active{background:#6366f1;color:#fff}.wy-img-toolbar button[data-action=delete]:hover{background:#ef4444;color:#fff}.wy-img-toolbar__sep{width:1px;height:16px;background:#ffffff26;margin:0 2px}.wy-img-resize-box{position:fixed;z-index:9997;pointer-events:none;box-sizing:border-box;outline:2px solid #6366f1;outline-offset:1px}.wy-img-resize-handle{position:absolute;width:10px;height:10px;background:#fff;border:2px solid #6366f1;border-radius:2px;pointer-events:all;cursor:nwse-resize}.wy-img-resize-handle[data-dir=nw]{top:-5px;left:-5px;cursor:nwse-resize}.wy-img-resize-handle[data-dir=ne]{top:-5px;right:-5px;cursor:nesw-resize}.wy-img-resize-handle[data-dir=sw]{bottom:-5px;left:-5px;cursor:nesw-resize}.wy-img-resize-handle[data-dir=se]{bottom:-5px;right:-5px;cursor:nwse-resize}.wysiwyg-area img{max-width:100%;height:auto;cursor:pointer;border-radius:4px;transition:outline .1s}.wysiwyg-area img.wy-img-selected{outline:2px solid #6366f1;outline-offset:2px}.wysiwyg-area img.wy-img-align-center{display:block;margin:0 auto}.wysiwyg-area img.wy-img-align-left{float:left;margin:0 1em .5em 0}.wysiwyg-area img.wy-img-align-right{float:right;margin:0 0 .5em 1em}.wysiwyg-area img.wy-img-align-block-left{display:block;margin:0 auto 0 0}.wysiwyg-area img.wy-img-align-block-right{display:block;margin:0 0 0 auto}.wy-img-uploading{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;background:#eef2ff;color:#6366f1;border-radius:6px;font-size:.8125rem;font-weight:500;border:1px dashed #a5b4fc}.wy-img-uploading--error{background:#fef2f2;color:#ef4444;border-color:#fca5a5}@media(prefers-color-scheme:dark){.wy-img-modal__box{background:#1f2937;color:#f9fafb}.wy-img-modal__title{color:#f9fafb}.wy-img-modal__close{background:#374151;color:#9ca3af}.wy-img-modal__close:hover{background:#4b5563;color:#f9fafb}.wy-img-tab{color:#9ca3af}.wy-img-tab:hover{color:#e5e7eb}.wy-img-modal__tabs{border-color:#374151}.wy-img-dropzone{border-color:#4b5563}.wy-img-dropzone:hover,.wy-img-dropzone.is-dragover{background:#6366f11a}.wy-img-url-label,.wy-img-opt-label{color:#d1d5db}.wy-img-url-input,.wy-img-alt-input,.wy-img-width-input,.wy-img-align-select{background:#111827;border-color:#4b5563;color:#f9fafb}.wy-img-modal__cancel{background:#374151;border-color:#4b5563;color:#d1d5db}.wy-img-modal__cancel:hover{background:#4b5563}}
|
|
1
|
+
.wysiwyg-editor{--wy-border: #e5e7eb;--wy-border-focus: #6366f1;--wy-bg: #ffffff;--wy-bg-toolbar: #f9fafb;--wy-text: #111827;--wy-muted: #9ca3af;--wy-btn-hover: #f3f4f6;--wy-btn-active: #e0e7ff;--wy-btn-active-fg:#4338ca;--wy-radius: .75rem;--wy-sep: #e5e7eb;display:flex;flex-direction:column;border:1.5px solid var(--wy-border);border-radius:var(--wy-radius);background:var(--wy-bg);overflow:hidden;transition:border-color .15s ease,box-shadow .15s ease;font-family:inherit}.wysiwyg-editor:focus-within{border-color:var(--wy-border-focus);box-shadow:0 0 0 3px #6366f11f}.wysiwyg-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:2px;padding:6px 8px;background:var(--wy-bg-toolbar);border-bottom:1px solid var(--wy-border)}.wysiwyg-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--wy-text);cursor:pointer;transition:background .1s ease,color .1s ease;flex-shrink:0}.wysiwyg-btn:hover{background:var(--wy-btn-hover)}.wysiwyg-btn.is-active{background:var(--wy-btn-active);color:var(--wy-btn-active-fg)}.wysiwyg-btn span{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%}.wysiwyg-separator{width:1px;height:20px;background:var(--wy-sep);margin:0 4px;flex-shrink:0}.wysiwyg-area{flex:1;padding:14px 16px;outline:none;color:var(--wy-text);line-height:1.7;word-break:break-word;overflow-y:auto}.wysiwyg-area:empty:before{content:attr(data-placeholder);color:var(--wy-muted);pointer-events:none}.wysiwyg-area h1,.wysiwyg-area h2,.wysiwyg-area h3{font-weight:700;line-height:1.3;margin-top:1em;margin-bottom:.4em;color:var(--wy-text)}.wysiwyg-area h1{font-size:1.75em}.wysiwyg-area h2{font-size:1.35em}.wysiwyg-area h3{font-size:1.1em}.wysiwyg-area h1:first-child,.wysiwyg-area h2:first-child,.wysiwyg-area h3:first-child{margin-top:0}.wysiwyg-area p{margin:0 0 .75em}.wysiwyg-area p:last-child{margin-bottom:0}.wysiwyg-area strong{font-weight:700}.wysiwyg-area em{font-style:italic}.wysiwyg-area u{text-decoration:underline}.wysiwyg-area s{text-decoration:line-through}.wysiwyg-area ul,.wysiwyg-area ol{padding-left:1.5em;margin:0 0 .75em}.wysiwyg-area ul{list-style-type:disc}.wysiwyg-area ol{list-style-type:decimal}.wysiwyg-area li{margin-bottom:.25em}.wysiwyg-area blockquote{border-left:3px solid var(--wy-border-focus);margin:.75em 0;padding:.5em 1em;background:#6366f10d;border-radius:0 6px 6px 0;color:#4b5563}.wysiwyg-area a{color:var(--wy-btn-active-fg);text-decoration:underline}.wysiwyg-area a:hover{opacity:.8}.wysiwyg-source-area{flex:1;padding:14px 16px;outline:none;color:#a5b4fc;background:#1e1b4b;font-family:Fira Code,Cascadia Code,Consolas,Monaco,monospace;font-size:.8125rem;line-height:1.7;resize:none;border:none;width:100%;box-sizing:border-box;min-height:200px}.wysiwyg-source-area:focus{outline:none}.dark .wysiwyg-source-area{background:#0f0c29}@media(prefers-color-scheme:dark){.wysiwyg-editor{--wy-border: #374151;--wy-bg: #1f2937;--wy-bg-toolbar: #111827;--wy-text: #f9fafb;--wy-muted: #6b7280;--wy-btn-hover: #374151;--wy-btn-active: #312e81;--wy-btn-active-fg:#a5b4fc;--wy-sep: #374151}.wysiwyg-area blockquote{background:#6366f11a;color:#d1d5db}}.dark .wysiwyg-editor{--wy-border: #374151;--wy-bg: #1f2937;--wy-bg-toolbar: #111827;--wy-text: #f9fafb;--wy-muted: #6b7280;--wy-btn-hover: #374151;--wy-btn-active: #312e81;--wy-btn-active-fg:#a5b4fc;--wy-sep: #374151}.dark .wysiwyg-area blockquote{background:#6366f11a;color:#d1d5db}.wysiwyg-area img{max-width:100%;height:auto;border-radius:4px;cursor:pointer;vertical-align:bottom;transition:outline-color .1s ease}.wysiwyg-area img:hover{outline:2px solid rgb(99 102 241 / .35);outline-offset:2px}.wysiwyg-area img.wy-img-align-center{display:block;float:none;margin:.5em auto}.wysiwyg-area img.wy-img-align-left{float:left;margin:.25em 1.25em .5em 0}.wysiwyg-area img.wy-img-align-right{float:right;margin:.25em 0 .5em 1.25em}.wysiwyg-area img.wy-img-align-block-left{display:block;float:none;margin:.5em auto .5em 0}.wysiwyg-area img.wy-img-align-block-right{display:block;float:none;margin:.5em 0 .5em auto}img.wy-img-align-center{display:block;float:none;margin:.5em auto}img.wy-img-align-left{float:left;margin:.25em 1.25em .5em 0}img.wy-img-align-right{float:right;margin:.25em 0 .5em 1.25em}img.wy-img-align-block-left{display:block;float:none;margin:.5em auto .5em 0}img.wy-img-align-block-right{display:block;float:none;margin:.5em 0 .5em auto}.wy-img-uploading{display:inline-block;padding:4px 10px;border-radius:6px;background:#6366f11a;color:#4338ca;font-size:.8125em;font-style:italic}.wy-img-uploading--error{background:#f43f5e1a;color:#e11d48}.wy-img-toolbar{position:fixed;z-index:10000;display:none;align-items:center;gap:2px;padding:4px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 8px 24px #00000026}.wy-img-toolbar button{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:none;border-radius:6px;background:transparent;color:#111827;cursor:pointer}.wy-img-toolbar button:hover{background:#f3f4f6}.wy-img-toolbar button.is-active{background:#e0e7ff;color:#4338ca}.wy-img-toolbar__sep{width:1px;height:18px;background:#e5e7eb;margin:0 4px;flex-shrink:0}.wy-img-resize-box{position:fixed;z-index:9999;display:none;box-sizing:border-box;border:1.5px dashed #6366f1;pointer-events:none}.wy-img-resize-handle{position:absolute;width:10px;height:10px;background:#fff;border:2px solid #6366f1;border-radius:2px;pointer-events:all}.wy-img-resize-handle[data-dir=nw]{top:-6px;left:-6px;cursor:nwse-resize}.wy-img-resize-handle[data-dir=ne]{top:-6px;right:-6px;cursor:nesw-resize}.wy-img-resize-handle[data-dir=sw]{bottom:-6px;left:-6px;cursor:nesw-resize}.wy-img-resize-handle[data-dir=se]{bottom:-6px;right:-6px;cursor:nwse-resize}.wy-img-modal{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center}.wy-img-modal__backdrop{position:absolute;inset:0;background:#0f0f1480;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.wy-img-modal__box{position:relative;width:100%;max-width:440px;max-height:90vh;overflow-y:auto;margin:16px;background:#fff;border-radius:16px;box-shadow:0 20px 60px #0000004d}.wy-img-modal__head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e5e7eb}.wy-img-modal__title{font-weight:600;font-size:.9375rem;color:#111827}.wy-img-modal__close{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:8px;background:transparent;color:#9ca3af;cursor:pointer}.wy-img-modal__close:hover{background:#f3f4f6;color:#111827}.wy-img-modal__tabs{display:flex;gap:6px;padding:14px 20px 0}.wy-img-tab{flex:1;padding:8px 12px;border:none;border-radius:8px;background:#f9fafb;color:#6b7280;font-size:.8125rem;font-weight:500;cursor:pointer;transition:background .1s ease,color .1s ease}.wy-img-tab.is-active{background:#e0e7ff;color:#4338ca}.wy-img-modal__panel{padding:16px 20px 0}.wy-img-dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:28px 16px;border:2px dashed #e5e7eb;border-radius:12px;cursor:pointer;text-align:center;transition:border-color .15s ease,background .15s ease}.wy-img-dropzone:hover,.wy-img-dropzone.is-dragover{border-color:#6366f1;background:#6366f10d}.wy-img-dropzone__text{margin:0;font-size:.8125rem;color:#111827}.wy-img-dropzone__text span{color:#6366f1;font-weight:600}.wy-img-dropzone__hint{margin:0;font-size:.6875rem;color:#9ca3af}.wy-img-file{position:absolute;width:1px;height:1px;overflow:hidden;opacity:0}.wy-img-preview{position:relative;display:block;width:max-content;max-width:100%;margin:12px auto 0}.wy-img-preview__img{display:block;max-height:180px;max-width:100%;border-radius:8px}.wy-img-preview__remove{position:absolute;top:-8px;right:-8px;width:22px;height:22px;border:none;border-radius:999px;background:#111827;color:#fff;font-size:14px;line-height:1;cursor:pointer}.wy-img-modal__progress{margin-top:12px;height:6px;border-radius:999px;background:#f3f4f6;overflow:hidden}.wy-img-modal__progress-bar{height:100%;width:0%;background:linear-gradient(90deg,#6366f1,#8b5cf6);transition:width .15s ease}.wy-img-url-label{display:block;font-size:.8125rem;font-weight:500;color:#111827}.wy-img-url-input,.wy-img-alt-input,.wy-img-width-input,.wy-img-align-select{display:block;width:100%;margin-top:6px;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;background:#fff;color:#111827;font-size:.8125rem;font-family:inherit;box-sizing:border-box}.wy-img-url-input:focus,.wy-img-alt-input:focus,.wy-img-width-input:focus,.wy-img-align-select:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px #6366f11f}.wy-img-url-preview img{max-height:120px;max-width:100%;border-radius:8px}.wy-img-modal__options{display:flex;gap:12px;padding:4px 20px 0}.wy-img-opt-label{flex:1;font-size:.8125rem;font-weight:500;color:#111827}.wy-img-modal__error{margin:12px 20px 0;padding:8px 12px;border-radius:8px;background:#f43f5e1a;color:#e11d48;font-size:.75rem}.wy-img-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px 20px}.wy-img-modal__cancel,.wy-img-modal__insert{padding:8px 18px;border:none;border-radius:8px;font-size:.8125rem;font-weight:600;cursor:pointer}.wy-img-modal__cancel{background:#f3f4f6;color:#111827}.wy-img-modal__cancel:hover{background:#e5e7eb}.wy-img-modal__insert{background:linear-gradient(90deg,#4f46e5,#7c3aed);color:#fff}.wy-img-modal__insert:disabled{opacity:.4;cursor:not-allowed}@media(prefers-color-scheme:dark){.wy-img-toolbar{background:#1f2937;border-color:#374151}.wy-img-toolbar button{color:#f9fafb}.wy-img-toolbar button:hover{background:#374151}.wy-img-toolbar button.is-active{background:#312e81;color:#a5b4fc}.wy-img-toolbar__sep{background:#374151}.wy-img-modal__box{background:#1f2937}.wy-img-modal__head{border-color:#374151}.wy-img-modal__title{color:#f9fafb}.wy-img-modal__close{color:#9ca3af}.wy-img-modal__close:hover{background:#374151;color:#f9fafb}.wy-img-tab{background:#111827;color:#9ca3af}.wy-img-tab.is-active{background:#312e81;color:#a5b4fc}.wy-img-dropzone{border-color:#374151}.wy-img-dropzone:hover,.wy-img-dropzone.is-dragover{border-color:#6366f1;background:#6366f11a}.wy-img-dropzone__text,.wy-img-url-label,.wy-img-opt-label{color:#f9fafb}.wy-img-url-input,.wy-img-alt-input,.wy-img-width-input,.wy-img-align-select{background:#111827;border-color:#374151;color:#f9fafb}.wy-img-modal__progress{background:#111827}.wy-img-modal__cancel{background:#374151;color:#f9fafb}.wy-img-modal__cancel:hover{background:#4b5563}}.dark .wy-img-toolbar{background:#1f2937;border-color:#374151}.dark .wy-img-toolbar button{color:#f9fafb}.dark .wy-img-toolbar button:hover{background:#374151}.dark .wy-img-toolbar button.is-active{background:#312e81;color:#a5b4fc}.dark .wy-img-toolbar__sep{background:#374151}.dark .wy-img-modal__box{background:#1f2937}.dark .wy-img-modal__head{border-color:#374151}.dark .wy-img-modal__title{color:#f9fafb}.dark .wy-img-modal__close{color:#9ca3af}.dark .wy-img-modal__close:hover{background:#374151;color:#f9fafb}.dark .wy-img-tab{background:#111827;color:#9ca3af}.dark .wy-img-tab.is-active{background:#312e81;color:#a5b4fc}.dark .wy-img-dropzone{border-color:#374151}.dark .wy-img-dropzone:hover,.dark .wy-img-dropzone.is-dragover{border-color:#6366f1;background:#6366f11a}.dark .wy-img-dropzone__text,.dark .wy-img-url-label,.dark .wy-img-opt-label{color:#f9fafb}.dark .wy-img-url-input,.dark .wy-img-alt-input,.dark .wy-img-width-input,.dark .wy-img-align-select{background:#111827;border-color:#374151;color:#f9fafb}.dark .wy-img-modal__progress{background:#111827}.dark .wy-img-modal__cancel{background:#374151;color:#f9fafb}.dark .wy-img-modal__cancel:hover{background:#4b5563}.wy-img-modal{position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center}.wy-img-modal__backdrop{position:absolute;inset:0;background:#0000008c;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.wy-img-modal__box{position:relative;z-index:1;width:min(540px,calc(100vw - 32px));max-height:calc(100vh - 64px);overflow-y:auto;background:#fff;border-radius:16px;box-shadow:0 24px 64px #00000040;padding:24px;display:flex;flex-direction:column;gap:16px}.wy-img-modal__head{display:flex;align-items:center;justify-content:space-between}.wy-img-modal__title{font-size:1rem;font-weight:700;color:#111827}.wy-img-modal__close{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border:none;background:#f3f4f6;border-radius:8px;cursor:pointer;color:#6b7280;transition:background .15s,color .15s}.wy-img-modal__close:hover{background:#e5e7eb;color:#111827}.wy-img-modal__tabs{display:flex;gap:4px;border-bottom:1px solid #e5e7eb;padding-bottom:0}.wy-img-tab{padding:8px 16px;border:none;background:none;font-size:.875rem;font-weight:500;color:#6b7280;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .15s,border-color .15s;border-radius:6px 6px 0 0}.wy-img-tab:hover{color:#374151}.wy-img-tab.is-active{color:#6366f1;border-bottom-color:#6366f1}.wy-img-dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:32px 24px;border:2px dashed #d1d5db;border-radius:12px;cursor:pointer;transition:border-color .15s,background .15s;text-align:center}.wy-img-dropzone input[type=file]{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.wy-img-dropzone:hover,.wy-img-dropzone.is-dragover{border-color:#6366f1;background:#eef2ff}.wy-img-dropzone__icon{color:#9ca3af}.wy-img-dropzone.is-dragover .wy-img-dropzone__icon{color:#6366f1}.wy-img-dropzone__text{font-size:.875rem;color:#6b7280;margin:0}.wy-img-dropzone__text span{color:#6366f1;font-weight:600;text-decoration:underline}.wy-img-dropzone__hint{font-size:.75rem;color:#9ca3af;margin:0}.wy-img-preview{position:relative;display:flex;justify-content:center}.wy-img-preview__img{max-height:200px;max-width:100%;border-radius:10px;object-fit:contain;border:1px solid #e5e7eb}.wy-img-preview__remove{position:absolute;top:-8px;right:-8px;width:24px;height:24px;border-radius:50%;border:none;background:#ef4444;color:#fff;font-size:14px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px #0003}.wy-img-preview__remove:hover{background:#dc2626}.wy-img-modal__progress{height:6px;background:#e5e7eb;border-radius:999px;overflow:hidden}.wy-img-modal__progress-bar{height:100%;background:linear-gradient(90deg,#6366f1,#8b5cf6);border-radius:999px;width:0%;transition:width .2s ease}.wy-img-url-label{display:flex;flex-direction:column;gap:6px;font-size:.8125rem;font-weight:500;color:#374151}.wy-img-url-input,.wy-img-alt-input{padding:9px 12px;border:1.5px solid #d1d5db;border-radius:8px;font-size:.875rem;color:#111827;outline:none;transition:border-color .15s,box-shadow .15s;width:100%;box-sizing:border-box}.wy-img-url-input:focus,.wy-img-alt-input:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f11f}.wy-img-modal__options{display:grid;grid-template-columns:1fr 1fr;gap:12px}.wy-img-opt-label{display:flex;flex-direction:column;gap:6px;font-size:.8125rem;font-weight:500;color:#374151}.wy-img-width-input,.wy-img-align-select{padding:9px 12px;border:1.5px solid #d1d5db;border-radius:8px;font-size:.875rem;color:#111827;outline:none;background:#fff;transition:border-color .15s;width:100%;box-sizing:border-box}.wy-img-width-input:focus,.wy-img-align-select:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f11f}.wy-img-modal__error{padding:10px 14px;background:#fef2f2;border:1px solid #fecaca;border-radius:8px;color:#dc2626;font-size:.8125rem;margin:0}.wy-img-modal__footer{display:flex;justify-content:flex-end;gap:10px;padding-top:4px}.wy-img-modal__cancel{padding:9px 20px;border:1.5px solid #d1d5db;border-radius:10px;background:#fff;color:#374151;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s}.wy-img-modal__cancel:hover{background:#f9fafb;border-color:#9ca3af}.wy-img-modal__insert{padding:9px 20px;border:none;border-radius:10px;background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;font-size:.875rem;font-weight:600;cursor:pointer;transition:opacity .15s,box-shadow .15s;box-shadow:0 2px 8px #6366f159}.wy-img-modal__insert:hover:not(:disabled){opacity:.9;box-shadow:0 4px 12px #6366f166}.wy-img-modal__insert:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}.wy-img-toolbar{position:fixed;z-index:9998;display:none;align-items:center;gap:2px;padding:4px 6px;background:#1f2937;border-radius:8px;box-shadow:0 4px 16px #0000004d}.wy-img-toolbar button{width:26px;height:26px;border:none;background:transparent;color:#d1d5db;border-radius:5px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .12s,color .12s}.wy-img-toolbar button:hover{background:#ffffff1f;color:#fff}.wy-img-toolbar button.is-active{background:#6366f1;color:#fff}.wy-img-toolbar button[data-action=delete]:hover{background:#ef4444;color:#fff}.wy-img-toolbar__sep{width:1px;height:16px;background:#ffffff26;margin:0 2px}.wy-img-resize-box{position:fixed;z-index:9997;pointer-events:none;box-sizing:border-box;outline:2px solid #6366f1;outline-offset:1px}.wy-img-resize-handle{position:absolute;width:10px;height:10px;background:#fff;border:2px solid #6366f1;border-radius:2px;pointer-events:all;cursor:nwse-resize}.wy-img-resize-handle[data-dir=nw]{top:-5px;left:-5px;cursor:nwse-resize}.wy-img-resize-handle[data-dir=ne]{top:-5px;right:-5px;cursor:nesw-resize}.wy-img-resize-handle[data-dir=sw]{bottom:-5px;left:-5px;cursor:nesw-resize}.wy-img-resize-handle[data-dir=se]{bottom:-5px;right:-5px;cursor:nwse-resize}.wysiwyg-area img{max-width:100%;height:auto;cursor:pointer;border-radius:4px;transition:outline .1s}.wysiwyg-area img.wy-img-selected{outline:2px solid #6366f1;outline-offset:2px}.wysiwyg-area img.wy-img-align-center{display:block;margin:0 auto}.wysiwyg-area img.wy-img-align-left{float:left;margin:0 1em .5em 0}.wysiwyg-area img.wy-img-align-right{float:right;margin:0 0 .5em 1em}.wysiwyg-area img.wy-img-align-block-left{display:block;margin:0 auto 0 0}.wysiwyg-area img.wy-img-align-block-right{display:block;margin:0 0 0 auto}.wy-img-uploading{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;background:#eef2ff;color:#6366f1;border-radius:6px;font-size:.8125rem;font-weight:500;border:1px dashed #a5b4fc}.wy-img-uploading--error{background:#fef2f2;color:#ef4444;border-color:#fca5a5}@media(prefers-color-scheme:dark){.wy-img-modal__box{background:#1f2937;color:#f9fafb}.wy-img-modal__title{color:#f9fafb}.wy-img-modal__close{background:#374151;color:#9ca3af}.wy-img-modal__close:hover{background:#4b5563;color:#f9fafb}.wy-img-tab{color:#9ca3af}.wy-img-tab:hover{color:#e5e7eb}.wy-img-modal__tabs{border-color:#374151}.wy-img-dropzone{border-color:#4b5563}.wy-img-dropzone:hover,.wy-img-dropzone.is-dragover{background:#6366f11a}.wy-img-url-label,.wy-img-opt-label{color:#d1d5db}.wy-img-url-input,.wy-img-alt-input,.wy-img-width-input,.wy-img-align-select{background:#111827;border-color:#4b5563;color:#f9fafb}.wy-img-modal__cancel{background:#374151;border-color:#4b5563;color:#d1d5db}.wy-img-modal__cancel:hover{background:#4b5563}}
|
package/wysiwyg.min.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var WysiwygEditor=(function(w
|
|
1
|
+
var WysiwygEditor=(function(w){"use strict";const k={placeholder:"Tulis sesuatu...",minHeight:200,plugins:[],onChange:null,syncInput:null,uploadUrl:"",uploadFn:null};class g{static#p=new Map;static registerPlugin(e){if(!e.name)throw new Error("[wysiwyg] Plugin harus punya nama.");g.#p.set(e.name,e)}static getPlugins(){return Array.from(g.#p.values())}#l;#e;#n;#u=!1;#s;#k=null;#t;#h;#a=null;#o=null;#i=null;#v=null;#g=null;#c=null;constructor(e,t={}){if(this.#l=typeof e=="string"?document.querySelector(e):e,!this.#l)throw new Error(`[wysiwyg] Element tidak ditemukan: ${e}`);this.#t={...k,...t},this.#h=new AbortController,this.#C(),this.#P(),this.#f()}#C(){this.#l.classList.add("wysiwyg-editor"),this.#l.innerHTML="",this.#s=document.createElement("div"),this.#s.className="wysiwyg-toolbar",this.#s.setAttribute("role","toolbar"),this.#s.setAttribute("aria-label","Toolbar editor"),this.#M(),this.#l.appendChild(this.#s),this.#e=document.createElement("div"),this.#e.className="wysiwyg-area",this.#e.contentEditable="true",this.#e.setAttribute("role","textbox"),this.#e.setAttribute("aria-multiline","true"),this.#e.setAttribute("aria-label","Area editor"),this.#e.setAttribute("spellcheck","true"),this.#e.style.minHeight=this.#t.minHeight+"px",this.#e.dataset.placeholder=this.#t.placeholder,this.#l.appendChild(this.#e),this.#n=document.createElement("textarea"),this.#n.className="wysiwyg-source-area",this.#n.setAttribute("aria-label","HTML source editor"),this.#n.setAttribute("spellcheck","false"),this.#n.setAttribute("autocorrect","off"),this.#n.setAttribute("autocapitalize","off"),this.#n.style.minHeight=this.#t.minHeight+"px",this.#n.style.display="none",this.#l.appendChild(this.#n),this.#t.syncInput&&(this.#k=typeof this.#t.syncInput=="string"?document.querySelector(this.#t.syncInput):this.#t.syncInput),this.#_(),this.#q(),this.#U()}#M(){const e=this.#t.plugins.length?this.#t.plugins.map(i=>typeof i=="string"?g.#p.get(i):i).filter(Boolean):g.getPlugins();let t=null;e.forEach(i=>{if(i.group&&i.group!==t){if(t!==null){const o=document.createElement("div");o.className="wysiwyg-separator",o.setAttribute("aria-hidden","true"),this.#s.appendChild(o)}t=i.group}const s=document.createElement("button");s.type="button",s.className="wysiwyg-btn",s.dataset.plugin=i.name,s.title=i.title||i.name,s.setAttribute("aria-label",i.title||i.name),s.innerHTML=i.icon,this.#s.appendChild(s)})}#_(){const e=document.createElement("div");e.className="wy-img-modal",e.style.display="none",e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),e.setAttribute("aria-label","Sisipkan gambar"),e.innerHTML=`
|
|
2
2
|
<div class="wy-img-modal__backdrop"></div>
|
|
3
3
|
<div class="wy-img-modal__box">
|
|
4
4
|
<div class="wy-img-modal__head">
|
|
@@ -91,16 +91,16 @@ var WysiwygEditor=(function(w,k){"use strict";const E={placeholder:"Tulis sesuat
|
|
|
91
91
|
<button type="button" data-action="delete" title="Hapus gambar">
|
|
92
92
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>
|
|
93
93
|
</button>
|
|
94
|
-
`,document.body.appendChild(e),this.#o=e,this.#N()}openImageDialog(){const e=window.getSelection();this.#v=e.rangeCount?e.getRangeAt(0).cloneRange():null,this.#H(),this.#a.style.display="flex"}#H(){const e=this.#a;e.querySelectorAll(".wy-img-tab").forEach(t=>t.classList.toggle("is-active",t.dataset.tab==="upload")),e.querySelectorAll(".wy-img-modal__panel").forEach(t=>{t.style.display=t.dataset.panel==="upload"?"":"none"}),e.querySelector(".wy-img-file").value="",e.querySelector(".wy-img-preview").style.display="none",e.querySelector(".wy-img-preview__img").src="",e.querySelector(".wy-img-url-input").value="",e.querySelector(".wy-img-alt-input").value="",e.querySelector(".wy-img-url-preview").style.display="none",e.querySelector(".wy-img-url-preview__img").src="",e.querySelector(".wy-img-width-input").value="",e.querySelector(".wy-img-align-select").value="",e.querySelector(".wy-img-modal__progress").style.display="none",e.querySelector(".wy-img-modal__progress-bar").style.width="0%",e.querySelector(".wy-img-modal__insert").disabled=!0,e.querySelector(".wy-img-dropzone").classList.remove("is-dragover"),this.#d(""),this.#
|
|
94
|
+
`,document.body.appendChild(e),this.#o=e,this.#N()}openImageDialog(){const e=window.getSelection();this.#v=e.rangeCount?e.getRangeAt(0).cloneRange():null,this.#H(),this.#a.style.display="flex"}#H(){const e=this.#a;e.querySelectorAll(".wy-img-tab").forEach(t=>t.classList.toggle("is-active",t.dataset.tab==="upload")),e.querySelectorAll(".wy-img-modal__panel").forEach(t=>{t.style.display=t.dataset.panel==="upload"?"":"none"}),e.querySelector(".wy-img-file").value="",e.querySelector(".wy-img-preview").style.display="none",e.querySelector(".wy-img-preview__img").src="",e.querySelector(".wy-img-url-input").value="",e.querySelector(".wy-img-alt-input").value="",e.querySelector(".wy-img-url-preview").style.display="none",e.querySelector(".wy-img-url-preview__img").src="",e.querySelector(".wy-img-width-input").value="",e.querySelector(".wy-img-align-select").value="",e.querySelector(".wy-img-modal__progress").style.display="none",e.querySelector(".wy-img-modal__progress-bar").style.width="0%",e.querySelector(".wy-img-modal__insert").disabled=!0,e.querySelector(".wy-img-dropzone").classList.remove("is-dragover"),this.#d(""),this.#g=null}#b(){this.#a.style.display="none",this.#g=null,this.#v=null}#d(e){const t=this.#a.querySelector(".wy-img-modal__error");t.textContent=e||"",t.style.display=e?"":"none"}#R(){const e=this.#a,t={signal:this.#h.signal};e.querySelectorAll(".wy-img-modal__close, .wy-img-modal__cancel, .wy-img-modal__backdrop").forEach(a=>a.addEventListener("click",()=>this.#b(),t)),e.querySelector(".wy-img-modal__box").addEventListener("click",a=>a.stopPropagation(),t),e.querySelectorAll(".wy-img-tab").forEach(a=>{a.addEventListener("click",()=>{e.querySelectorAll(".wy-img-tab").forEach(d=>d.classList.toggle("is-active",d===a)),e.querySelectorAll(".wy-img-modal__panel").forEach(d=>{d.style.display=d.dataset.panel===a.dataset.tab?"":"none"}),this.#d(""),this.#x()},t)});const i=e.querySelector(".wy-img-file"),s=e.querySelector(".wy-img-dropzone");i.addEventListener("change",()=>{const a=i.files?.[0];a&&this.#E(a)},t),["dragover","dragenter"].forEach(a=>{s.addEventListener(a,d=>{d.preventDefault(),s.classList.add("is-dragover")},t)}),["dragleave","dragend"].forEach(a=>{s.addEventListener(a,d=>{d.preventDefault(),s.classList.remove("is-dragover")},t)}),s.addEventListener("drop",a=>{a.preventDefault(),s.classList.remove("is-dragover");const d=a.dataTransfer.files?.[0];d&&this.#E(d)},t),e.querySelector(".wy-img-preview__remove").addEventListener("click",a=>{a.preventDefault(),this.#g=null,i.value="",e.querySelector(".wy-img-preview").style.display="none",this.#x()},t);const o=e.querySelector(".wy-img-url-input"),r=e.querySelector(".wy-img-url-preview"),l=e.querySelector(".wy-img-url-preview__img");let c;o.addEventListener("input",()=>{this.#d(""),this.#x(),clearTimeout(c);const a=o.value.trim();if(!a){r.style.display="none";return}c=setTimeout(()=>{l.onload=()=>{r.style.display=""},l.onerror=()=>{r.style.display="none"},l.src=a},300)},t),e.querySelector(".wy-img-modal__insert").addEventListener("click",()=>this.#I(),t),e.addEventListener("keydown",a=>{a.key==="Escape"&&this.#b()},t)}#x(){const e=this.#a,t=e.querySelector(".wy-img-tab.is-active")?.dataset.tab,i=e.querySelector(".wy-img-modal__insert");t==="upload"?i.disabled=!this.#g:i.disabled=e.querySelector(".wy-img-url-input").value.trim()===""}#E(e){if(this.#d(""),!e.type.startsWith("image/")){this.#d("File harus berupa gambar.");return}if(e.size>5242880){this.#d("Ukuran gambar maksimal 5MB.");return}this.#g=e;const t=this.#a,i=t.querySelector(".wy-img-preview"),s=t.querySelector(".wy-img-preview__img"),o=new FileReader;o.onload=()=>{s.src=o.result,i.style.display=""},o.readAsDataURL(e),this.#x()}async#I(){const e=this.#a,t=e.querySelector(".wy-img-tab.is-active")?.dataset.tab,i=e.querySelector(".wy-img-alt-input").value.trim(),s=e.querySelector(".wy-img-width-input").value.trim(),o=e.querySelector(".wy-img-align-select").value,r=e.querySelector(".wy-img-modal__insert");if(this.#d(""),t==="upload"){if(!this.#g)return;const l=e.querySelector(".wy-img-modal__progress"),c=e.querySelector(".wy-img-modal__progress-bar");r.disabled=!0,l.style.display="",c.style.width="0%";try{const a=await this.#L(this.#g,d=>{c.style.width=d+"%"});this.#A({src:a,alt:i,width:s,align:o}),this.#b()}catch(a){const d=a?.response?.data?.errors?.image?.[0]||a?.response?.data?.message;this.#d(d||"Upload gambar gagal. Silakan coba lagi."),r.disabled=!1}finally{l.style.display="none"}}else{const l=e.querySelector(".wy-img-url-input").value.trim();if(!l)return;this.#A({src:l,alt:i,width:s,align:o}),this.#b()}}#A(e){this.#e.focus();const t=window.getSelection();t.removeAllRanges(),this.#v&&t.addRange(this.#v);const i=document.createElement("img");if(i.src=e.src,i.alt=e.alt||"",this.#z(i,e),t.rangeCount){const s=t.getRangeAt(0);s.deleteContents(),s.insertNode(i),s.setStartAfter(i),s.collapse(!0),t.removeAllRanges(),t.addRange(s)}else this.#e.appendChild(i);this.#r()}async#L(e,t){const i=this.#t.uploadUrl||window.location.origin+"/wysiwyg/upload-image";if(typeof this.#t.uploadFn=="function")return this.#t.uploadFn(e,i,t);const s=new FormData;return s.append("image",e),new Promise((o,r)=>{const l=new XMLHttpRequest;l.upload.addEventListener("progress",a=>{t&&a.total&&t(Math.round(a.loaded/a.total*100))}),l.addEventListener("load",()=>{if(l.status>=400){r(new Error(`Upload failed: ${l.status} ${l.statusText}`));return}try{const a=JSON.parse(l.responseText);if(!a?.ok||!a?.url)throw new Error("Invalid server response");o(a.url)}catch(a){r(a)}}),l.addEventListener("error",()=>{r(new Error("Network error during upload"))}),l.addEventListener("abort",()=>{r(new Error("Upload aborted"))});const c=document.querySelector('meta[name="csrf-token"]')?.content;c&&l.setRequestHeader("X-CSRF-TOKEN",c),l.open("POST",i,!0),l.send(s)})}async#B(e){if(!e.type.startsWith("image/"))return;if(e.size>5242880){window.alert("Ukuran gambar maksimal 5MB.");return}this.#e.focus();const t=window.getSelection(),i=t.rangeCount?t.getRangeAt(0):null,s=document.createElement("span");s.className="wy-img-uploading",s.contentEditable="false",s.textContent="Mengunggah gambarโฆ",i?(i.deleteContents(),i.insertNode(s),i.setStartAfter(s),i.collapse(!0),t.removeAllRanges(),t.addRange(i)):this.#e.appendChild(s);try{const o=await this.#L(e,l=>{s.textContent=`Mengunggah gambarโฆ ${l}%`}),r=document.createElement("img");r.src=o,r.alt="",s.replaceWith(r)}catch{s.textContent="Gagal mengunggah gambar.",s.classList.add("wy-img-uploading--error"),setTimeout(()=>{s.remove(),this.#r()},3e3)}finally{this.#r()}}#z(e,{width:t,align:i}={}){if(t){const s=String(t).trim();e.style.width=/^\d+$/.test(s)?`${s}px`:s,e.style.height="auto"}this.#S(e,i||"")}#S(e,t){e.classList.remove("wy-img-align-center","wy-img-align-left","wy-img-align-right","wy-img-align-block-left","wy-img-align-block-right"),t&&e.classList.add(`wy-img-align-${t}`)}#N(){const e={signal:this.#h.signal};this.#o.addEventListener("mousedown",t=>t.preventDefault(),e),this.#o.addEventListener("click",t=>{const i=t.target.closest("button");if(!(!i||!this.#i)){if(i.dataset.action==="delete"){const s=this.#i;this.#y(),s.remove(),this.#r();return}i.dataset.align!==void 0&&(this.#S(this.#i,i.dataset.align),this.#T(),this.#m(this.#i),this.#r())}},e)}#T(){if(!this.#i)return;const e=["center","left","right","block-left","block-right"].find(t=>this.#i.classList.contains(`wy-img-align-${t}`))||"";this.#o.querySelectorAll("[data-align]").forEach(t=>{t.classList.toggle("is-active",t.dataset.align===e)})}#D(e){this.#i=e,e.classList.add("wy-img-selected"),this.#o.style.display="flex",this.#c.style.display="block",this.#T(),this.#m(e)}#y(){this.#i&&this.#i.classList.remove("wy-img-selected"),this.#i=null,this.#o.style.display="none",this.#c.style.display="none"}#m(e){const t=e.getBoundingClientRect();this.#c.style.top=`${t.top}px`,this.#c.style.left=`${t.left}px`,this.#c.style.width=`${t.width}px`,this.#c.style.height=`${t.height}px`;const i=this.#o.getBoundingClientRect(),o=t.top>i.height+12?t.top-i.height-8:t.bottom+8,r=Math.max(8,Math.min(t.left,window.innerWidth-i.width-8));this.#o.style.top=`${o}px`,this.#o.style.left=`${r}px`}#U(){const e=document.createElement("div");e.className="wy-img-resize-box",e.style.display="none",e.innerHTML=`
|
|
95
95
|
<div class="wy-img-resize-handle" data-dir="nw"></div>
|
|
96
96
|
<div class="wy-img-resize-handle" data-dir="ne"></div>
|
|
97
97
|
<div class="wy-img-resize-handle" data-dir="sw"></div>
|
|
98
98
|
<div class="wy-img-resize-handle" data-dir="se"></div>
|
|
99
|
-
`,document.body.appendChild(e),this.#c=e;const t={signal:this.#h.signal};e.querySelectorAll(".wy-img-resize-handle").forEach(i=>{i.addEventListener("mousedown",s=>this.#F(s,i.dataset.dir),t)}),window.addEventListener("scroll",()=>{this.#i&&this.#
|
|
99
|
+
`,document.body.appendChild(e),this.#c=e;const t={signal:this.#h.signal};e.querySelectorAll(".wy-img-resize-handle").forEach(i=>{i.addEventListener("mousedown",s=>this.#F(s,i.dataset.dir),t)}),window.addEventListener("scroll",()=>{this.#i&&this.#m(this.#i)},{signal:this.#h.signal,capture:!0}),window.addEventListener("resize",()=>{this.#i&&this.#m(this.#i)},t)}#F(e,t){e.preventDefault(),e.stopPropagation();const i=this.#i;if(!i)return;const s=e.clientX,o=i.getBoundingClientRect().width,r=t==="ne"||t==="se"?1:-1,l=30,c=this.#e.getBoundingClientRect().width,a=h=>{const y=(h.clientX-s)*r,p=Math.min(c,Math.max(l,o+y));i.style.width=`${p}px`,i.style.height="auto",this.#m(i)},d=()=>{document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",d),this.#r()};document.addEventListener("mousemove",a),document.addEventListener("mouseup",d)}#P(){const e={signal:this.#h.signal};this.#s.addEventListener("click",t=>{const i=t.target.closest(".wysiwyg-btn");if(!i)return;t.preventDefault(),this.#e.focus();const s=i.dataset.plugin,o=g.#p.get(s)??this.#t.plugins.find(r=>r?.name===s);o?.action&&(o.action(this,i),this.#r())},e),this.#s.addEventListener("mousedown",t=>{t.preventDefault()},e),this.#e.addEventListener("keyup",()=>this.#w(),e),this.#e.addEventListener("mouseup",()=>this.#w(),e),this.#e.addEventListener("selectionchange",()=>this.#w(),e),this.#e.addEventListener("input",()=>this.#r(),e),this.#n.addEventListener("input",()=>{this.#f(),this.#t.onChange?.(this.getHTML())},e),this.#e.addEventListener("paste",t=>{t.preventDefault();const i=t.clipboardData.getData("text/html"),s=t.clipboardData.getData("text/plain"),o=Array.from(t.clipboardData.files||[]);if(i){const r=this.#j(i);document.execCommand("insertHTML",!1,r)}else if(o.length&&o[0].type.startsWith("image/"))this.#B(o[0]);else{const r=s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/\n/g,"<br>");document.execCommand("insertHTML",!1,r)}},e),this.#e.addEventListener("keydown",t=>{t.key==="Enter"&&this.#$(t)},e),this.#e.addEventListener("click",t=>{const i=t.target.closest("img");i?(t.preventDefault(),this.#D(i)):this.#y()},e),document.addEventListener("click",t=>{!this.#l.contains(t.target)&&!this.#o?.contains(t.target)&&!this.#a?.contains(t.target)&&!this.#c?.contains(t.target)&&this.#y()},e),this.#e.addEventListener("scroll",()=>{this.#i&&this.#m(this.#i)},e)}#$(e){const t=window.getSelection();if(!t.rangeCount)return;const i=t.getRangeAt(0).startContainer,s=i.nodeType===3?i.parentElement.closest("blockquote"):i.closest?.("blockquote");if(s&&(i.nodeType===3?i.parentElement:i).textContent.trim()===""){e.preventDefault(),s.insertAdjacentElement("afterend",Object.assign(document.createElement("p"),{innerHTML:"<br>"}));const r=s.nextElementSibling,l=document.createRange();l.setStart(r,0),l.collapse(!0),t.removeAllRanges(),t.addRange(l)}}#r(){this.#f(),this.#w(),this.#t.onChange?.(this.getHTML())}#w(){const e=this.#t.plugins.length?this.#t.plugins.map(t=>typeof t=="string"?g.#p.get(t):t).filter(Boolean):g.getPlugins();this.#s.querySelectorAll(".wysiwyg-btn").forEach(t=>{const i=e.find(s=>s?.name===t.dataset.plugin);i?.isActive&&(t.classList.toggle("is-active",i.isActive(this)),t.setAttribute("aria-pressed",i.isActive(this)))})}#f(){this.#k&&(this.#k.value=this.getHTML())}getHTML(){if(this.#u)return this.#n.value.trim();const e=this.#e.cloneNode(!0);return e.querySelectorAll("p:empty, div:empty").forEach(t=>{(t.innerHTML===""||t.innerHTML==="<br>")&&t.remove()}),e.querySelectorAll("img.wy-img-selected").forEach(t=>{t.classList.remove("wy-img-selected"),t.className.trim()||t.removeAttribute("class")}),e.innerHTML.trim()}setHTML(e){this.#y(),this.#e.innerHTML=e||"",this.#u&&(this.#n.value=e||""),this.#f()}toggleSource(){if(this.#y(),this.#u){const e=this.#n.value.trim();this.#u=!1,this.#e.innerHTML=e,this.#e.style.display="",this.#n.style.display="none",this.#e.focus(),this.#s.querySelectorAll(".wysiwyg-btn").forEach(t=>{t.disabled=!1,t.style.opacity=""})}else{const e=this.getHTML();this.#u=!0,this.#n.value=this.#O(e),this.#e.style.display="none",this.#n.style.display="",this.#n.focus(),this.#s.querySelectorAll('.wysiwyg-btn:not([data-plugin="source"])').forEach(t=>{t.disabled=!0,t.style.opacity="0.35"})}this.#f(),this.#w()}#O(e){const t=["p","div","h1","h2","h3","h4","h5","h6","ul","ol","li","blockquote","pre","table","thead","tbody","tfoot","tr","th","td","figure","figcaption","section","article","header","footer","nav","br","hr"],s=e.replace(new RegExp(`<(${t.join("|")})(\\s[^>]*)?>`,"gi"),`
|
|
100
100
|
<$1$2>`).replace(new RegExp(`</(${t.join("|")})>`,"gi"),`</$1>
|
|
101
101
|
`).split(`
|
|
102
|
-
`);let o=0;const r=" ";return s.map(c=>{const a=c.trim();if(!a)return null;const d=(a.match(/^<\/[a-z]/i)||[]).length;d&&(o=Math.max(0,o-1));const h=r.repeat(o)+a,y=(a.match(/<[a-zA-Z][^>]*[^/]>/g)||[]).length,p=(a.match(/<\/[a-zA-Z]/g)||[]).length,
|
|
103
|
-
`).trim()}isSourceMode(){return this.#u}focus(){this.#e.focus()}getArea(){return this.#e}exec(e,t=null){this.#e.focus(),document.execCommand(e,!1,t),this.#r()}wrapBlock(e){document.execCommand("formatBlock",!1,e),this.#r()}#
|
|
102
|
+
`);let o=0;const r=" ";return s.map(c=>{const a=c.trim();if(!a)return null;const d=(a.match(/^<\/[a-z]/i)||[]).length;d&&(o=Math.max(0,o-1));const h=r.repeat(o)+a,y=(a.match(/<[a-zA-Z][^>]*[^/]>/g)||[]).length,p=(a.match(/<\/[a-zA-Z]/g)||[]).length,m=(a.match(/<[a-zA-Z][^>]*\/>/g)||[]).length;return o=Math.max(0,o+y-p-m+(d?1:0)),h}).filter(c=>c!==null).join(`
|
|
103
|
+
`).trim()}isSourceMode(){return this.#u}focus(){this.#e.focus()}getArea(){return this.#e}exec(e,t=null){this.#e.focus(),document.execCommand(e,!1,t),this.#r()}wrapBlock(e){document.execCommand("formatBlock",!1,e),this.#r()}#j(e){const t=new DOMParser().parseFromString(e,"text/html");["script","style","meta","link","head","xml","o\\:p","w\\:sdt","w\\:sdtContent"].forEach(l=>{t.querySelectorAll(l).forEach(c=>c.remove())}),["span","font","div > font","ins"].forEach(l=>{t.querySelectorAll(l).forEach(c=>{c.replaceWith(...c.childNodes)})});const o={a:["href","target","rel"],img:["src","alt","width","height"],td:["colspan","rowspan"],th:["colspan","rowspan","scope"],"*":[]};return t.body.querySelectorAll("*").forEach(l=>{const c=l.tagName.toLowerCase(),a=o[c]??o["*"];if(Array.from(l.attributes).map(h=>h.name).forEach(h=>{a.includes(h)||l.removeAttribute(h)}),c==="a"){const h=l.getAttribute("href")||"";/^javascript:/i.test(h.trim())&&l.removeAttribute("href"),l.setAttribute("target","_blank"),l.setAttribute("rel","noopener noreferrer")}}),Object.entries({b:"strong",i:"em",strike:"s",del:"s"}).forEach(([l,c])=>{t.querySelectorAll(l).forEach(a=>{const d=t.createElement(c);d.append(...a.childNodes),a.replaceWith(d)})}),t.querySelectorAll("div:empty, p:empty").forEach(l=>l.remove()),t.body.innerHTML.trim()}destroy(){this.#h.abort(),this.#a?.remove(),this.#o?.remove(),this.#c?.remove(),this.#i=null,this.#l.innerHTML="",this.#l.classList.remove("wysiwyg-editor"),this.#u=!1}queryCommand(e){try{return document.queryCommandState(e)}catch{return!1}}getActiveBlock(){const e=window.getSelection();if(!e.rangeCount)return null;let t=e.getRangeAt(0).startContainer;t.nodeType===3&&(t=t.parentElement);const i=t.closest("h1,h2,h3,h4,h5,h6,p,blockquote,pre,li");return i?i.tagName.toLowerCase():null}}const E={smileys:{title:"๐ Smileys",emoji:["๐","๐","๐","๐","๐","๐
","๐","๐คฃ","๐","๐","๐","๐ค","๐","๐","๐","๐ฅฐ","๐","๐","๐","๐","๐","๐","๐","๐คช","๐","๐ค","๐ค","๐"]},hand:{title:"๐ Hand Gestures",emoji:["๐","๐ค","๐","โ","๐","๐","๐ค","๐ค","โ","๐ค","๐ซฐ","๐ค","๐ค","๐ค","๐","๐","โ","๐","๐ค","๐ค","๐","๐","๐","๐คฒ","๐ค","๐ค","๐ซฑ","๐ซฒ","๐ซถ"]},hearts:{title:"โค๏ธ Hearts & Love",emoji:["โค๏ธ","๐งก","๐","๐","๐","๐","๐ค","๐ค","๐ค","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐"]},animals:{title:"๐ถ Animals & Nature",emoji:["๐ถ","๐ฑ","๐ญ","๐น","๐ฐ","๐ฆ","๐ป","๐ผ","๐จ","๐ฏ","๐ฆ","๐ฎ","๐ท","๐ธ","๐ต","๐","๐","๐","๐","๐","๐ง","๐ฆ","๐ค","๐ฆ","๐ฆ
","๐ฆ","๐ฆ","๐บ"]},food:{title:"๐ Food & Drink",emoji:["๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐ฅญ","๐","๐ฅฅ","๐ฅ","๐
","๐","๐ฅ","๐ฅฆ","๐ฅฌ","๐ฅ","๐ถ","๐ฝ","๐ฅ","๐ ","๐ฅ","๐","๐ฅ","๐ฅจ"]},activities:{title:"โฝ Activities & Sports",emoji:["โฝ","๐","๐","โพ","๐ฅ","๐พ","๐","๐","๐ฅ","๐ณ","๐","๐ธ","๐","๐","๐ฅ","๐ฅ","๐ฃ","๐ฝ","๐ฟ","โท","๐","๐ช","๐น","๐ผ","๐ท","๐ฅ","๐ฏ","๐ช","๐ช"]},travel:{title:"โ๏ธ Travel & Places",emoji:["โ๏ธ","๐","๐ธ","๐","๐","๐","๐","๐
","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐"]},symbols:{title:"โญ Symbols & Objects",emoji:["โญ","๐","โจ","โก","๐ฅ","๐ง","โ๏ธ","๐","๐","๐","๐","๐","๐","๐","๐ฐ","๐","๐","๐ฅ","๐ธ","๐บ","๐ป","๐ท","๐น","๐ฅ","๐ผ"]}};function A(n){const e=document.createElement("div");e.style.cssText=`
|
|
104
104
|
position: fixed;
|
|
105
105
|
top: 0;
|
|
106
106
|
left: 0;
|
|
@@ -145,7 +145,7 @@ var WysiwygEditor=(function(w,k){"use strict";const E={placeholder:"Tulis sesuat
|
|
|
145
145
|
flex: 1;
|
|
146
146
|
overflow-y: auto;
|
|
147
147
|
padding: 12px;
|
|
148
|
-
`,Object.entries(
|
|
148
|
+
`,Object.entries(E).forEach(([l,c],a)=>{const d=document.createElement("button");d.innerHTML=c.emoji[0],d.style.cssText=`
|
|
149
149
|
background: ${a===0?"#e0e7ff":"#f3f4f6"};
|
|
150
150
|
border: none;
|
|
151
151
|
width: 36px;
|
|
@@ -164,7 +164,7 @@ var WysiwygEditor=(function(w,k){"use strict";const E={placeholder:"Tulis sesuat
|
|
|
164
164
|
display: grid;
|
|
165
165
|
grid-template-columns: repeat(auto-fill, 36px);
|
|
166
166
|
gap: 4px;
|
|
167
|
-
`,c.emoji.forEach(p=>{const
|
|
167
|
+
`,c.emoji.forEach(p=>{const m=document.createElement("button");m.textContent=p,m.style.cssText=`
|
|
168
168
|
width: 36px;
|
|
169
169
|
height: 36px;
|
|
170
170
|
border: 1px solid #ddd;
|
|
@@ -173,7 +173,7 @@ var WysiwygEditor=(function(w,k){"use strict";const E={placeholder:"Tulis sesuat
|
|
|
173
173
|
cursor: pointer;
|
|
174
174
|
font-size: 20px;
|
|
175
175
|
transition: all 0.15s;
|
|
176
|
-
`,
|
|
176
|
+
`,m.onmouseover=()=>{m.style.background="#f3f4f6",m.style.transform="scale(1.2)"},m.onmouseout=()=>{m.style.background="white",m.style.transform="scale(1)"},m.onclick=()=>{n(p),e.remove(),t.remove()},y.appendChild(m)}),r.appendChild(y)},o.appendChild(d)}),o.querySelector("button").click(),t.appendChild(i),t.appendChild(o),t.appendChild(r),document.body.appendChild(e),document.body.appendChild(t),e.onclick=()=>{e.remove(),t.remove()},t}const u=(n,e="")=>`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" ${e}>${n}</svg>`,f=(n,e="")=>window.prompt(n,e),v=[{name:"bold",group:"format",title:"Bold (Ctrl+B)",icon:u('<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>'),action:n=>n.exec("bold"),isActive:n=>n.queryCommand("bold")},{name:"italic",group:"format",title:"Italic (Ctrl+I)",icon:u('<line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>'),action:n=>n.exec("italic"),isActive:n=>n.queryCommand("italic")},{name:"underline",group:"format",title:"Underline (Ctrl+U)",icon:u('<path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"/><line x1="4" y1="21" x2="20" y2="21"/>'),action:n=>n.exec("underline"),isActive:n=>n.queryCommand("underline")},{name:"strikethrough",group:"format",title:"Strikethrough",icon:u('<path d="M17.3 12a4.6 4.6 0 0 1-1.3 3A5 5 0 0 1 12 17a5.7 5.7 0 0 1-4-1.5"/><path d="M6.6 8.8A4.1 4.1 0 0 1 8 6a5 5 0 0 1 4-1.5 5.7 5.7 0 0 1 3.5 1c1 .7 1.6 1.5 1.8 2.5"/><line x1="4" y1="12" x2="20" y2="12"/>'),action:n=>n.exec("strikeThrough"),isActive:n=>n.queryCommand("strikeThrough")},{name:"h1",group:"heading",title:"Heading 1",icon:'<span style="font-weight:700;font-size:12px;line-height:1">H1</span>',action:n=>{const e=n.getActiveBlock()==="h1";n.wrapBlock(e?"p":"h1")},isActive:n=>n.getActiveBlock()==="h1"},{name:"h2",group:"heading",title:"Heading 2",icon:'<span style="font-weight:700;font-size:12px;line-height:1">H2</span>',action:n=>{const e=n.getActiveBlock()==="h2";n.wrapBlock(e?"p":"h2")},isActive:n=>n.getActiveBlock()==="h2"},{name:"h3",group:"heading",title:"Heading 3",icon:'<span style="font-weight:700;font-size:12px;line-height:1">H3</span>',action:n=>{const e=n.getActiveBlock()==="h3";n.wrapBlock(e?"p":"h3")},isActive:n=>n.getActiveBlock()==="h3"},{name:"ul",group:"list",title:"Bullet List",icon:u('<line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4" cy="6" r="1.5" fill="currentColor" stroke="none"/><circle cx="4" cy="12" r="1.5" fill="currentColor" stroke="none"/><circle cx="4" cy="18" r="1.5" fill="currentColor" stroke="none"/>'),action:n=>n.exec("insertUnorderedList"),isActive:n=>n.queryCommand("insertUnorderedList")},{name:"ol",group:"list",title:"Numbered List",icon:u('<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'),action:n=>n.exec("insertOrderedList"),isActive:n=>n.queryCommand("insertOrderedList")},{name:"blockquote",group:"block",title:"Blockquote",icon:u('<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/>'),action:n=>{const e=n.getActiveBlock()==="blockquote";n.wrapBlock(e?"p":"blockquote")},isActive:n=>n.getActiveBlock()==="blockquote"},{name:"link",group:"link",title:"Insert Link",icon:u('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>'),action:n=>{const e=document.queryCommandValue("createLink"),t=f("Masukkan URL:",e||"https://");t!==null&&(t.trim()===""?n.exec("unlink"):(n.exec("createLink",t.trim()),n.getArea().querySelectorAll("a").forEach(i=>{i.target="_blank",i.rel="noopener noreferrer"})))},isActive:n=>n.queryCommand("createLink")},{name:"unlink",group:"link",title:"Remove Link",icon:u('<path d="M18.84 12.25l1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.006 5.006 0 0 0-6.95 0l-1.72 1.71"/><path d="M5.17 11.75l-1.71 1.71a5.004 5.004 0 0 0 .12 7.07 5.006 5.006 0 0 0 6.95 0l1.71-1.71"/><line x1="8" y1="2" x2="8" y2="5"/><line x1="2" y1="8" x2="5" y2="8"/><line x1="16" y1="19" x2="16" y2="22"/><line x1="19" y1="16" x2="22" y2="16"/>'),action:n=>n.exec("unlink")},{name:"undo",group:"history",title:"Undo (Ctrl+Z)",icon:u('<polyline points="9 14 4 9 9 4"/><path d="M20 20v-7a4 4 0 0 0-4-4H4"/>'),action:n=>n.exec("undo")},{name:"redo",group:"history",title:"Redo (Ctrl+Y)",icon:u('<polyline points="15 14 20 9 15 4"/><path d="M4 20v-7a4 4 0 0 0 4-4h12"/>'),action:n=>n.exec("redo")},{name:"removeFormat",group:"clear",title:"Hapus Formatting",icon:u('<path d="M4 7V4h16v3"/><path d="M5 20h6"/><path d="M13 4 8 20"/><line x1="17" y1="13" x2="22" y2="18"/><line x1="22" y1="13" x2="17" y2="18"/>'),action:n=>n.exec("removeFormat")},{name:"source",group:"source",title:"Edit HTML Source",icon:u('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>'),action:n=>n.toggleSource(),isActive:n=>n.isSourceMode()},{name:"image",group:"image",title:"Sisipkan Gambar",icon:u('<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/>'),action:n=>n.openImageDialog()},{name:"table",group:"table",title:"Insert Table (2x2)",icon:u('<rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/>'),action:n=>{const e=L(2,2),t=window.getSelection(),i=t.getRangeAt(0);i.insertNode(e),i.collapse(!1),t.removeAllRanges(),t.addRange(i),n.focus()}},{name:"table-add-row",group:"table",title:"Add Row Below",icon:u('<rect x="2" y="2" width="20" height="20"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="12" y1="2" x2="12" y2="22"/><line x1="17" y1="15" x2="23" y2="15"/><line x1="20" y1="12" x2="20" y2="18"/>'),action:n=>S(n)},{name:"table-delete",group:"table",title:"Delete Table",icon:u('<rect x="3" y="3" width="18" height="18" rx="1" stroke-dasharray="3,3"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><line x1="9" y1="9" x2="15" y2="15"/><line x1="15" y1="9" x2="9" y2="15"/>'),action:n=>T(n)}];function L(n,e){const t=document.createElement("table");t.style.cssText=`
|
|
177
177
|
border-collapse: collapse;
|
|
178
178
|
width: 100%;
|
|
179
179
|
margin: 12px 0;
|
|
@@ -182,7 +182,7 @@ var WysiwygEditor=(function(w,k){"use strict";const E={placeholder:"Tulis sesuat
|
|
|
182
182
|
padding: 8px;
|
|
183
183
|
text-align: left;
|
|
184
184
|
min-width: 80px;
|
|
185
|
-
`,r.innerHTML=i===0?`<strong>Header ${o+1}</strong>`:"<br>",s.appendChild(r)}t.appendChild(s)}return t}function
|
|
185
|
+
`,r.innerHTML=i===0?`<strong>Header ${o+1}</strong>`:"<br>",s.appendChild(r)}t.appendChild(s)}return t}function S(n){n.getArea();const e=window.getSelection();if(e.rangeCount===0)return;let i=e.getRangeAt(0).commonAncestorContainer;i.nodeType===3&&(i=i.parentElement);const s=i.closest("td")||i.closest("th");if(!s){alert("Posisikan kursor di dalam tabel terlebih dahulu");return}const o=s.closest("tr"),r=o.closest("table"),l=document.createElement("tr");Array.from(o.children).forEach(()=>{const c=document.createElement("td");c.style.cssText=s.style.cssText,c.innerHTML="<br>",l.appendChild(c)}),r.appendChild(l)}function T(n){n.getArea();const e=window.getSelection();if(e.rangeCount===0)return;let i=e.getRangeAt(0).commonAncestorContainer;i.nodeType===3&&(i=i.parentElement);const s=i.closest("table");if(!s){alert("Posisikan kursor di dalam tabel terlebih dahulu");return}confirm("Hapus tabel ini?")&&(s.remove(),n.focus())}v.push({name:"mention",group:"insert",title:"Mention (@username)",icon:u('<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 11h-6m-2-2 2 2-2 2" stroke-width="1.5"/>'),action:n=>{const e=f("Username:","@");if(e&&e.trim()){const t=document.createElement("span");t.className="mention",t.contentEditable=!1,t.style.cssText=`
|
|
186
186
|
background: #e0e7ff;
|
|
187
187
|
color: #4f46e5;
|
|
188
188
|
padding: 2px 6px;
|
|
@@ -191,4 +191,4 @@ var WysiwygEditor=(function(w,k){"use strict";const E={placeholder:"Tulis sesuat
|
|
|
191
191
|
font-weight: 500;
|
|
192
192
|
cursor: default;
|
|
193
193
|
display: inline-block;
|
|
194
|
-
`,t.textContent=e,t.dataset.mention=e.replace("@","");const i=window.getSelection().getRangeAt(0);i.insertNode(t),i.setStartAfter(t),i.collapse(!0);const s=window.getSelection();s.removeAllRanges(),s.addRange(i),n.focus()}}},{name:"emoji",group:"insert",title:"Insert Emoji",icon:u('<circle cx="12" cy="12" r="9"/><circle cx="9" cy="9" r="1.5" fill="currentColor"/><circle cx="15" cy="9" r="1.5" fill="currentColor"/><path d="M8 15a4 4 0 0 0 8 0"/>'),action:n=>{
|
|
194
|
+
`,t.textContent=e,t.dataset.mention=e.replace("@","");const i=window.getSelection().getRangeAt(0);i.insertNode(t),i.setStartAfter(t),i.collapse(!0);const s=window.getSelection();s.removeAllRanges(),s.addRange(i),n.focus()}}},{name:"emoji",group:"insert",title:"Insert Emoji",icon:u('<circle cx="12" cy="12" r="9"/><circle cx="9" cy="9" r="1.5" fill="currentColor"/><circle cx="15" cy="9" r="1.5" fill="currentColor"/><path d="M8 15a4 4 0 0 0 8 0"/>'),action:n=>{A(e=>{const t=window.getSelection().getRangeAt(0),i=document.createTextNode(e);t.insertNode(i),t.setStartAfter(i),t.collapse(!0);const s=window.getSelection();s.removeAllRanges(),s.addRange(t),n.focus()})}});async function b(n,e,t){const i=new FormData;i.append("image",n);const s=document.querySelector('meta[name="csrf-token"]')?.content,o={};return s&&(o["X-CSRF-TOKEN"]=s,o["X-Requested-With"]="XMLHttpRequest"),new Promise((r,l)=>{const c=new XMLHttpRequest;c.open("POST",e),Object.entries(o).forEach(([a,d])=>c.setRequestHeader(a,d)),c.upload.addEventListener("progress",a=>{a.lengthComputable&&t&&t(Math.round(a.loaded/a.total*100))}),c.addEventListener("load",()=>{try{const a=JSON.parse(c.responseText);if(c.status>=200&&c.status<300&&a?.ok&&a?.url)r(a.url);else{const d=a?.message||a?.errors?.image?.[0]||`Server error ${c.status}`;l(new Error(d))}}catch{l(new Error("Respon server tidak valid"))}}),c.addEventListener("error",()=>l(new Error("Koneksi gagal"))),c.addEventListener("abort",()=>l(new Error("Upload dibatalkan"))),c.send(i)})}v.forEach(n=>g.registerPlugin(n));function x(){document.querySelectorAll("[data-wysiwyg]").forEach(n=>{if(n._wysiwygEditor)return;const e=new g(n,{placeholder:n.dataset.wysiwygPlaceholder||"Tulis sesuatu...",minHeight:parseInt(n.dataset.wysiwygHeight||"200",10),syncInput:n.dataset.wysiwygInput||null,uploadUrl:n.dataset.wysiwygUploadUrl||"",uploadFn:b}),t=n.dataset.wysiwygInitial||"";t&&e.setHTML(t),n._wysiwygEditor=e})}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",x):x(),typeof window<"u"&&(window.WysiwygEditor=g,window.WysiwygEditor.create=(n,e={})=>new g(n,{uploadFn:b,...e})),w.WysiwygEditor=g,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"}),w})({});
|