@wiliarko/wysiwyg-editor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/package.json +46 -0
- package/wysiwyg.min.css +1 -0
- package/wysiwyg.min.js +194 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WYSIWYG Editor Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# WYSIWYG Editor Standalone
|
|
2
|
+
|
|
3
|
+
A custom, lightweight rich-text editor built with zero external dependencies. Perfect for any projectโLaravel, Vue, React, or plain HTML.
|
|
4
|
+
|
|
5
|
+
**Features:**
|
|
6
|
+
- โจ Pure JavaScript (no dependencies)
|
|
7
|
+
- ๐ฆ Tiny bundle: 38 KB min, 11 KB gzipped
|
|
8
|
+
- ๐จ Rich formatting (bold, italic, underline, strikethrough, headings, lists, blockquote)
|
|
9
|
+
- ๐ผ๏ธ Image upload & resize
|
|
10
|
+
- ๐ Link insertion & removal
|
|
11
|
+
- ๐ Table creation & editing
|
|
12
|
+
- ๐ท๏ธ Mention support (@username)
|
|
13
|
+
- ๐ Emoji picker (8 categories)
|
|
14
|
+
- ๐ป HTML source mode
|
|
15
|
+
- ๐ฑ Mobile friendly
|
|
16
|
+
- โฟ Accessible
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### 1. Add to HTML
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<link rel="stylesheet" href="wysiwyg.min.css">
|
|
24
|
+
<script src="wysiwyg.min.js"></script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2a. Auto-Init (Simplest)
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<div data-wysiwyg
|
|
31
|
+
data-wysiwyg-upload-url="/api/upload"
|
|
32
|
+
data-wysiwyg-height="400"
|
|
33
|
+
data-wysiwyg-placeholder="Write something..."
|
|
34
|
+
data-wysiwyg-input="#content"></div>
|
|
35
|
+
<input type="hidden" id="content" name="content">
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2b. Manual Init (More Control)
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
const editor = WysiwygEditor.create('#my-editor', {
|
|
42
|
+
uploadUrl: '/api/upload',
|
|
43
|
+
minHeight: 400,
|
|
44
|
+
placeholder: 'Type here...',
|
|
45
|
+
onChange: (html) => console.log('Content:', html)
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Get content
|
|
49
|
+
const html = editor.getHTML();
|
|
50
|
+
|
|
51
|
+
// Set content
|
|
52
|
+
editor.setHTML('<h1>Hello</h1><p>World</p>');
|
|
53
|
+
|
|
54
|
+
// Toggle HTML source mode
|
|
55
|
+
editor.toggleSource();
|
|
56
|
+
|
|
57
|
+
// Cleanup
|
|
58
|
+
editor.destroy();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Configuration Options
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
{
|
|
65
|
+
placeholder: 'Tulis sesuatu...', // Input placeholder
|
|
66
|
+
minHeight: 250, // Minimum editor height (px)
|
|
67
|
+
uploadUrl: '/upload', // Image upload endpoint
|
|
68
|
+
uploadFn: customUploadFunction, // Custom upload logic (async)
|
|
69
|
+
onChange: (html) => {}, // Content change callback
|
|
70
|
+
syncInput: '#hidden-input', // Hidden input selector to sync
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Upload Endpoint Format
|
|
75
|
+
|
|
76
|
+
Server should respond with JSON:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"success": true,
|
|
81
|
+
"url": "https://yourserver.com/uploads/image.jpg"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Request:
|
|
86
|
+
```
|
|
87
|
+
POST /your-upload-endpoint
|
|
88
|
+
Content-Type: multipart/form-data
|
|
89
|
+
Field: "file" (binary image)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Toolbar Plugins
|
|
93
|
+
|
|
94
|
+
Default plugins:
|
|
95
|
+
- **Format**: Bold, Italic, Underline, Strikethrough
|
|
96
|
+
- **Heading**: H1, H2, H3
|
|
97
|
+
- **Lists**: Bullet list, Ordered list
|
|
98
|
+
- **Blocks**: Blockquote
|
|
99
|
+
- **Links**: Insert link, Remove link
|
|
100
|
+
- **Images**: Upload, URL, Resize, Alignment
|
|
101
|
+
- **Tables**: Insert 2x2, Add row, Delete
|
|
102
|
+
- **Mention**: @username
|
|
103
|
+
- **Emoji**: Picker with 8 categories
|
|
104
|
+
- **Source**: Edit raw HTML
|
|
105
|
+
- **Format**: Remove all formatting
|
|
106
|
+
|
|
107
|
+
## Usage Examples
|
|
108
|
+
|
|
109
|
+
### Laravel
|
|
110
|
+
|
|
111
|
+
```blade
|
|
112
|
+
<x-wysiwyg
|
|
113
|
+
name="content"
|
|
114
|
+
label="Article Content"
|
|
115
|
+
:value="$post->content"
|
|
116
|
+
placeholder="Write article..."
|
|
117
|
+
:height="400"
|
|
118
|
+
:uploadUrl="route('wysiwyg.upload-image')"
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Vue 3
|
|
123
|
+
|
|
124
|
+
```vue
|
|
125
|
+
<template>
|
|
126
|
+
<div ref="editorEl"></div>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<script setup>
|
|
130
|
+
import { onMounted, onUnmounted } from 'vue'
|
|
131
|
+
|
|
132
|
+
let editor
|
|
133
|
+
|
|
134
|
+
onMounted(() => {
|
|
135
|
+
editor = WysiwygEditor.create(editorEl, {
|
|
136
|
+
uploadUrl: '/api/upload',
|
|
137
|
+
onChange: (html) => console.log(html)
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
onUnmounted(() => {
|
|
142
|
+
editor.destroy()
|
|
143
|
+
})
|
|
144
|
+
</script>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### React
|
|
148
|
+
|
|
149
|
+
```jsx
|
|
150
|
+
import { useEffect, useRef } from 'react'
|
|
151
|
+
|
|
152
|
+
export function Editor() {
|
|
153
|
+
const editorRef = useRef(null)
|
|
154
|
+
const editorInstance = useRef(null)
|
|
155
|
+
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (typeof window !== 'undefined' && window.WysiwygEditor) {
|
|
158
|
+
editorInstance.current = WysiwygEditor.create(editorRef.current, {
|
|
159
|
+
uploadUrl: '/api/upload'
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
return () => editorInstance.current?.destroy()
|
|
163
|
+
}
|
|
164
|
+
}, [])
|
|
165
|
+
|
|
166
|
+
return <div ref={editorRef} style={{ height: '400px' }} />
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Plain HTML + Vanilla JS
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<!DOCTYPE html>
|
|
174
|
+
<html>
|
|
175
|
+
<head>
|
|
176
|
+
<link rel="stylesheet" href="wysiwyg.min.css">
|
|
177
|
+
</head>
|
|
178
|
+
<body>
|
|
179
|
+
<textarea id="content" hidden></textarea>
|
|
180
|
+
<div id="editor"></div>
|
|
181
|
+
|
|
182
|
+
<script src="wysiwyg.min.js"></script>
|
|
183
|
+
<script>
|
|
184
|
+
const editor = WysiwygEditor.create('#editor', {
|
|
185
|
+
syncInput: '#content',
|
|
186
|
+
uploadUrl: '/upload'
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
document.querySelector('form').addEventListener('submit', (e) => {
|
|
190
|
+
const html = editor.getHTML()
|
|
191
|
+
document.getElementById('content').value = html
|
|
192
|
+
})
|
|
193
|
+
</script>
|
|
194
|
+
</body>
|
|
195
|
+
</html>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## API Reference
|
|
199
|
+
|
|
200
|
+
### Constructor
|
|
201
|
+
```javascript
|
|
202
|
+
const editor = new WysiwygEditor(element, options)
|
|
203
|
+
const editor = WysiwygEditor.create(element, options)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Methods
|
|
207
|
+
```javascript
|
|
208
|
+
editor.getHTML() // Get content as HTML string
|
|
209
|
+
editor.setHTML(html) // Set content
|
|
210
|
+
editor.focus() // Focus editor
|
|
211
|
+
editor.destroy() // Cleanup
|
|
212
|
+
editor.toggleSource() // Toggle HTML source mode
|
|
213
|
+
editor.isSourceMode() // Check if in source mode
|
|
214
|
+
editor.openImageDialog() // Open image insertion dialog
|
|
215
|
+
editor.getArea() // Get contenteditable element
|
|
216
|
+
editor.exec(command, value) // Execute browser command
|
|
217
|
+
editor.queryCommand(cmd) // Query command state
|
|
218
|
+
editor.getActiveBlock() // Get current block element
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Static Methods
|
|
222
|
+
```javascript
|
|
223
|
+
WysiwygEditor.registerPlugin(pluginObject) // Register custom plugin
|
|
224
|
+
WysiwygEditor.getPlugins() // Get all registered plugins
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Custom Plugin
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
WysiwygEditor.registerPlugin({
|
|
231
|
+
name: 'highlight',
|
|
232
|
+
group: 'format',
|
|
233
|
+
title: 'Highlight',
|
|
234
|
+
icon: '<span style="bg:yellow;px:4px">H</span>',
|
|
235
|
+
action: (editor) => editor.exec('backColor', 'yellow'),
|
|
236
|
+
isActive: (editor) => editor.queryCommand('backColor')
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Browser Support
|
|
241
|
+
|
|
242
|
+
- Chrome/Edge 60+
|
|
243
|
+
- Firefox 55+
|
|
244
|
+
- Safari 11+
|
|
245
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
246
|
+
|
|
247
|
+
## Performance
|
|
248
|
+
|
|
249
|
+
- **Bundle size**: 38 KB minified, 11 KB gzipped
|
|
250
|
+
- **No dependencies**: Faster load, zero conflicts
|
|
251
|
+
- **contenteditable based**: Native browser support
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT
|
|
256
|
+
|
|
257
|
+
## Support
|
|
258
|
+
|
|
259
|
+
For issues, features, or questions:
|
|
260
|
+
- GitHub: [github.com/yourusername/wysiwyg-editor](https://github.com/yourusername/wysiwyg-editor)
|
|
261
|
+
- Email: support@example.com
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
**Made with โค๏ธ by [Your Team]**
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wiliarko/wysiwyg-editor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Custom rich-text editor with zero dependencies. Supports formatting, images, tables, mentions, emojis, and more.",
|
|
5
|
+
"main": "wysiwyg.min.js",
|
|
6
|
+
"module": "wysiwyg.min.js",
|
|
7
|
+
"types": "wysiwyg.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"wysiwyg.min.js",
|
|
10
|
+
"wysiwyg.min.css",
|
|
11
|
+
"demo.html",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"No tests for dist package\""
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"wysiwyg",
|
|
20
|
+
"editor",
|
|
21
|
+
"rich-text",
|
|
22
|
+
"rich text editor",
|
|
23
|
+
"contenteditable",
|
|
24
|
+
"markdown",
|
|
25
|
+
"html",
|
|
26
|
+
"formatting",
|
|
27
|
+
"table",
|
|
28
|
+
"emoji",
|
|
29
|
+
"mention",
|
|
30
|
+
"zero-dependency",
|
|
31
|
+
"vanilla-js"
|
|
32
|
+
],
|
|
33
|
+
"author": "Your Name",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/yourusername/wysiwyg-editor-standalone.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/yourusername/wysiwyg-editor-standalone/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/yourusername/wysiwyg-editor-standalone#readme",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=14.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/wysiwyg.min.css
ADDED
|
@@ -0,0 +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}}
|
package/wysiwyg.min.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
var WysiwygEditor=(function(w,k){"use strict";const E={placeholder:"Tulis sesuatu...",minHeight:200,plugins:[],onChange:null,syncInput:null,uploadUrl:"",uploadFn:null};class m{static#p=new Map;static registerPlugin(e){if(!e.name)throw new Error("[wysiwyg] Plugin harus punya nama.");m.#p.set(e.name,e)}static getPlugins(){return Array.from(m.#p.values())}#l;#e;#n;#u=!1;#s;#k=null;#t;#h;#a=null;#o=null;#i=null;#v=null;#m=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={...E,...t},this.#h=new AbortController,this.#T(),this.#j(),this.#f()}#T(){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"?m.#p.get(i):i).filter(Boolean):m.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
|
+
<div class="wy-img-modal__backdrop"></div>
|
|
3
|
+
<div class="wy-img-modal__box">
|
|
4
|
+
<div class="wy-img-modal__head">
|
|
5
|
+
<span class="wy-img-modal__title">Sisipkan Gambar</span>
|
|
6
|
+
<button type="button" class="wy-img-modal__close" aria-label="Tutup">
|
|
7
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
8
|
+
</button>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!-- Tab switcher -->
|
|
12
|
+
<div class="wy-img-modal__tabs">
|
|
13
|
+
<button type="button" class="wy-img-tab is-active" data-tab="upload">Upload File</button>
|
|
14
|
+
<button type="button" class="wy-img-tab" data-tab="url">URL Gambar</button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Tab: Upload -->
|
|
18
|
+
<div class="wy-img-modal__panel" data-panel="upload">
|
|
19
|
+
<label class="wy-img-dropzone" tabindex="0" aria-label="Pilih atau seret file gambar">
|
|
20
|
+
<input type="file" accept="image/*" class="wy-img-file" aria-hidden="true" />
|
|
21
|
+
<div class="wy-img-dropzone__icon">
|
|
22
|
+
<svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><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"/></svg>
|
|
23
|
+
</div>
|
|
24
|
+
<p class="wy-img-dropzone__text">Seret gambar ke sini atau <span>klik untuk pilih</span></p>
|
|
25
|
+
<p class="wy-img-dropzone__hint">PNG, JPG, GIF, WebP โ maks 5MB</p>
|
|
26
|
+
</label>
|
|
27
|
+
<div class="wy-img-preview" style="display:none">
|
|
28
|
+
<img class="wy-img-preview__img" src="" alt="" />
|
|
29
|
+
<button type="button" class="wy-img-preview__remove" aria-label="Hapus pilihan">ร</button>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="wy-img-modal__progress" style="display:none">
|
|
32
|
+
<div class="wy-img-modal__progress-bar"></div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Tab: URL -->
|
|
37
|
+
<div class="wy-img-modal__panel" data-panel="url" style="display:none">
|
|
38
|
+
<label class="wy-img-url-label">
|
|
39
|
+
URL Gambar
|
|
40
|
+
<input type="url" class="wy-img-url-input" placeholder="https://contoh.com/gambar.jpg" />
|
|
41
|
+
</label>
|
|
42
|
+
<label class="wy-img-url-label" style="margin-top:12px">
|
|
43
|
+
Alt Text (deskripsi gambar)
|
|
44
|
+
<input type="text" class="wy-img-alt-input" placeholder="Deskripsi gambar..." />
|
|
45
|
+
</label>
|
|
46
|
+
<div class="wy-img-url-preview" style="display:none; margin-top:12px; text-align:center">
|
|
47
|
+
<img class="wy-img-url-preview__img" src="" alt="" style="max-height:120px; max-width:100%; border-radius:6px;" />
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Options bersama -->
|
|
52
|
+
<div class="wy-img-modal__options">
|
|
53
|
+
<label class="wy-img-opt-label">
|
|
54
|
+
Lebar
|
|
55
|
+
<input type="text" class="wy-img-width-input" placeholder="otomatis" />
|
|
56
|
+
</label>
|
|
57
|
+
<label class="wy-img-opt-label">
|
|
58
|
+
Alignment
|
|
59
|
+
<select class="wy-img-align-select">
|
|
60
|
+
<option value="">Default</option>
|
|
61
|
+
<option value="center">Center</option>
|
|
62
|
+
<option value="left">Float Kiri</option>
|
|
63
|
+
<option value="right">Float Kanan</option>
|
|
64
|
+
<option value="block-left">Blok Kiri</option>
|
|
65
|
+
<option value="block-right">Blok Kanan</option>
|
|
66
|
+
</select>
|
|
67
|
+
</label>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<p class="wy-img-modal__error" style="display:none"></p>
|
|
71
|
+
|
|
72
|
+
<div class="wy-img-modal__footer">
|
|
73
|
+
<button type="button" class="wy-img-modal__cancel">Batal</button>
|
|
74
|
+
<button type="button" class="wy-img-modal__insert" disabled>Sisipkan</button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
`,document.body.appendChild(e),this.#a=e,this.#R()}#q(){const e=document.createElement("div");e.className="wy-img-toolbar",e.style.display="none",e.innerHTML=`
|
|
78
|
+
<button type="button" data-align="" title="Default">
|
|
79
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="1"/></svg>
|
|
80
|
+
</button>
|
|
81
|
+
<button type="button" data-align="center" title="Center">
|
|
82
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
|
83
|
+
</button>
|
|
84
|
+
<button type="button" data-align="left" title="Float Kiri">
|
|
85
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="8" height="8" rx="1"/><line x1="12" y1="6" x2="21" y2="6"/><line x1="12" y1="10" x2="21" y2="10"/><line x1="2" y1="16" x2="21" y2="16"/><line x1="2" y1="20" x2="21" y2="20"/></svg>
|
|
86
|
+
</button>
|
|
87
|
+
<button type="button" data-align="right" title="Float Kanan">
|
|
88
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="14" y="4" width="8" height="8" rx="1"/><line x1="3" y1="6" x2="12" y2="6"/><line x1="3" y1="10" x2="12" y2="10"/><line x1="3" y1="16" x2="21" y2="16"/><line x1="3" y1="20" x2="21" y2="20"/></svg>
|
|
89
|
+
</button>
|
|
90
|
+
<div class="wy-img-toolbar__sep"></div>
|
|
91
|
+
<button type="button" data-action="delete" title="Hapus gambar">
|
|
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
|
+
</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.#m=null}#b(){this.#a.style.display="none",this.#m=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.#m=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.#B(),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.#m: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.#m=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#B(){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.#m)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.#S(this.#m,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#S(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;s.append("image",e);const o=document.querySelector('meta[name="csrf-token"]')?.content,{data:r}=await k.post(i,s,{headers:o?{"X-CSRF-TOKEN":o}:{},onUploadProgress:l=>{!t||!l.total||t(Math.round(l.loaded/l.total*100))}});if(!r?.ok||!r?.url)throw new Error("Respon server tidak valid.");return r.url}async#I(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.#S(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.#L(e,i||"")}#L(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.#L(this.#i,i.dataset.align),this.#C(),this.#g(this.#i),this.#r())}},e)}#C(){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.#C(),this.#g(e)}#y(){this.#i&&this.#i.classList.remove("wy-img-selected"),this.#i=null,this.#o.style.display="none",this.#c.style.display="none"}#g(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
|
+
<div class="wy-img-resize-handle" data-dir="nw"></div>
|
|
96
|
+
<div class="wy-img-resize-handle" data-dir="ne"></div>
|
|
97
|
+
<div class="wy-img-resize-handle" data-dir="sw"></div>
|
|
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.#g(this.#i)},{signal:this.#h.signal,capture:!0}),window.addEventListener("resize",()=>{this.#i&&this.#g(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.#g(i)},d=()=>{document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",d),this.#r()};document.addEventListener("mousemove",a),document.addEventListener("mouseup",d)}#j(){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=m.#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.#O(i);document.execCommand("insertHTML",!1,r)}else if(o.length&&o[0].type.startsWith("image/"))this.#I(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.#P(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.#g(this.#i)},e)}#P(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"?m.#p.get(t):t).filter(Boolean):m.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.#$(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()}#$(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
|
+
<$1$2>`).replace(new RegExp(`</(${t.join("|")})>`,"gi"),`</$1>
|
|
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,g=(a.match(/<[a-zA-Z][^>]*\/>/g)||[]).length;return o=Math.max(0,o+y-p-g+(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()}#O(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 A={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 S(n){const e=document.createElement("div");e.style.cssText=`
|
|
104
|
+
position: fixed;
|
|
105
|
+
top: 0;
|
|
106
|
+
left: 0;
|
|
107
|
+
width: 100%;
|
|
108
|
+
height: 100%;
|
|
109
|
+
background: rgba(0, 0, 0, 0.5);
|
|
110
|
+
z-index: 9998;
|
|
111
|
+
`;const t=document.createElement("div");t.style.cssText=`
|
|
112
|
+
position: fixed;
|
|
113
|
+
top: 50%;
|
|
114
|
+
left: 50%;
|
|
115
|
+
transform: translate(-50%, -50%);
|
|
116
|
+
background: white;
|
|
117
|
+
border-radius: 12px;
|
|
118
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
119
|
+
width: 90%;
|
|
120
|
+
max-width: 500px;
|
|
121
|
+
max-height: 600px;
|
|
122
|
+
z-index: 9999;
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
`;const i=document.createElement("div");i.style.cssText=`
|
|
127
|
+
padding: 16px;
|
|
128
|
+
border-bottom: 1px solid #eee;
|
|
129
|
+
display: flex;
|
|
130
|
+
justify-content: space-between;
|
|
131
|
+
align-items: center;
|
|
132
|
+
`,i.innerHTML='<h3 style="margin: 0; font-size: 16px;">Select Emoji</h3>';const s=document.createElement("button");s.innerHTML="โ",s.style.cssText=`
|
|
133
|
+
background: none;
|
|
134
|
+
border: none;
|
|
135
|
+
font-size: 20px;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
color: #666;
|
|
138
|
+
`,s.onclick=()=>{e.remove(),t.remove()},i.appendChild(s);const o=document.createElement("div");o.style.cssText=`
|
|
139
|
+
display: flex;
|
|
140
|
+
gap: 4px;
|
|
141
|
+
padding: 8px;
|
|
142
|
+
border-bottom: 1px solid #eee;
|
|
143
|
+
overflow-x: auto;
|
|
144
|
+
`;const r=document.createElement("div");return r.style.cssText=`
|
|
145
|
+
flex: 1;
|
|
146
|
+
overflow-y: auto;
|
|
147
|
+
padding: 12px;
|
|
148
|
+
`,Object.entries(A).forEach(([l,c],a)=>{const d=document.createElement("button");d.innerHTML=c.emoji[0],d.style.cssText=`
|
|
149
|
+
background: ${a===0?"#e0e7ff":"#f3f4f6"};
|
|
150
|
+
border: none;
|
|
151
|
+
width: 36px;
|
|
152
|
+
height: 36px;
|
|
153
|
+
border-radius: 6px;
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
font-size: 18px;
|
|
156
|
+
flex-shrink: 0;
|
|
157
|
+
`,d.onclick=()=>{o.querySelectorAll("button").forEach(p=>{p.style.background="#f3f4f6"}),d.style.background="#e0e7ff",r.innerHTML="";const h=document.createElement("div");h.style.cssText=`
|
|
158
|
+
font-size: 12px;
|
|
159
|
+
font-weight: 600;
|
|
160
|
+
color: #666;
|
|
161
|
+
margin-bottom: 8px;
|
|
162
|
+
text-transform: uppercase;
|
|
163
|
+
`,h.textContent=c.title,r.appendChild(h);const y=document.createElement("div");y.style.cssText=`
|
|
164
|
+
display: grid;
|
|
165
|
+
grid-template-columns: repeat(auto-fill, 36px);
|
|
166
|
+
gap: 4px;
|
|
167
|
+
`,c.emoji.forEach(p=>{const g=document.createElement("button");g.textContent=p,g.style.cssText=`
|
|
168
|
+
width: 36px;
|
|
169
|
+
height: 36px;
|
|
170
|
+
border: 1px solid #ddd;
|
|
171
|
+
background: white;
|
|
172
|
+
border-radius: 6px;
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
font-size: 20px;
|
|
175
|
+
transition: all 0.15s;
|
|
176
|
+
`,g.onmouseover=()=>{g.style.background="#f3f4f6",g.style.transform="scale(1.2)"},g.onmouseout=()=>{g.style.background="white",g.style.transform="scale(1)"},g.onclick=()=>{n(p),e.remove(),t.remove()},y.appendChild(g)}),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=>C(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
|
+
border-collapse: collapse;
|
|
178
|
+
width: 100%;
|
|
179
|
+
margin: 12px 0;
|
|
180
|
+
`;for(let i=0;i<n;i++){const s=document.createElement("tr");for(let o=0;o<e;o++){const r=document.createElement(i===0?"th":"td");r.style.cssText=`
|
|
181
|
+
border: 1px solid #ccc;
|
|
182
|
+
padding: 8px;
|
|
183
|
+
text-align: left;
|
|
184
|
+
min-width: 80px;
|
|
185
|
+
`,r.innerHTML=i===0?`<strong>Header ${o+1}</strong>`:"<br>",s.appendChild(r)}t.appendChild(s)}return t}function C(n){n.getArea();const e=window.getSelection();if(e.rangeCount===0)return;const t=e.getRangeAt(0),i=t.commonAncestorContainer.closest("td")||t.commonAncestorContainer.closest("th");if(!i){alert("Posisikan kursor di dalam tabel terlebih dahulu");return}const s=i.closest("tr"),o=s.closest("table"),r=document.createElement("tr");Array.from(s.children).forEach(()=>{const l=document.createElement("td");l.style.cssText=i.style.cssText,l.innerHTML="<br>",r.appendChild(l)}),o.appendChild(r)}function T(n){n.getArea();const e=window.getSelection();if(e.rangeCount===0)return;const i=e.getRangeAt(0).commonAncestorContainer.closest("table");if(!i){alert("Posisikan kursor di dalam tabel terlebih dahulu");return}confirm("Hapus tabel ini?")&&(i.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
|
+
background: #e0e7ff;
|
|
187
|
+
color: #4f46e5;
|
|
188
|
+
padding: 2px 6px;
|
|
189
|
+
border-radius: 4px;
|
|
190
|
+
margin: 0 2px;
|
|
191
|
+
font-weight: 500;
|
|
192
|
+
cursor: default;
|
|
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=>{S(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=>m.registerPlugin(n));function x(){document.querySelectorAll("[data-wysiwyg]").forEach(n=>{if(n._wysiwygEditor)return;const e=new m(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=m,window.WysiwygEditor.create=(n,e={})=>new m(n,{uploadFn:b,...e})),w.WysiwygEditor=m,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"}),w})({},axios);
|