@malaya_jeeva/rich-text-editor 1.0.4 → 1.0.6

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/dist/index.mjs CHANGED
@@ -1,144 +1,293 @@
1
1
  // src/RichTextEditor.tsx
2
2
  import {
3
- useState,
4
- useRef,
5
- useCallback,
6
- useEffect
3
+ useState as useState5,
4
+ useRef as useRef4,
5
+ useCallback as useCallback3,
6
+ useEffect as useEffect3
7
7
  } from "react";
8
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
- var CSS = `
10
- * { box-sizing: border-box; }
11
- .rte-toolbar {
12
- display: flex; flex-wrap: wrap; align-items: center; gap: 1px;
13
- padding: 4px 8px; background: #f8f8f8; border-bottom: 1px solid #e0e0e0;
14
- }
15
- @media (prefers-color-scheme: dark) { .rte-toolbar { background: #1e1e1e; border-color: #333; } }
16
-
17
- .rte-btn {
18
- background: transparent; border: none; border-radius: 3px; cursor: pointer;
19
- height: 30px; min-width: 30px; padding: 0 6px; color: #444; font-size: 13px;
20
- font-weight: 500; font-family: var(--font-sans); display: inline-flex;
21
- align-items: center; justify-content: center; user-select: none;
22
- white-space: nowrap; transition: background 0.1s; flex-shrink: 0;
23
- }
24
- .rte-btn:hover { background: #e8e8e8; }
25
- .rte-btn.active { background: #d0e4ff; color: #1a5fb4; }
26
- .rte-btn.danger { color: #c0392b; }
27
- .rte-btn.danger:hover { background: #fdecea; }
28
- @media (prefers-color-scheme: dark) {
29
- .rte-btn { color: #ccc; } .rte-btn:hover { background: #2e2e2e; }
30
- .rte-btn.active { background: #1a3a5c; color: #90c4ff; }
31
- .rte-btn.danger { color: #ff6b6b; } .rte-btn.danger:hover { background: #3a1a1a; }
32
- }
33
- .rte-sep { width: 1px; height: 20px; background: #d8d8d8; margin: 0 4px; flex-shrink: 0; }
34
- @media (prefers-color-scheme: dark) { .rte-sep { background: #3a3a3a; } }
35
8
 
36
- .rte-select {
37
- height: 28px; border: 1px solid #d8d8d8; border-radius: 3px;
38
- background: #fff; color: #333; font-size: 12px; padding: 0 22px 0 7px;
39
- cursor: pointer; outline: none; font-family: var(--font-sans);
40
- appearance: none; -webkit-appearance: none;
41
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E");
42
- background-repeat: no-repeat; background-position: right 6px center;
43
- }
44
- @media (prefers-color-scheme: dark) { .rte-select { background-color: #2a2a2a; border-color: #444; color: #ccc; } }
45
- .rte-swatch { width: 14px; height: 3px; border-radius: 1px; margin-top: 2px; }
46
-
47
- /* Table picker */
48
- .rte-tp-wrap { position: relative; display: inline-flex; }
49
- .rte-tp {
50
- position: absolute; top: 34px; left: 0; z-index: 100;
51
- background: #fff; border: 1px solid #d8d8d8; border-radius: 6px;
52
- box-shadow: 0 4px 16px rgba(0,0,0,0.12); padding: 10px;
53
- }
54
- @media (prefers-color-scheme: dark) { .rte-tp { background: #222; border-color: #444; } }
55
- .rte-tp-lbl { font-size: 11px; color: #888; text-align: center; margin-bottom: 8px; font-family: var(--font-sans); }
56
- .rte-tp-grid { display: grid; grid-template-columns: repeat(8, 18px); gap: 2px; }
57
- .rte-tp-cell { width: 18px; height: 18px; border-radius: 2px; border: 1px solid #ddd; cursor: pointer; background: #fff; transition: background 0.08s; }
58
- .rte-tp-cell.on { background: #d0e4ff; border-color: #1a5fb4; }
59
- @media (prefers-color-scheme: dark) {
60
- .rte-tp-cell { background: #2a2a2a; border-color: #444; }
61
- .rte-tp-cell.on { background: #1a3a5c; border-color: #90c4ff; }
62
- }
9
+ // src/rte/constants.ts
10
+ var PALETTE = [
11
+ "#000000",
12
+ "#434343",
13
+ "#666666",
14
+ "#999999",
15
+ "#b7b7b7",
16
+ "#cccccc",
17
+ "#d9d9d9",
18
+ "#ffffff",
19
+ "#ff0000",
20
+ "#ff4500",
21
+ "#ff9900",
22
+ "#ffff00",
23
+ "#00ff00",
24
+ "#00ffff",
25
+ "#4a86e8",
26
+ "#0000ff",
27
+ "#9900ff",
28
+ "#ff00ff",
29
+ "#e06666",
30
+ "#f6b26b",
31
+ "#ffd966",
32
+ "#93c47d",
33
+ "#76a5af",
34
+ "#6fa8dc",
35
+ "#8e7cc3",
36
+ "#c27ba0",
37
+ "#a61c00",
38
+ "#783f04",
39
+ "#7f6000",
40
+ "#274e13",
41
+ "#0c343d",
42
+ "#073763",
43
+ "#20124d",
44
+ "#4c1130"
45
+ ];
46
+ var CSS = `
47
+ *{box-sizing:border-box;}
48
+ .rte-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:1px;padding:4px 8px;background:#f8f8f8;border-bottom:1px solid #e0e0e0;}
49
+ @media(prefers-color-scheme:dark){.rte-toolbar{background:#1e1e1e;border-color:#333;}}
50
+ .rte-btn{background:transparent;border:none;border-radius:3px;cursor:pointer;height:30px;min-width:30px;padding:0 6px;color:#444;font-size:13px;font-weight:500;font-family:var(--font-sans);display:inline-flex;align-items:center;justify-content:center;user-select:none;white-space:nowrap;transition:background 0.1s;flex-shrink:0;}
51
+ .rte-btn:hover{background:#e8e8e8;}.rte-btn.active{background:#d0e4ff;color:#1a5fb4;}
52
+ .rte-btn.danger{color:#c0392b;}.rte-btn.danger:hover{background:#fdecea;}
53
+ @media(prefers-color-scheme:dark){.rte-btn{color:#ccc;}.rte-btn:hover{background:#2e2e2e;}.rte-btn.active{background:#1a3a5c;color:#90c4ff;}.rte-btn.danger{color:#ff6b6b;}.rte-btn.danger:hover{background:#3a1a1a;}}
54
+ .rte-sep{width:1px;height:20px;background:#d8d8d8;margin:0 4px;flex-shrink:0;}
55
+ @media(prefers-color-scheme:dark){.rte-sep{background:#3a3a3a;}}
56
+ .rte-select{height:28px;border:1px solid #d8d8d8;border-radius:3px;background:#fff;color:#333;font-size:12px;padding:0 22px 0 7px;cursor:pointer;outline:none;font-family:var(--font-sans);appearance:none;-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 6px center;}
57
+ @media(prefers-color-scheme:dark){.rte-select{background-color:#2a2a2a;border-color:#444;color:#ccc;}}
58
+ .rte-swatch{width:14px;height:3px;border-radius:1px;margin-top:2px;}
59
+ .rte-cpick{background:#fff;border:1px solid #ccc;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,0.15);padding:8px;min-width:190px;}
60
+ @media(prefers-color-scheme:dark){.rte-cpick{background:#222;border-color:#444;}}
61
+ .rte-cgrid{display:grid;grid-template-columns:repeat(8,20px);gap:2px;margin-bottom:6px;}
62
+ .rte-ccell{width:20px;height:20px;border-radius:3px;cursor:pointer;border:1px solid rgba(0,0,0,0.12);transition:transform 0.08s,box-shadow 0.08s;}
63
+ .rte-ccell:hover{transform:scale(1.2);box-shadow:0 1px 4px rgba(0,0,0,0.3);z-index:1;position:relative;}
64
+ .rte-ccell.sel{outline:2px solid #1a5fb4;outline-offset:1px;}
65
+ .rte-cactions{display:flex;align-items:center;gap:2px;border-top:1px solid #eee;padding-top:6px;margin-top:2px;}
66
+ @media(prefers-color-scheme:dark){.rte-cactions{border-color:#333;}}
67
+ .rte-caction{flex:1;height:28px;display:flex;align-items:center;justify-content:center;border-radius:4px;cursor:pointer;border:1px solid transparent;transition:background 0.1s;}
68
+ .rte-caction:hover{background:#f0f0f0;border-color:#ddd;}
69
+ @media(prefers-color-scheme:dark){.rte-caction:hover{background:#2a2a2a;border-color:#444;}}
70
+ .rte-tpick{background:#fff;border:1px solid #d8d8d8;border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.12);padding:10px;}
71
+ @media(prefers-color-scheme:dark){.rte-tpick{background:#222;border-color:#444;}}
72
+ .rte-tplbl{font-size:11px;color:#888;text-align:center;margin-bottom:8px;font-family:var(--font-sans);}
73
+ .rte-tpgrid{display:grid;grid-template-columns:repeat(8,18px);gap:2px;}
74
+ .rte-tpcell{width:18px;height:18px;border-radius:2px;border:1px solid #ddd;cursor:pointer;background:#fff;transition:background 0.08s;}
75
+ .rte-tpcell.on{background:#d0e4ff;border-color:#1a5fb4;}
76
+ @media(prefers-color-scheme:dark){.rte-tpcell{background:#2a2a2a;border-color:#444;}.rte-tpcell.on{background:#1a3a5c;border-color:#90c4ff;}}
77
+ .rte-ipick{background:#fff;border:1px solid #ccc;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,0.15);width:300px;overflow:hidden;}
78
+ @media(prefers-color-scheme:dark){.rte-ipick{background:#222;border-color:#444;}}
79
+ .rte-itabs{display:flex;border-bottom:1px solid #eee;}
80
+ @media(prefers-color-scheme:dark){.rte-itabs{border-color:#333;}}
81
+ .rte-itab{flex:1;padding:8px 4px;font-size:12px;font-weight:500;background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;color:#888;font-family:var(--font-sans);}
82
+ .rte-itab:hover{color:#444;background:#f8f8f8;}.rte-itab.active{color:#1a5fb4;border-bottom-color:#1a5fb4;}
83
+ @media(prefers-color-scheme:dark){.rte-itab:hover{background:#2a2a2a;}}
84
+ .rte-ipbody{padding:12px;display:flex;flex-direction:column;gap:10px;}
85
+ .rte-idrop{border:2px dashed #ccc;border-radius:6px;padding:24px 12px;text-align:center;cursor:pointer;transition:border-color 0.15s,background 0.15s;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;min-height:110px;}
86
+ .rte-idrop:hover,.rte-idrop.drag{border-color:#1a5fb4;background:#f0f4ff;}
87
+ @media(prefers-color-scheme:dark){.rte-idrop{border-color:#555;}.rte-idrop:hover,.rte-idrop.drag{background:#1a3a5c;border-color:#90c4ff;}}
88
+ .rte-iinsert{width:100%;height:30px;background:#1a6fc4;color:#fff;border:none;border-radius:4px;font-size:12px;font-weight:600;cursor:pointer;}
89
+ .rte-iinsert:disabled{opacity:0.4;cursor:not-allowed;}
90
+ .rte-float{position:absolute;z-index:50;background:#fff;border:1px solid #d0d0d0;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,0.13);padding:4px 6px;display:flex;align-items:center;gap:1px;flex-wrap:wrap;}
91
+ @media(prefers-color-scheme:dark){.rte-float{background:#222;border-color:#444;}}
92
+ .rte-float-arrow{position:absolute;top:-7px;width:14px;height:7px;}
93
+ .rte-float-arrow::before,.rte-float-arrow::after{content:'';position:absolute;left:0;border-left:7px solid transparent;border-right:7px solid transparent;}
94
+ .rte-float-arrow::before{top:0;border-bottom:7px solid #d0d0d0;}
95
+ .rte-float-arrow::after{top:1px;border-bottom:6px solid #fff;}
96
+ @media(prefers-color-scheme:dark){.rte-float-arrow::before{border-bottom-color:#444;}.rte-float-arrow::after{border-bottom-color:#222;}}
97
+ .rte-linkbar{min-width:340px;}
98
+ .rte-linkbar input{flex:1;height:26px;border:1px solid #ccc;border-radius:3px;padding:0 8px;font-size:13px;outline:none;font-family:var(--font-sans);background:#fff;color:#222;min-width:0;}
99
+ @media(prefers-color-scheme:dark){.rte-linkbar input{background:#1a1a1a;border-color:#555;color:#ddd;}}
100
+ .rte-area{position:relative;background:#fff;}
101
+ @media(prefers-color-scheme:dark){.rte-area{background:#141414;}}
102
+ .rte-body{min-height:280px;outline:none;font-size:15px;line-height:1.75;color:#222;caret-color:#222;padding:18px 20px;}
103
+ @media(prefers-color-scheme:dark){.rte-body{color:#ddd;caret-color:#ddd;}}
104
+ .rte-body h1{font-size:26px;font-weight:600;margin:0.6em 0 0.2em;}
105
+ .rte-body h2{font-size:20px;font-weight:600;margin:0.6em 0 0.2em;}
106
+ .rte-body h3{font-size:16px;font-weight:600;margin:0.6em 0 0.2em;}
107
+ .rte-body p{margin:0.2em 0;}
108
+ .rte-body ol{list-style-type:decimal;padding-left:1.6em;margin:0.3em 0;}
109
+ .rte-body ol ol{list-style-type:lower-alpha;padding-left:1.6em;}
110
+ .rte-body ol ol ol{list-style-type:lower-roman;padding-left:1.6em;}
111
+ .rte-body ol ol ol ol{list-style-type:decimal;padding-left:1.6em;}
112
+ .rte-body ul{list-style-type:disc;padding-left:1.6em;margin:0.3em 0;}
113
+ .rte-body ul ul{list-style-type:circle;padding-left:1.6em;}
114
+ .rte-body ul ul ul{list-style-type:square;padding-left:1.6em;}
115
+ .rte-body li{margin:0.1em 0;}
116
+ .rte-body a{color:#1a6fc4;text-decoration:underline;cursor:pointer;}
117
+ .rte-body pre{background:#f4f4f4;border:1px solid #e0e0e0;border-radius:4px;padding:12px 14px;margin:0.6em 0;overflow-x:auto;}
118
+ .rte-body code{font-family:var(--font-mono);font-size:13px;}
119
+ .rte-body:empty:before{content:attr(data-ph);color:#aaa;pointer-events:none;}
120
+ .rte-body table{border-collapse:collapse;width:100%;margin:0.8em 0;}
121
+ .rte-body td,.rte-body th{border:1px solid #ccc;padding:7px 10px;min-width:60px;text-align:left;vertical-align:top;}
122
+ .rte-body th{background:#f5f5f5;font-weight:600;}
123
+ @media(prefers-color-scheme:dark){.rte-body td,.rte-body th{border-color:#444;}.rte-body th{background:#2a2a2a;}}
124
+ .rte-body td.rte-sel,.rte-body th.rte-sel{background:rgba(26,95,180,0.15)!important;outline:2px solid #1a5fb4;outline-offset:-2px;}
125
+ .rte-body img{max-width:100%;height:auto;display:block;margin:4px 0;cursor:pointer;}
126
+ .rte-body figure{margin:0.8em 0;display:block;overflow:hidden;}
127
+ .rte-body figcaption{font-size:13px;color:#666;text-align:center;padding:4px 0;outline:none;clear:both;display:block;}
128
+ .rte-code{min-height:280px;width:100%;background:#1e1e2e;color:#cdd6f4;font-family:var(--font-mono);font-size:13px;line-height:1.6;padding:18px 20px;border:none;outline:none;resize:vertical;}
129
+ .rte-footer{font-size:11px;color:#999;padding:4px 20px 5px;border-top:1px solid #e8e8e8;background:#fafafa;}
130
+ @media(prefers-color-scheme:dark){.rte-footer{border-color:#2a2a2a;background:#1a1a1a;color:#555;}}
131
+ .rte-ie-panel{position:absolute;z-index:52;background:#fff;border:1px solid #d0d0d0;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,0.15);width:210px;overflow:hidden;}
132
+ @media(prefers-color-scheme:dark){.rte-ie-panel{background:#222;border-color:#444;}}
133
+ .rte-ie-tabs{display:flex;border-bottom:1px solid #eee;}
134
+ @media(prefers-color-scheme:dark){.rte-ie-tabs{border-color:#333;}}
135
+ .rte-ie-tab{flex:1;padding:7px 4px;font-size:11px;font-weight:500;background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;color:#888;font-family:var(--font-sans);}
136
+ .rte-ie-tab.active{color:#1a5fb4;border-bottom-color:#1a5fb4;}
137
+ .rte-ie-tab:hover{background:#f8f8f8;}
138
+ @media(prefers-color-scheme:dark){.rte-ie-tab:hover{background:#2a2a2a;}}
139
+ .rte-ie-body{padding:10px;display:flex;flex-direction:column;gap:8px;}
140
+ .rte-ie-label{font-size:10px;color:#888;margin-bottom:3px;font-family:var(--font-sans);}
141
+ .rte-ie-input{width:100%;height:26px;border:1px solid #ccc;border-radius:4px;padding:0 8px;font-size:11px;outline:none;font-family:var(--font-sans);background:#fff;color:#222;}
142
+ @media(prefers-color-scheme:dark){.rte-ie-input{background:#1a1a1a;border-color:#444;color:#ddd;}}
143
+ .rte-ie-row{display:flex;gap:4px;}
144
+ .rte-ie-seg-btn{flex:1;height:26px;font-size:11px;border:1px solid #ccc;border-radius:4px;background:transparent;color:#444;cursor:pointer;font-family:var(--font-sans);transition:all 0.1s;}
145
+ .rte-ie-seg-btn.active{background:#d0e4ff;border-color:#1a5fb4;color:#1a5fb4;}
146
+ .rte-ie-preset{flex:1;height:22px;font-size:10px;border:1px solid #ccc;border-radius:3px;background:transparent;cursor:pointer;font-family:var(--font-sans);}
147
+ .rte-ie-preset:hover{background:#e8e8e8;}
148
+ .rte-ie-delete{width:100%;height:28px;background:#fdecea;color:#c0392b;border:1px solid #f5c6c2;border-radius:4px;font-size:11px;font-weight:600;cursor:pointer;font-family:var(--font-sans);}
149
+ .rte-ie-apply{flex:1;height:26px;background:#1a6fc4;color:#fff;border:none;border-radius:4px;font-size:11px;font-weight:600;cursor:pointer;}
150
+ .rte-ie-remove{flex:1;height:26px;background:#fdecea;color:#c0392b;border:1px solid #f5c6c2;border-radius:4px;font-size:11px;cursor:pointer;}
151
+ .rte-handle{position:absolute;width:10px;height:10px;background:#fff;border:2px solid #1a5fb4;border-radius:2px;pointer-events:all;z-index:53;}
152
+ `;
63
153
 
64
- /* \u2500\u2500 Floating toolbar \u2500\u2500 */
65
- .rte-float {
66
- position: absolute; z-index: 50;
67
- background: #fff; border: 1px solid #d0d0d0;
68
- border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.13);
69
- padding: 4px 6px;
70
- display: flex; align-items: center; gap: 1px; flex-wrap: wrap;
71
- }
72
- @media (prefers-color-scheme: dark) { .rte-float { background: #222; border-color: #444; } }
73
- .rte-float-arrow {
74
- position: absolute; top: -7px; width: 14px; height: 7px;
75
- }
76
- .rte-float-arrow::before,
77
- .rte-float-arrow::after {
78
- content: ''; position: absolute; left: 0;
79
- border-left: 7px solid transparent; border-right: 7px solid transparent;
80
- }
81
- .rte-float-arrow::before { top: 0; border-bottom: 7px solid #d0d0d0; }
82
- .rte-float-arrow::after { top: 1px; border-bottom: 6px solid #fff; }
83
- @media (prefers-color-scheme: dark) {
84
- .rte-float-arrow::before { border-bottom-color: #444; }
85
- .rte-float-arrow::after { border-bottom-color: #222; }
154
+ // src/rte/utils.ts
155
+ function hsv2rgb(h, s, v) {
156
+ const f = (n) => {
157
+ const k = (n + h / 60) % 6;
158
+ return v - v * s * Math.max(0, Math.min(k, 4 - k, 1));
159
+ };
160
+ return [Math.round(f(5) * 255), Math.round(f(3) * 255), Math.round(f(1) * 255)];
161
+ }
162
+ function rgb2hex(r, g, b) {
163
+ return "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join("");
164
+ }
165
+ function hex2hsv(hex) {
166
+ if (hex.length < 7) return [0, 1, 1];
167
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
168
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
169
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
170
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
171
+ let h = 0;
172
+ const s = max === 0 ? 0 : d / max, v = max;
173
+ if (d !== 0) {
174
+ if (max === r) h = (g - b) / d % 6;
175
+ else if (max === g) h = (b - r) / d + 2;
176
+ else h = (r - g) / d + 4;
177
+ h = Math.round(h * 60);
178
+ if (h < 0) h += 360;
86
179
  }
87
-
88
- /* Link bar inside float */
89
- .rte-link-float { min-width: 340px; }
90
- .rte-link-float input {
91
- flex: 1; height: 26px; border: 1px solid #ccc; border-radius: 3px;
92
- padding: 0 8px; font-size: 13px; outline: none; font-family: var(--font-sans);
93
- background: #fff; color: #222;
94
- min-width: 0;
180
+ return [h, s, v];
181
+ }
182
+ function getColorAtCursor(editor) {
183
+ var _a, _b;
184
+ if (!editor) return "#000000";
185
+ const sel = window.getSelection();
186
+ if (!(sel == null ? void 0 : sel.rangeCount)) return "#000000";
187
+ let el = ((_a = sel.anchorNode) == null ? void 0 : _a.nodeType) === 3 ? sel.anchorNode.parentElement : sel.anchorNode;
188
+ while (el && el !== editor) {
189
+ const c = (_b = el.style) == null ? void 0 : _b.color;
190
+ if (c) {
191
+ const m = c.match(/\d+/g);
192
+ if (m && m.length >= 3)
193
+ return "#" + m.slice(0, 3).map((n) => parseInt(n).toString(16).padStart(2, "0")).join("");
194
+ if (c.startsWith("#")) return c;
195
+ }
196
+ el = el.parentElement;
95
197
  }
96
- @media (prefers-color-scheme: dark) { .rte-link-float input { background: #1a1a1a; border-color: #555; color: #ddd; } }
97
- .rte-link-apply {
98
- height: 26px; padding: 0 10px; background: #1a6fc4; color: #fff;
99
- border: none; border-radius: 3px; font-size: 12px; font-weight: 600; cursor: pointer; white-space: nowrap;
198
+ return "#000000";
199
+ }
200
+ function prettifyHtml(html) {
201
+ const INLINE = /* @__PURE__ */ new Set(["a", "b", "i", "u", "em", "strong", "span", "code", "br", "small", "sub", "sup"]);
202
+ let indent = 0;
203
+ const pad = () => " ".repeat(indent);
204
+ const tokens = html.replace(/>\s+</g, "><").replace(/(<\/?[^>]+>)/g, "\0$1\0").split("\0").filter(Boolean);
205
+ const lines = [];
206
+ let inline = "";
207
+ const flush = () => {
208
+ if (inline.trim()) {
209
+ lines.push(pad() + inline.trim());
210
+ inline = "";
211
+ }
212
+ };
213
+ for (const tok of tokens) {
214
+ const ot = tok.match(/^<([a-zA-Z][a-zA-Z0-9]*)[^>]*>$/);
215
+ const ct = tok.match(/^<\/([a-zA-Z][a-zA-Z0-9]*)>$/);
216
+ if (tok.match(/^<[^>]+\/>$/)) {
217
+ flush();
218
+ lines.push(pad() + tok);
219
+ continue;
220
+ }
221
+ if (ot) {
222
+ const tag = ot[1].toLowerCase();
223
+ if (INLINE.has(tag)) {
224
+ inline += tok;
225
+ continue;
226
+ }
227
+ flush();
228
+ lines.push(pad() + tok);
229
+ indent++;
230
+ continue;
231
+ }
232
+ if (ct) {
233
+ const tag = ct[1].toLowerCase();
234
+ if (INLINE.has(tag)) {
235
+ inline += tok;
236
+ continue;
237
+ }
238
+ flush();
239
+ indent = Math.max(0, indent - 1);
240
+ const prev = lines[lines.length - 1];
241
+ prev && prev.trim().startsWith("<") && !prev.includes("</") ? lines[lines.length - 1] = prev + tok : lines.push(pad() + tok);
242
+ continue;
243
+ }
244
+ inline += tok;
100
245
  }
101
- .rte-link-cancel {
102
- height: 26px; padding: 0 8px; background: transparent; color: #666;
103
- border: 1px solid #ccc; border-radius: 3px; font-size: 12px; cursor: pointer;
246
+ flush();
247
+ return lines.join("\n");
248
+ }
249
+ function getCurrentCell() {
250
+ var _a;
251
+ const node = (_a = window.getSelection()) == null ? void 0 : _a.anchorNode;
252
+ const el = (node == null ? void 0 : node.nodeType) === 1 ? node : node == null ? void 0 : node.parentElement;
253
+ return el == null ? void 0 : el.closest("td, th");
254
+ }
255
+ function getCurrentTable() {
256
+ var _a;
257
+ return (_a = getCurrentCell()) == null ? void 0 : _a.closest("table");
258
+ }
259
+ function getCurrentLink() {
260
+ var _a;
261
+ const node = (_a = window.getSelection()) == null ? void 0 : _a.anchorNode;
262
+ const el = (node == null ? void 0 : node.nodeType) === 1 ? node : node == null ? void 0 : node.parentElement;
263
+ return el == null ? void 0 : el.closest("a");
264
+ }
265
+ function getCellCoords(table, cell) {
266
+ for (let r = 0; r < table.rows.length; r++) {
267
+ const cells = Array.from(table.rows[r].cells);
268
+ for (let c = 0; c < cells.length; c++) {
269
+ if (cells[c] === cell) return [r, c];
270
+ }
104
271
  }
105
- @media (prefers-color-scheme: dark) { .rte-link-cancel { color: #aaa; border-color: #555; } }
272
+ return null;
273
+ }
274
+ function getCellsInRange(table, a, b) {
275
+ const ca = getCellCoords(table, a), cb = getCellCoords(table, b);
276
+ if (!ca || !cb) return [a];
277
+ const [r1, c1] = [Math.min(ca[0], cb[0]), Math.min(ca[1], cb[1])];
278
+ const [r2, c2] = [Math.max(ca[0], cb[0]), Math.max(ca[1], cb[1])];
279
+ const result = [];
280
+ Array.from(table.rows).forEach((row, ri) => {
281
+ if (ri >= r1 && ri <= r2)
282
+ Array.from(row.cells).forEach((cell, ci) => {
283
+ if (ci >= c1 && ci <= c2) result.push(cell);
284
+ });
285
+ });
286
+ return result;
287
+ }
106
288
 
107
- /* Editor */
108
- .rte-area { position: relative; background: #fff; }
109
- @media (prefers-color-scheme: dark) { .rte-area { background: #141414; } }
110
- .rte-body {
111
- min-height: 280px; outline: none; font-size: 15px; line-height: 1.75;
112
- color: #222; caret-color: #222; padding: 18px 20px;
113
- }
114
- @media (prefers-color-scheme: dark) { .rte-body { color: #ddd; caret-color: #ddd; } }
115
- .rte-body h1 { font-size: 26px; font-weight: 600; margin: 0.6em 0 0.2em; }
116
- .rte-body h2 { font-size: 20px; font-weight: 600; margin: 0.6em 0 0.2em; }
117
- .rte-body h3 { font-size: 16px; font-weight: 600; margin: 0.6em 0 0.2em; }
118
- .rte-body p { margin: 0.2em 0; }
119
- .rte-body ol { list-style-type: decimal; padding-left: 1.6em; margin: 0.3em 0; }
120
- .rte-body ol ol { list-style-type: lower-alpha; padding-left: 1.6em; }
121
- .rte-body ol ol ol { list-style-type: lower-roman; padding-left: 1.6em; }
122
- .rte-body ul { list-style-type: disc; padding-left: 1.6em; margin: 0.3em 0; }
123
- .rte-body ul ul { list-style-type: circle; padding-left: 1.6em; }
124
- .rte-body ul ul ul { list-style-type: square; padding-left: 1.6em; }
125
- .rte-body li { margin: 0.1em 0; }
126
- .rte-body a { color: #1a6fc4; text-decoration: underline; cursor: pointer; }
127
- .rte-body pre { background: #f4f4f4; border: 1px solid #e0e0e0; border-radius: 4px; padding: 12px 14px; margin: 0.6em 0; overflow-x: auto; }
128
- .rte-body code { font-family: var(--font-mono); font-size: 13px; }
129
- .rte-body:empty:before { content: attr(data-ph); color: #aaa; pointer-events: none; }
130
- .rte-body table { border-collapse: collapse; width: 100%; margin: 0.8em 0; }
131
- .rte-body td, .rte-body th { border: 1px solid #ccc; padding: 7px 10px; min-width: 60px; text-align: left; vertical-align: top; }
132
- .rte-body th { background: #f5f5f5; font-weight: 600; }
133
- @media (prefers-color-scheme: dark) {
134
- .rte-body td, .rte-body th { border-color: #444; }
135
- .rte-body th { background: #2a2a2a; }
136
- }
137
- .rte-body td.rte-sel, .rte-body th.rte-sel { background: rgba(26,95,180,0.15) !important; outline: 2px solid #1a5fb4; outline-offset: -2px; }
138
- .rte-code { min-height: 280px; width: 100%; background: #1e1e2e; color: #cdd6f4; font-family: var(--font-mono); font-size: 13px; line-height: 1.6; padding: 18px 20px; border: none; outline: none; resize: vertical; }
139
- .rte-footer { font-size: 11px; color: #999; padding: 4px 20px 5px; border-top: 1px solid #e8e8e8; background: #fafafa; }
140
- @media (prefers-color-scheme: dark) { .rte-footer { border-color: #2a2a2a; background: #1a1a1a; color: #555; } }
141
- `;
289
+ // src/rte/Btn.tsx
290
+ import { jsx } from "react/jsx-runtime";
142
291
  function Btn({ onClick, title, active, danger, children, style }) {
143
292
  return /* @__PURE__ */ jsx(
144
293
  "button",
@@ -157,264 +306,858 @@ function Btn({ onClick, title, active, danger, children, style }) {
157
306
  function Sep() {
158
307
  return /* @__PURE__ */ jsx("div", { className: "rte-sep" });
159
308
  }
160
- function TablePicker({ onInsert, onClose }) {
161
- const [hover, setHover] = useState([0, 0]);
162
- return /* @__PURE__ */ jsxs("div", { className: "rte-tp", children: [
163
- /* @__PURE__ */ jsx("div", { className: "rte-tp-lbl", children: hover[0] > 0 ? `${hover[0]} \xD7 ${hover[1]}` : "Select table size" }),
164
- /* @__PURE__ */ jsx("div", { className: "rte-tp-grid", children: Array.from({ length: 64 }, (_, i) => {
165
- const r = Math.floor(i / 8) + 1, c = i % 8 + 1;
166
- return /* @__PURE__ */ jsx(
167
- "div",
168
- {
169
- className: `rte-tp-cell${r <= hover[0] && c <= hover[1] ? " on" : ""}`,
170
- onMouseEnter: () => setHover([r, c]),
171
- onMouseDown: (e) => {
172
- e.preventDefault();
173
- if (hover[0] > 0) {
174
- onInsert(hover[0], hover[1]);
175
- onClose();
176
- }
177
- }
178
- },
179
- i
180
- );
181
- }) })
182
- ] });
183
- }
309
+
310
+ // src/rte/Icons.tsx
311
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
184
312
  function IcoUL() {
185
313
  return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
186
- /* @__PURE__ */ jsx("circle", { cx: "2", cy: "4", r: "1.3", fill: "currentColor" }),
187
- /* @__PURE__ */ jsx("circle", { cx: "2", cy: "8", r: "1.3", fill: "currentColor" }),
188
- /* @__PURE__ */ jsx("circle", { cx: "2", cy: "12", r: "1.3", fill: "currentColor" }),
189
- /* @__PURE__ */ jsx("rect", { x: "5.5", y: "3.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
190
- /* @__PURE__ */ jsx("rect", { x: "5.5", y: "7.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
191
- /* @__PURE__ */ jsx("rect", { x: "5.5", y: "11.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" })
314
+ /* @__PURE__ */ jsx2("circle", { cx: "2", cy: "4", r: "1.3", fill: "currentColor" }),
315
+ /* @__PURE__ */ jsx2("circle", { cx: "2", cy: "8", r: "1.3", fill: "currentColor" }),
316
+ /* @__PURE__ */ jsx2("circle", { cx: "2", cy: "12", r: "1.3", fill: "currentColor" }),
317
+ /* @__PURE__ */ jsx2("rect", { x: "5.5", y: "3.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
318
+ /* @__PURE__ */ jsx2("rect", { x: "5.5", y: "7.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
319
+ /* @__PURE__ */ jsx2("rect", { x: "5.5", y: "11.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" })
192
320
  ] });
193
321
  }
194
322
  function IcoOL() {
195
323
  return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
196
- /* @__PURE__ */ jsx("text", { x: "0", y: "5", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "1." }),
197
- /* @__PURE__ */ jsx("text", { x: "0", y: "9.5", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "2." }),
198
- /* @__PURE__ */ jsx("text", { x: "0", y: "14", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "3." }),
199
- /* @__PURE__ */ jsx("rect", { x: "5.5", y: "3.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
200
- /* @__PURE__ */ jsx("rect", { x: "5.5", y: "7.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
201
- /* @__PURE__ */ jsx("rect", { x: "5.5", y: "11.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" })
324
+ /* @__PURE__ */ jsx2("text", { x: "0", y: "5", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "1." }),
325
+ /* @__PURE__ */ jsx2("text", { x: "0", y: "9.5", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "2." }),
326
+ /* @__PURE__ */ jsx2("text", { x: "0", y: "14", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "3." }),
327
+ /* @__PURE__ */ jsx2("rect", { x: "5.5", y: "3.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
328
+ /* @__PURE__ */ jsx2("rect", { x: "5.5", y: "7.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
329
+ /* @__PURE__ */ jsx2("rect", { x: "5.5", y: "11.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" })
202
330
  ] });
203
331
  }
204
332
  function IcoIndent() {
205
333
  return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
206
- /* @__PURE__ */ jsx("rect", { x: "1", y: "2", width: "13", height: "1.6", rx: ".8", fill: "currentColor" }),
207
- /* @__PURE__ */ jsx("rect", { x: "4.5", y: "6", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
208
- /* @__PURE__ */ jsx("rect", { x: "4.5", y: "10", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
209
- /* @__PURE__ */ jsx("path", { d: "M1 6.5L3.5 8.25L1 10V6.5Z", fill: "currentColor" })
334
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "2", width: "13", height: "1.6", rx: ".8", fill: "currentColor" }),
335
+ /* @__PURE__ */ jsx2("rect", { x: "4.5", y: "6", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
336
+ /* @__PURE__ */ jsx2("rect", { x: "4.5", y: "10", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
337
+ /* @__PURE__ */ jsx2("path", { d: "M1 6.5L3.5 8.25L1 10V6.5Z", fill: "currentColor" })
210
338
  ] });
211
339
  }
212
340
  function IcoOutdent() {
213
341
  return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
214
- /* @__PURE__ */ jsx("rect", { x: "1", y: "2", width: "13", height: "1.6", rx: ".8", fill: "currentColor" }),
215
- /* @__PURE__ */ jsx("rect", { x: "4.5", y: "6", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
216
- /* @__PURE__ */ jsx("rect", { x: "4.5", y: "10", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
217
- /* @__PURE__ */ jsx("path", { d: "M3.5 6.5L1 8.25L3.5 10V6.5Z", fill: "currentColor" })
342
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "2", width: "13", height: "1.6", rx: ".8", fill: "currentColor" }),
343
+ /* @__PURE__ */ jsx2("rect", { x: "4.5", y: "6", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
344
+ /* @__PURE__ */ jsx2("rect", { x: "4.5", y: "10", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
345
+ /* @__PURE__ */ jsx2("path", { d: "M3.5 6.5L1 8.25L3.5 10V6.5Z", fill: "currentColor" })
218
346
  ] });
219
347
  }
220
348
  function IcoLink() {
221
349
  return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
222
- /* @__PURE__ */ jsx("path", { d: "M6 9C6.4 9.6 7.1 10 7.8 10H9.8C10.6 10 11.4 9.6 11.9 9C12.4 8.4 12.6 7.6 12.6 6.8C12.6 6 12.4 5.3 11.9 4.7C11.4 4.1 10.6 3.8 9.8 3.8H8.6", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" }),
223
- /* @__PURE__ */ jsx("path", { d: "M9 6C8.6 5.4 7.9 5 7.2 5H5.2C4.4 5 3.6 5.4 3.1 6C2.6 6.6 2.4 7.4 2.4 8.2C2.4 9 2.6 9.7 3.1 10.3C3.6 10.9 4.4 11.2 5.2 11.2H6.4", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
350
+ /* @__PURE__ */ jsx2("path", { d: "M6 9C6.4 9.6 7.1 10 7.8 10H9.8C10.6 10 11.4 9.6 11.9 9C12.4 8.4 12.6 7.6 12.6 6.8C12.6 6 12.4 5.3 11.9 4.7C11.4 4.1 10.6 3.8 9.8 3.8H8.6", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" }),
351
+ /* @__PURE__ */ jsx2("path", { d: "M9 6C8.6 5.4 7.9 5 7.2 5H5.2C4.4 5 3.6 5.4 3.1 6C2.6 6.6 2.4 7.4 2.4 8.2C2.4 9 2.6 9.7 3.1 10.3C3.6 10.9 4.4 11.2 5.2 11.2H6.4", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
224
352
  ] });
225
353
  }
226
354
  function IcoCode() {
227
355
  return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
228
- /* @__PURE__ */ jsx("path", { d: "M5 4.5L1.5 7.5L5 10.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }),
229
- /* @__PURE__ */ jsx("path", { d: "M10 4.5L13.5 7.5L10 10.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }),
230
- /* @__PURE__ */ jsx("path", { d: "M8.5 2L6.5 13", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })
356
+ /* @__PURE__ */ jsx2("path", { d: "M5 4.5L1.5 7.5L5 10.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }),
357
+ /* @__PURE__ */ jsx2("path", { d: "M10 4.5L13.5 7.5L10 10.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }),
358
+ /* @__PURE__ */ jsx2("path", { d: "M8.5 2L6.5 13", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })
231
359
  ] });
232
360
  }
233
361
  function IcoCopy() {
234
362
  return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", children: [
235
- /* @__PURE__ */ jsx("rect", { x: "1", y: "3", width: "8.5", height: "10", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
236
- /* @__PURE__ */ jsx("path", { d: "M4 1H12.5C13.1 1 13.5 1.4 13.5 2V10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
363
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "3", width: "8.5", height: "10", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
364
+ /* @__PURE__ */ jsx2("path", { d: "M4 1H12.5C13.1 1 13.5 1.4 13.5 2V10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
237
365
  ] });
238
366
  }
239
367
  function IcoTable() {
240
368
  return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
241
- /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "13", height: "13", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
242
- /* @__PURE__ */ jsx("line", { x1: "1", y1: "5.5", x2: "14", y2: "5.5", stroke: "currentColor", strokeWidth: "1.1" }),
243
- /* @__PURE__ */ jsx("line", { x1: "1", y1: "10", x2: "14", y2: "10", stroke: "currentColor", strokeWidth: "1.1" }),
244
- /* @__PURE__ */ jsx("line", { x1: "5.5", y1: "5.5", x2: "5.5", y2: "14", stroke: "currentColor", strokeWidth: "1.1" }),
245
- /* @__PURE__ */ jsx("line", { x1: "10", y1: "5.5", x2: "10", y2: "14", stroke: "currentColor", strokeWidth: "1.1" })
369
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "1", width: "13", height: "13", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
370
+ /* @__PURE__ */ jsx2("line", { x1: "1", y1: "5.5", x2: "14", y2: "5.5", stroke: "currentColor", strokeWidth: "1.1" }),
371
+ /* @__PURE__ */ jsx2("line", { x1: "1", y1: "10", x2: "14", y2: "10", stroke: "currentColor", strokeWidth: "1.1" }),
372
+ /* @__PURE__ */ jsx2("line", { x1: "5.5", y1: "5.5", x2: "5.5", y2: "14", stroke: "currentColor", strokeWidth: "1.1" }),
373
+ /* @__PURE__ */ jsx2("line", { x1: "10", y1: "5.5", x2: "10", y2: "14", stroke: "currentColor", strokeWidth: "1.1" })
374
+ ] });
375
+ }
376
+ function IcoImage() {
377
+ return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
378
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "2", width: "13", height: "11", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
379
+ /* @__PURE__ */ jsx2("circle", { cx: "5", cy: "5.5", r: "1.2", fill: "currentColor" }),
380
+ /* @__PURE__ */ jsx2("path", { d: "M1 10L4.5 7L7 9.5L9.5 7L14 11", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
246
381
  ] });
247
382
  }
248
383
  function IcoRowAbove() {
249
384
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
250
- /* @__PURE__ */ jsx("rect", { x: "1", y: "7", width: "14", height: "8", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
251
- /* @__PURE__ */ jsx("line", { x1: "8", y1: "7", x2: "8", y2: "15", stroke: "currentColor", strokeWidth: "1" }),
252
- /* @__PURE__ */ jsx("path", { d: "M8 5V1M6 3L8 1L10 3", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
385
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "7", width: "14", height: "8", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
386
+ /* @__PURE__ */ jsx2("line", { x1: "8", y1: "7", x2: "8", y2: "15", stroke: "currentColor", strokeWidth: "1" }),
387
+ /* @__PURE__ */ jsx2("path", { d: "M8 5V1M6 3L8 1L10 3", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
253
388
  ] });
254
389
  }
255
390
  function IcoRowBelow() {
256
391
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
257
- /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "14", height: "8", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
258
- /* @__PURE__ */ jsx("line", { x1: "8", y1: "1", x2: "8", y2: "9", stroke: "currentColor", strokeWidth: "1" }),
259
- /* @__PURE__ */ jsx("path", { d: "M8 11V15M6 13L8 15L10 13", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
392
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "1", width: "14", height: "8", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
393
+ /* @__PURE__ */ jsx2("line", { x1: "8", y1: "1", x2: "8", y2: "9", stroke: "currentColor", strokeWidth: "1" }),
394
+ /* @__PURE__ */ jsx2("path", { d: "M8 11V15M6 13L8 15L10 13", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
260
395
  ] });
261
396
  }
262
397
  function IcoDelRow() {
263
398
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
264
- /* @__PURE__ */ jsx("rect", { x: "1", y: "4", width: "14", height: "8", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
265
- /* @__PURE__ */ jsx("line", { x1: "1", y1: "8", x2: "15", y2: "8", stroke: "currentColor", strokeWidth: "1" }),
266
- /* @__PURE__ */ jsx("path", { d: "M5.5 6L10.5 10M10.5 6L5.5 10", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
399
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "4", width: "14", height: "8", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
400
+ /* @__PURE__ */ jsx2("line", { x1: "1", y1: "8", x2: "15", y2: "8", stroke: "currentColor", strokeWidth: "1" }),
401
+ /* @__PURE__ */ jsx2("path", { d: "M5.5 6L10.5 10M10.5 6L5.5 10", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
267
402
  ] });
268
403
  }
269
404
  function IcoColLeft() {
270
405
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
271
- /* @__PURE__ */ jsx("rect", { x: "7", y: "1", width: "8", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
272
- /* @__PURE__ */ jsx("line", { x1: "7", y1: "8", x2: "15", y2: "8", stroke: "currentColor", strokeWidth: "1" }),
273
- /* @__PURE__ */ jsx("path", { d: "M5 8H1M3 6L1 8L3 10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
406
+ /* @__PURE__ */ jsx2("rect", { x: "7", y: "1", width: "8", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
407
+ /* @__PURE__ */ jsx2("line", { x1: "7", y1: "8", x2: "15", y2: "8", stroke: "currentColor", strokeWidth: "1" }),
408
+ /* @__PURE__ */ jsx2("path", { d: "M5 8H1M3 6L1 8L3 10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
274
409
  ] });
275
410
  }
276
411
  function IcoColRight() {
277
412
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
278
- /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "8", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
279
- /* @__PURE__ */ jsx("line", { x1: "1", y1: "8", x2: "9", y2: "8", stroke: "currentColor", strokeWidth: "1" }),
280
- /* @__PURE__ */ jsx("path", { d: "M11 8H15M13 6L15 8L13 10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
413
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "1", width: "8", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
414
+ /* @__PURE__ */ jsx2("line", { x1: "1", y1: "8", x2: "9", y2: "8", stroke: "currentColor", strokeWidth: "1" }),
415
+ /* @__PURE__ */ jsx2("path", { d: "M11 8H15M13 6L15 8L13 10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
281
416
  ] });
282
417
  }
283
418
  function IcoDelCol() {
284
419
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
285
- /* @__PURE__ */ jsx("rect", { x: "4", y: "1", width: "8", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
286
- /* @__PURE__ */ jsx("line", { x1: "8", y1: "1", x2: "8", y2: "15", stroke: "currentColor", strokeWidth: "1" }),
287
- /* @__PURE__ */ jsx("path", { d: "M6 5.5L10 10.5M10 5.5L6 10.5", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
420
+ /* @__PURE__ */ jsx2("rect", { x: "4", y: "1", width: "8", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
421
+ /* @__PURE__ */ jsx2("line", { x1: "8", y1: "1", x2: "8", y2: "15", stroke: "currentColor", strokeWidth: "1" }),
422
+ /* @__PURE__ */ jsx2("path", { d: "M6 5.5L10 10.5M10 5.5L6 10.5", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
288
423
  ] });
289
424
  }
290
425
  function IcoMerge() {
291
426
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
292
- /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "6", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
293
- /* @__PURE__ */ jsx("rect", { x: "9", y: "1", width: "6", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
294
- /* @__PURE__ */ jsx("path", { d: "M7 8H9M7.5 6.5L9 8L7.5 9.5M8.5 6.5L7 8L8.5 9.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
427
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "1", width: "6", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
428
+ /* @__PURE__ */ jsx2("rect", { x: "9", y: "1", width: "6", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
429
+ /* @__PURE__ */ jsx2("path", { d: "M7 8H9M7.5 6.5L9 8L7.5 9.5M8.5 6.5L7 8L8.5 9.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
295
430
  ] });
296
431
  }
297
432
  function IcoSplit() {
298
433
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
299
- /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "14", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
300
- /* @__PURE__ */ jsx("line", { x1: "8", y1: "1", x2: "8", y2: "15", stroke: "currentColor", strokeWidth: "1.2" }),
301
- /* @__PURE__ */ jsx("path", { d: "M5.5 8H3M12.5 8H10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }),
302
- /* @__PURE__ */ jsx("path", { d: "M4.5 6.5L3 8L4.5 9.5M11.5 6.5L13 8L11.5 9.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
434
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "1", width: "14", height: "14", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
435
+ /* @__PURE__ */ jsx2("line", { x1: "8", y1: "1", x2: "8", y2: "15", stroke: "currentColor", strokeWidth: "1.2" }),
436
+ /* @__PURE__ */ jsx2("path", { d: "M5.5 8H3M12.5 8H10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }),
437
+ /* @__PURE__ */ jsx2("path", { d: "M4.5 6.5L3 8L4.5 9.5M11.5 6.5L13 8L11.5 9.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
303
438
  ] });
304
439
  }
305
440
  function IcoDelTable() {
306
441
  return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
307
- /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "14", height: "14", rx: "1.5", stroke: "currentColor", strokeWidth: "1.1" }),
308
- /* @__PURE__ */ jsx("path", { d: "M5 5L11 11M11 5L5 11", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })
442
+ /* @__PURE__ */ jsx2("rect", { x: "1", y: "1", width: "14", height: "14", rx: "1.5", stroke: "currentColor", strokeWidth: "1.1" }),
443
+ /* @__PURE__ */ jsx2("path", { d: "M5 5L11 11M11 5L5 11", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })
309
444
  ] });
310
445
  }
311
446
  function AlignIco({ t }) {
312
447
  const w = { left: [13, 9, 11], center: [9, 7, 11], right: [13, 9, 11], justify: [13, 13, 13] };
313
- return /* @__PURE__ */ jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx("rect", { x: t === "right" ? 15 - w[t][i] - 1 : 1, y: [2, 6.5, 11][i], width: w[t][i], height: "1.6", rx: ".8", fill: "currentColor" }, i)) });
448
+ return /* @__PURE__ */ jsx2("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx2(
449
+ "rect",
450
+ {
451
+ x: t === "right" ? 15 - w[t][i] - 1 : 1,
452
+ y: [2, 6.5, 11][i],
453
+ width: w[t][i],
454
+ height: "1.6",
455
+ rx: ".8",
456
+ fill: "currentColor"
457
+ },
458
+ i
459
+ )) });
314
460
  }
315
- function getCurrentCell() {
316
- var _a;
317
- const node = (_a = window.getSelection()) == null ? void 0 : _a.anchorNode;
318
- const el = (node == null ? void 0 : node.nodeType) === 1 ? node : node == null ? void 0 : node.parentElement;
319
- return el == null ? void 0 : el.closest("td, th");
461
+
462
+ // src/rte/ColorPicker.tsx
463
+ import { useState, useRef, useEffect, useCallback } from "react";
464
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
465
+ function CustomColorPicker({ initialColor, onApply, onBack }) {
466
+ const [hsv, setHsv] = useState(() => hex2hsv(initialColor || "#ff0000"));
467
+ const [hexInput, setHexInput] = useState(initialColor || "#ff0000");
468
+ const svRef = useRef(null);
469
+ const hueRef = useRef(null);
470
+ const svDrag = useRef(false);
471
+ const hueDrag = useRef(false);
472
+ const [h, s, v] = hsv;
473
+ const currentHex = rgb2hex(...hsv2rgb(h, s, v));
474
+ useEffect(() => {
475
+ const c = svRef.current;
476
+ if (!c) return;
477
+ const ctx = c.getContext("2d");
478
+ const W = c.width, H = c.height;
479
+ const [r, g, b] = hsv2rgb(h, 1, 1);
480
+ ctx.fillStyle = `rgb(${r},${g},${b})`;
481
+ ctx.fillRect(0, 0, W, H);
482
+ const wg = ctx.createLinearGradient(0, 0, W, 0);
483
+ wg.addColorStop(0, "rgba(255,255,255,1)");
484
+ wg.addColorStop(1, "rgba(255,255,255,0)");
485
+ ctx.fillStyle = wg;
486
+ ctx.fillRect(0, 0, W, H);
487
+ const bg = ctx.createLinearGradient(0, 0, 0, H);
488
+ bg.addColorStop(0, "rgba(0,0,0,0)");
489
+ bg.addColorStop(1, "rgba(0,0,0,1)");
490
+ ctx.fillStyle = bg;
491
+ ctx.fillRect(0, 0, W, H);
492
+ const cx = s * W, cy = (1 - v) * H;
493
+ ctx.beginPath();
494
+ ctx.arc(cx, cy, 7, 0, Math.PI * 2);
495
+ ctx.strokeStyle = "rgba(0,0,0,0.25)";
496
+ ctx.lineWidth = 2;
497
+ ctx.stroke();
498
+ ctx.beginPath();
499
+ ctx.arc(cx, cy, 6, 0, Math.PI * 2);
500
+ ctx.strokeStyle = "#fff";
501
+ ctx.lineWidth = 2;
502
+ ctx.stroke();
503
+ }, [h, s, v]);
504
+ useEffect(() => {
505
+ const c = hueRef.current;
506
+ if (!c) return;
507
+ const ctx = c.getContext("2d");
508
+ const W = c.width, H = c.height;
509
+ const grad = ctx.createLinearGradient(0, 0, W, 0);
510
+ for (let i = 0; i <= 360; i += 30) {
511
+ const [r, g, b] = hsv2rgb(i, 1, 1);
512
+ grad.addColorStop(i / 360, `rgb(${r},${g},${b})`);
513
+ }
514
+ ctx.fillStyle = grad;
515
+ ctx.fillRect(0, 0, W, H);
516
+ const tx = h / 360 * W;
517
+ ctx.beginPath();
518
+ ctx.roundRect(tx - 4, -1, 8, H + 2, 3);
519
+ ctx.fillStyle = "#fff";
520
+ ctx.fill();
521
+ ctx.strokeStyle = "rgba(0,0,0,0.3)";
522
+ ctx.lineWidth = 1;
523
+ ctx.stroke();
524
+ }, [h]);
525
+ const updateSV = useCallback((e) => {
526
+ const c = svRef.current;
527
+ if (!c) return;
528
+ const rect = c.getBoundingClientRect();
529
+ const ns = Math.max(0, Math.min(1, (e.clientX - rect.left) / c.width));
530
+ const nv = Math.max(0, Math.min(1, 1 - (e.clientY - rect.top) / c.height));
531
+ const next = [h, ns, nv];
532
+ setHsv(next);
533
+ setHexInput(rgb2hex(...hsv2rgb(...next)));
534
+ }, [h]);
535
+ const updateHue = useCallback((e) => {
536
+ const c = hueRef.current;
537
+ if (!c) return;
538
+ const rect = c.getBoundingClientRect();
539
+ const nh = Math.max(0, Math.min(360, (e.clientX - rect.left) / c.width * 360));
540
+ const next = [nh, s, v];
541
+ setHsv(next);
542
+ setHexInput(rgb2hex(...hsv2rgb(...next)));
543
+ }, [s, v]);
544
+ useEffect(() => {
545
+ const mm = (e) => {
546
+ if (svDrag.current) updateSV(e);
547
+ if (hueDrag.current) updateHue(e);
548
+ };
549
+ const mu = () => {
550
+ svDrag.current = false;
551
+ hueDrag.current = false;
552
+ };
553
+ window.addEventListener("mousemove", mm);
554
+ window.addEventListener("mouseup", mu);
555
+ return () => {
556
+ window.removeEventListener("mousemove", mm);
557
+ window.removeEventListener("mouseup", mu);
558
+ };
559
+ }, [updateSV, updateHue]);
560
+ return /* @__PURE__ */ jsxs2("div", { children: [
561
+ /* @__PURE__ */ jsxs2(
562
+ "button",
563
+ {
564
+ onMouseDown: onBack,
565
+ style: { background: "none", border: "none", cursor: "pointer", color: "#666", fontSize: 12, display: "flex", alignItems: "center", gap: 4, padding: "2px 4px", borderRadius: 3, marginBottom: 8 },
566
+ children: [
567
+ /* @__PURE__ */ jsx3("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx3("path", { d: "M8 2L4 6L8 10", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" }) }),
568
+ "Back"
569
+ ]
570
+ }
571
+ ),
572
+ /* @__PURE__ */ jsx3(
573
+ "canvas",
574
+ {
575
+ ref: svRef,
576
+ width: 180,
577
+ height: 150,
578
+ style: { display: "block", borderRadius: 4, cursor: "crosshair", marginBottom: 8 },
579
+ onMouseDown: (e) => {
580
+ svDrag.current = true;
581
+ updateSV(e);
582
+ }
583
+ }
584
+ ),
585
+ /* @__PURE__ */ jsx3(
586
+ "canvas",
587
+ {
588
+ ref: hueRef,
589
+ width: 180,
590
+ height: 14,
591
+ style: { display: "block", borderRadius: 4, cursor: "ew-resize", marginBottom: 10 },
592
+ onMouseDown: (e) => {
593
+ hueDrag.current = true;
594
+ updateHue(e);
595
+ }
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }, children: [
599
+ /* @__PURE__ */ jsx3("div", { style: { width: 28, height: 28, borderRadius: 4, background: currentHex, border: "1px solid rgba(0,0,0,0.15)", flexShrink: 0 } }),
600
+ /* @__PURE__ */ jsx3(
601
+ "input",
602
+ {
603
+ value: hexInput,
604
+ onChange: (e) => {
605
+ setHexInput(e.target.value);
606
+ if (/^#[0-9a-fA-F]{6}$/.test(e.target.value)) setHsv(hex2hsv(e.target.value));
607
+ },
608
+ style: { flex: 1, height: 28, border: "1px solid #ccc", borderRadius: 4, padding: "0 8px", fontSize: 12, fontFamily: "var(--font-mono)", outline: "none" },
609
+ spellCheck: false,
610
+ placeholder: "#000000"
611
+ }
612
+ )
613
+ ] }),
614
+ /* @__PURE__ */ jsx3(
615
+ "button",
616
+ {
617
+ onMouseDown: () => onApply(currentHex),
618
+ style: { width: "100%", height: 28, background: "#1a6fc4", color: "#fff", border: "none", borderRadius: 4, fontSize: 12, fontWeight: 600, cursor: "pointer" },
619
+ children: "Apply"
620
+ }
621
+ )
622
+ ] });
320
623
  }
321
- function getCurrentTable() {
322
- var _a;
323
- return (_a = getCurrentCell()) == null ? void 0 : _a.closest("table");
624
+ function ColorPicker({ color, onColor, onRemove, onClose }) {
625
+ const [custom, setCustom] = useState(false);
626
+ const pick = (c) => {
627
+ onColor(c);
628
+ onClose();
629
+ };
630
+ return /* @__PURE__ */ jsx3("div", { className: "rte-cpick", children: custom ? /* @__PURE__ */ jsx3(CustomColorPicker, { initialColor: color, onApply: pick, onBack: () => setCustom(false) }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
631
+ /* @__PURE__ */ jsx3("div", { className: "rte-cgrid", children: PALETTE.map((hex) => /* @__PURE__ */ jsx3(
632
+ "div",
633
+ {
634
+ className: `rte-ccell${color.toLowerCase() === hex.toLowerCase() ? " sel" : ""}`,
635
+ style: { background: hex },
636
+ title: hex,
637
+ onMouseDown: () => pick(hex)
638
+ },
639
+ hex
640
+ )) }),
641
+ /* @__PURE__ */ jsxs2("div", { className: "rte-cactions", children: [
642
+ /* @__PURE__ */ jsx3("div", { className: "rte-caction", title: "Black", onMouseDown: () => pick("#000000"), children: /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
643
+ /* @__PURE__ */ jsx3("rect", { x: "2", y: "2", width: "12", height: "12", rx: "2", fill: "#000" }),
644
+ /* @__PURE__ */ jsx3("path", { d: "M5 8L7 10L11 6", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
645
+ ] }) }),
646
+ /* @__PURE__ */ jsx3("div", { className: "rte-caction", title: "Remove color", onMouseDown: () => {
647
+ onRemove();
648
+ onClose();
649
+ }, children: /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
650
+ /* @__PURE__ */ jsx3("rect", { x: "2", y: "7.5", width: "12", height: "1.2", rx: ".6", fill: "#e74c3c", transform: "rotate(-30 8 8)" }),
651
+ /* @__PURE__ */ jsx3("rect", { x: "1", y: "11", width: "14", height: "2", rx: "1", fill: "#e74c3c" })
652
+ ] }) }),
653
+ /* @__PURE__ */ jsx3("div", { className: "rte-caction", title: "Custom color", onMouseDown: () => setCustom(true), children: /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
654
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "6", stroke: "#888", strokeWidth: "1.2", fill: "none" }),
655
+ /* @__PURE__ */ jsx3("circle", { cx: "5.5", cy: "6", r: "1.5", fill: "#e74c3c" }),
656
+ /* @__PURE__ */ jsx3("circle", { cx: "10.5", cy: "6", r: "1.5", fill: "#3498db" }),
657
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "10.5", r: "1.5", fill: "#2ecc71" })
658
+ ] }) })
659
+ ] })
660
+ ] }) });
324
661
  }
325
- function getCellCoords(table, cell) {
326
- const rows = Array.from(table.rows);
327
- for (let r = 0; r < rows.length; r++) {
328
- const cells = Array.from(rows[r].cells);
329
- for (let c = 0; c < cells.length; c++) {
330
- if (cells[c] === cell) return [r, c];
331
- }
332
- }
333
- return null;
662
+
663
+ // src/rte/TablePicker.tsx
664
+ import { useState as useState2 } from "react";
665
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
666
+ function TablePicker({ onInsert, onClose }) {
667
+ const [hover, setHover] = useState2([0, 0]);
668
+ return /* @__PURE__ */ jsxs3("div", { className: "rte-tpick", children: [
669
+ /* @__PURE__ */ jsx4("div", { className: "rte-tplbl", children: hover[0] > 0 ? `${hover[0]} \xD7 ${hover[1]}` : "Select table size" }),
670
+ /* @__PURE__ */ jsx4("div", { className: "rte-tpgrid", children: Array.from({ length: 64 }, (_, i) => {
671
+ const r = Math.floor(i / 8) + 1, c = i % 8 + 1;
672
+ return /* @__PURE__ */ jsx4(
673
+ "div",
674
+ {
675
+ className: `rte-tpcell${r <= hover[0] && c <= hover[1] ? " on" : ""}`,
676
+ onMouseEnter: () => setHover([r, c]),
677
+ onMouseDown: (e) => {
678
+ e.preventDefault();
679
+ if (hover[0] > 0) {
680
+ onInsert(hover[0], hover[1]);
681
+ onClose();
682
+ }
683
+ }
684
+ },
685
+ i
686
+ );
687
+ }) })
688
+ ] });
334
689
  }
335
- function getCellsInRange(table, a, b) {
336
- const ca = getCellCoords(table, a), cb = getCellCoords(table, b);
337
- if (!ca || !cb) return [a];
338
- const [r1, c1] = [Math.min(ca[0], cb[0]), Math.min(ca[1], cb[1])];
339
- const [r2, c2] = [Math.max(ca[0], cb[0]), Math.max(ca[1], cb[1])];
340
- const result = [];
341
- Array.from(table.rows).forEach((row, ri) => {
342
- if (ri >= r1 && ri <= r2) Array.from(row.cells).forEach((cell, ci) => {
343
- if (ci >= c1 && ci <= c2) result.push(cell);
344
- });
690
+
691
+ // src/rte/ImagePicker.tsx
692
+ import { useState as useState3, useRef as useRef2 } from "react";
693
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
694
+ function ImagePicker({ onInsert, onClose }) {
695
+ const [tab, setTab] = useState3("upload");
696
+ const [url, setUrl] = useState3("");
697
+ const [preview, setPreview] = useState3(null);
698
+ const [dragging, setDragging] = useState3(false);
699
+ const [urlError, setUrlError] = useState3(false);
700
+ const fileRef = useRef2(null);
701
+ const readFile = (file) => {
702
+ if (!file.type.startsWith("image/")) return;
703
+ const reader = new FileReader();
704
+ reader.onload = () => setPreview(reader.result);
705
+ reader.readAsDataURL(file);
706
+ };
707
+ const canInsert = tab === "upload" ? !!preview : !!url.trim() && !urlError;
708
+ return /* @__PURE__ */ jsxs4("div", { className: "rte-ipick", children: [
709
+ /* @__PURE__ */ jsx5("div", { className: "rte-itabs", children: ["upload", "url"].map((t) => /* @__PURE__ */ jsx5("button", { className: `rte-itab${tab === t ? " active" : ""}`, onMouseDown: () => setTab(t), children: t === "upload" ? "Upload / Drop" : "Image URL" }, t)) }),
710
+ /* @__PURE__ */ jsxs4("div", { className: "rte-ipbody", children: [
711
+ tab === "upload" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
712
+ /* @__PURE__ */ jsx5(
713
+ "div",
714
+ {
715
+ className: `rte-idrop${dragging ? " drag" : ""}`,
716
+ onDragOver: (e) => {
717
+ e.preventDefault();
718
+ setDragging(true);
719
+ },
720
+ onDragLeave: () => setDragging(false),
721
+ onDrop: (e) => {
722
+ e.preventDefault();
723
+ setDragging(false);
724
+ const f = e.dataTransfer.files[0];
725
+ if (f) readFile(f);
726
+ },
727
+ onMouseDown: () => {
728
+ var _a;
729
+ return (_a = fileRef.current) == null ? void 0 : _a.click();
730
+ },
731
+ children: preview ? /* @__PURE__ */ jsx5("img", { src: preview, alt: "preview", style: { maxWidth: "100%", maxHeight: 130, objectFit: "contain", borderRadius: 4, border: "1px solid #eee" } }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
732
+ /* @__PURE__ */ jsxs4("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", children: [
733
+ /* @__PURE__ */ jsx5("rect", { x: "1", y: "4", width: "26", height: "20", rx: "3", stroke: "#aaa", strokeWidth: "1.5" }),
734
+ /* @__PURE__ */ jsx5("circle", { cx: "9", cy: "10", r: "2.5", stroke: "#aaa", strokeWidth: "1.5" }),
735
+ /* @__PURE__ */ jsx5("path", { d: "M1 20L9 13L14 18L19 13.5L27 21", stroke: "#aaa", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
736
+ ] }),
737
+ /* @__PURE__ */ jsxs4("span", { style: { fontSize: 12, color: "#888" }, children: [
738
+ "Drop or ",
739
+ /* @__PURE__ */ jsx5("span", { style: { color: "#1a5fb4", textDecoration: "underline" }, children: "browse" })
740
+ ] }),
741
+ /* @__PURE__ */ jsx5("span", { style: { fontSize: 11, color: "#bbb" }, children: "PNG, JPG, GIF, WEBP" })
742
+ ] })
743
+ }
744
+ ),
745
+ /* @__PURE__ */ jsx5(
746
+ "input",
747
+ {
748
+ ref: fileRef,
749
+ type: "file",
750
+ accept: "image/*",
751
+ style: { display: "none" },
752
+ onChange: (e) => {
753
+ var _a;
754
+ const f = (_a = e.target.files) == null ? void 0 : _a[0];
755
+ if (f) readFile(f);
756
+ e.target.value = "";
757
+ }
758
+ }
759
+ ),
760
+ preview && /* @__PURE__ */ jsx5(
761
+ "button",
762
+ {
763
+ onMouseDown: () => setPreview(null),
764
+ style: { fontSize: 11, color: "#e74c3c", background: "none", border: "none", cursor: "pointer", padding: 0, alignSelf: "flex-start" },
765
+ children: "\u2715 Remove"
766
+ }
767
+ )
768
+ ] }),
769
+ tab === "url" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
770
+ /* @__PURE__ */ jsx5(
771
+ "input",
772
+ {
773
+ type: "text",
774
+ value: url,
775
+ onChange: (e) => {
776
+ setUrl(e.target.value);
777
+ setUrlError(false);
778
+ },
779
+ placeholder: "https://example.com/image.png",
780
+ style: { width: "100%", height: 30, border: `1px solid ${urlError ? "#e74c3c" : "#ccc"}`, borderRadius: 4, padding: "0 8px", fontSize: 12, outline: "none", fontFamily: "var(--font-sans)" }
781
+ }
782
+ ),
783
+ url && /* @__PURE__ */ jsx5(
784
+ "img",
785
+ {
786
+ src: url,
787
+ alt: "preview",
788
+ style: { maxWidth: "100%", maxHeight: 130, objectFit: "contain", borderRadius: 4, border: "1px solid #eee", display: "block" },
789
+ onError: () => setUrlError(true)
790
+ }
791
+ ),
792
+ urlError && /* @__PURE__ */ jsx5("span", { style: { fontSize: 11, color: "#e74c3c" }, children: "Could not load image." })
793
+ ] }),
794
+ /* @__PURE__ */ jsx5(
795
+ "button",
796
+ {
797
+ className: "rte-iinsert",
798
+ disabled: !canInsert,
799
+ onMouseDown: () => {
800
+ const src = tab === "url" ? url.trim() : preview;
801
+ if (src) {
802
+ onInsert(src);
803
+ onClose();
804
+ }
805
+ },
806
+ children: "Insert Image"
807
+ }
808
+ )
809
+ ] })
810
+ ] });
811
+ }
812
+
813
+ // src/rte/ImageEditor.tsx
814
+ import { useState as useState4, useRef as useRef3, useEffect as useEffect2, useCallback as useCallback2 } from "react";
815
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
816
+ var HANDLE_POS = {
817
+ nw: [0, 0],
818
+ n: [0, 50],
819
+ ne: [0, 100],
820
+ e: [50, 100],
821
+ se: [100, 100],
822
+ s: [100, 50],
823
+ sw: [100, 0],
824
+ w: [50, 0]
825
+ };
826
+ var HANDLE_CURSOR = {
827
+ nw: "nw-resize",
828
+ n: "n-resize",
829
+ ne: "ne-resize",
830
+ e: "e-resize",
831
+ se: "se-resize",
832
+ s: "s-resize",
833
+ sw: "sw-resize",
834
+ w: "w-resize"
835
+ };
836
+ function readAlign(img) {
837
+ if (img.style.float === "right") return "right";
838
+ if (img.style.float === "left") return "left";
839
+ const ml = img.style.marginLeft, mr = img.style.marginRight;
840
+ if (ml === "auto" && (mr === "0px" || mr === "0")) return "right";
841
+ if (ml === "auto" && mr === "auto") return "center";
842
+ return "left";
843
+ }
844
+ function ImageEditor({ img, containerRef, onClose, onDelete, onChange }) {
845
+ var _a;
846
+ const [overlay, setOverlay] = useState4({ top: 0, left: 0, width: 0, height: 0 });
847
+ const [activeTab, setActiveTab] = useState4("style");
848
+ const [dispMode, setDispMode] = useState4(img.style.display === "inline" ? "inline" : "block");
849
+ const [align, setAlign] = useState4(() => readAlign(img));
850
+ const [widthVal, setWidthVal] = useState4(img.style.width || `${img.offsetWidth}px`);
851
+ const [altVal, setAltVal] = useState4(img.alt || "");
852
+ const [captionTxt, setCaptionTxt] = useState4(() => {
853
+ var _a2, _b;
854
+ return ((_b = (_a2 = img.closest("figure")) == null ? void 0 : _a2.querySelector("figcaption")) == null ? void 0 : _b.textContent) || "";
345
855
  });
346
- return result;
856
+ const [showCaption, setShowCaption] = useState4(() => !!img.closest("figure"));
857
+ const [linkVal, setLinkVal] = useState4(() => {
858
+ var _a2;
859
+ return ((_a2 = img.closest("a")) == null ? void 0 : _a2.href) || "";
860
+ });
861
+ const aspectRef = useRef3(img.naturalWidth && img.naturalHeight ? img.naturalWidth / img.naturalHeight : img.offsetWidth / Math.max(img.offsetHeight, 1));
862
+ const updateOverlay = useCallback2(() => {
863
+ if (!containerRef.current) return;
864
+ const r = img.getBoundingClientRect(), cr = containerRef.current.getBoundingClientRect();
865
+ setOverlay({ top: r.top - cr.top, left: r.left - cr.left, width: r.width, height: r.height });
866
+ }, [img, containerRef]);
867
+ useEffect2(() => {
868
+ updateOverlay();
869
+ window.addEventListener("resize", updateOverlay);
870
+ return () => window.removeEventListener("resize", updateOverlay);
871
+ }, [updateOverlay]);
872
+ const startResize = useCallback2((e, handle) => {
873
+ e.preventDefault();
874
+ e.stopPropagation();
875
+ const sx = e.clientX, sy = e.clientY, sw = img.offsetWidth, sh = img.offsetHeight;
876
+ const isCorner = handle.length === 2;
877
+ const onMove = (ev) => {
878
+ const dx = ev.clientX - sx, dy = ev.clientY - sy;
879
+ let nw = sw;
880
+ if (handle.includes("e")) nw = Math.max(50, sw + dx);
881
+ if (handle.includes("w")) nw = Math.max(50, sw - dx);
882
+ if (isCorner) {
883
+ img.style.width = `${Math.round(nw)}px`;
884
+ img.style.height = `${Math.round(nw / aspectRef.current)}px`;
885
+ } else {
886
+ if (handle.includes("e") || handle.includes("w")) {
887
+ img.style.width = `${Math.round(nw)}px`;
888
+ img.style.height = "auto";
889
+ } else {
890
+ const nh = handle === "s" ? Math.max(30, sh + dy) : Math.max(30, sh - dy);
891
+ img.style.height = `${Math.round(nh)}px`;
892
+ }
893
+ }
894
+ setWidthVal(`${Math.round(img.offsetWidth)}px`);
895
+ updateOverlay();
896
+ };
897
+ const onUp = () => {
898
+ window.removeEventListener("mousemove", onMove);
899
+ window.removeEventListener("mouseup", onUp);
900
+ onChange();
901
+ };
902
+ window.addEventListener("mousemove", onMove);
903
+ window.addEventListener("mouseup", onUp);
904
+ }, [img, onChange, updateOverlay]);
905
+ const applyWidth = (w) => {
906
+ img.style.width = w;
907
+ img.style.height = "auto";
908
+ setWidthVal(w);
909
+ updateOverlay();
910
+ onChange();
911
+ };
912
+ const applyAlign = (a) => {
913
+ setAlign(a);
914
+ if (dispMode === "block") {
915
+ img.style.float = "none";
916
+ img.style.marginLeft = a === "center" || a === "right" ? "auto" : "0";
917
+ img.style.marginRight = a === "center" || a === "left" ? "auto" : "0";
918
+ } else {
919
+ img.style.float = a === "left" ? "left" : a === "right" ? "right" : "none";
920
+ img.style.marginLeft = "0";
921
+ img.style.marginRight = "0";
922
+ }
923
+ onChange();
924
+ };
925
+ const applyDisplay = (m) => {
926
+ setDispMode(m);
927
+ img.style.display = m;
928
+ img.style.float = "none";
929
+ img.style.marginLeft = "0";
930
+ img.style.marginRight = "auto";
931
+ setAlign("left");
932
+ onChange();
933
+ };
934
+ const applyAlt = (v) => {
935
+ setAltVal(v);
936
+ img.alt = v;
937
+ onChange();
938
+ };
939
+ const toggleCaption = () => {
940
+ var _a2, _b;
941
+ const fig = img.closest("figure");
942
+ if (fig) {
943
+ (_a2 = fig.parentNode) == null ? void 0 : _a2.insertBefore(img, fig);
944
+ fig.remove();
945
+ setShowCaption(false);
946
+ setCaptionTxt("");
947
+ } else {
948
+ const figure = document.createElement("figure");
949
+ figure.style.cssText = "margin:0.8em 0;display:block;overflow:hidden;";
950
+ const figcap = document.createElement("figcaption");
951
+ figcap.contentEditable = "true";
952
+ figcap.style.cssText = "font-size:13px;color:#666;text-align:center;padding:4px 0;outline:none;clear:both;display:block;";
953
+ figcap.textContent = captionTxt || "Caption";
954
+ (_b = img.parentNode) == null ? void 0 : _b.insertBefore(figure, img);
955
+ figure.appendChild(img);
956
+ figure.appendChild(figcap);
957
+ setShowCaption(true);
958
+ }
959
+ updateOverlay();
960
+ onChange();
961
+ };
962
+ const updateCaption = (v) => {
963
+ var _a2;
964
+ setCaptionTxt(v);
965
+ const fc = (_a2 = img.closest("figure")) == null ? void 0 : _a2.querySelector("figcaption");
966
+ if (fc) {
967
+ fc.textContent = v;
968
+ onChange();
969
+ }
970
+ };
971
+ const applyLink = (href) => {
972
+ var _a2, _b;
973
+ const existing = img.closest("a");
974
+ if (!href.trim()) {
975
+ if (existing) {
976
+ (_a2 = existing.parentNode) == null ? void 0 : _a2.insertBefore(img, existing);
977
+ existing.remove();
978
+ }
979
+ } else if (existing) {
980
+ existing.href = href;
981
+ } else {
982
+ const a = document.createElement("a");
983
+ a.href = href;
984
+ a.target = "_blank";
985
+ a.rel = "noopener";
986
+ (_b = img.parentNode) == null ? void 0 : _b.insertBefore(a, img);
987
+ a.appendChild(img);
988
+ }
989
+ onChange();
990
+ };
991
+ const PANEL_W = 218;
992
+ const panelRight = overlay.left + overlay.width + 8;
993
+ const containerW = ((_a = containerRef.current) == null ? void 0 : _a.clientWidth) || 600;
994
+ const panelLeft = panelRight + PANEL_W < containerW ? panelRight : Math.max(4, overlay.left + overlay.width - PANEL_W);
995
+ const panelTop = panelRight + PANEL_W < containerW ? overlay.top : overlay.top + overlay.height + 10;
996
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
997
+ /* @__PURE__ */ jsxs5("div", { style: { position: "absolute", top: overlay.top, left: overlay.left, width: overlay.width, height: overlay.height, pointerEvents: "none", zIndex: 51 }, children: [
998
+ /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, border: "2px solid #1a5fb4", borderRadius: 2, boxSizing: "border-box" } }),
999
+ Object.keys(HANDLE_POS).map((h) => {
1000
+ const [tp, lp] = HANDLE_POS[h];
1001
+ return /* @__PURE__ */ jsx6(
1002
+ "div",
1003
+ {
1004
+ className: "rte-handle",
1005
+ style: { top: `calc(${tp}% - 5px)`, left: `calc(${lp}% - 5px)`, cursor: HANDLE_CURSOR[h] },
1006
+ onMouseDown: (e) => startResize(e, h)
1007
+ },
1008
+ h
1009
+ );
1010
+ })
1011
+ ] }),
1012
+ /* @__PURE__ */ jsxs5("div", { className: "rte-ie-panel", style: { top: panelTop, left: panelLeft }, children: [
1013
+ /* @__PURE__ */ jsx6(
1014
+ "button",
1015
+ {
1016
+ onMouseDown: onClose,
1017
+ style: { position: "absolute", top: 4, right: 6, background: "none", border: "none", cursor: "pointer", color: "#999", fontSize: 13, lineHeight: 1 },
1018
+ children: "\u2715"
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ jsx6("div", { className: "rte-ie-tabs", children: ["style", "caption", "link"].map((t) => /* @__PURE__ */ jsx6("button", { className: `rte-ie-tab${activeTab === t ? " active" : ""}`, onMouseDown: () => setActiveTab(t), children: t === "style" ? "Style" : t === "caption" ? "Caption" : "Link" }, t)) }),
1022
+ /* @__PURE__ */ jsxs5("div", { className: "rte-ie-body", children: [
1023
+ activeTab === "style" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1024
+ /* @__PURE__ */ jsxs5("div", { children: [
1025
+ /* @__PURE__ */ jsx6("div", { className: "rte-ie-label", children: "Display" }),
1026
+ /* @__PURE__ */ jsx6("div", { className: "rte-ie-row", children: ["block", "inline"].map((m) => /* @__PURE__ */ jsx6("button", { className: `rte-ie-seg-btn${dispMode === m ? " active" : ""}`, onMouseDown: () => applyDisplay(m), children: m === "block" ? "\u2B1B Block" : "\u25AC Inline" }, m)) })
1027
+ ] }),
1028
+ /* @__PURE__ */ jsxs5("div", { children: [
1029
+ /* @__PURE__ */ jsx6("div", { className: "rte-ie-label", children: "Alignment" }),
1030
+ /* @__PURE__ */ jsx6("div", { className: "rte-ie-row", children: ["left", "center", "right"].map((a) => /* @__PURE__ */ jsx6("button", { className: `rte-ie-seg-btn${align === a ? " active" : ""}`, onMouseDown: () => applyAlign(a), children: /* @__PURE__ */ jsx6(AlignIco, { t: a }) }, a)) })
1031
+ ] }),
1032
+ /* @__PURE__ */ jsxs5("div", { children: [
1033
+ /* @__PURE__ */ jsx6("div", { className: "rte-ie-label", children: "Width" }),
1034
+ /* @__PURE__ */ jsxs5("div", { className: "rte-ie-row", style: { marginBottom: 4 }, children: [
1035
+ [25, 50, 75, 100].map((p) => /* @__PURE__ */ jsxs5("button", { className: "rte-ie-preset", onMouseDown: () => applyWidth(`${p}%`), children: [
1036
+ p,
1037
+ "%"
1038
+ ] }, p)),
1039
+ /* @__PURE__ */ jsx6("button", { className: "rte-ie-preset", title: "Natural size", onMouseDown: () => applyWidth(`${img.naturalWidth || img.offsetWidth}px`), children: "Orig" })
1040
+ ] }),
1041
+ /* @__PURE__ */ jsx6(
1042
+ "input",
1043
+ {
1044
+ className: "rte-ie-input",
1045
+ value: widthVal,
1046
+ onChange: (e) => setWidthVal(e.target.value),
1047
+ onBlur: (e) => applyWidth(e.target.value),
1048
+ onKeyDown: (e) => e.key === "Enter" && applyWidth(widthVal),
1049
+ placeholder: "e.g. 300px or 50%"
1050
+ }
1051
+ )
1052
+ ] }),
1053
+ /* @__PURE__ */ jsxs5("div", { children: [
1054
+ /* @__PURE__ */ jsx6("div", { className: "rte-ie-label", children: "Alt text" }),
1055
+ /* @__PURE__ */ jsx6("input", { className: "rte-ie-input", value: altVal, onChange: (e) => applyAlt(e.target.value), placeholder: "Describe the image" })
1056
+ ] }),
1057
+ /* @__PURE__ */ jsx6("button", { className: "rte-ie-delete", onMouseDown: onDelete, children: "Delete image" })
1058
+ ] }),
1059
+ activeTab === "caption" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1060
+ /* @__PURE__ */ jsxs5("p", { style: { fontSize: 11, color: "#777", margin: 0, lineHeight: 1.5, fontFamily: "var(--font-sans)" }, children: [
1061
+ "Wraps image in ",
1062
+ /* @__PURE__ */ jsx6("code", { children: "<figure>" }),
1063
+ " with ",
1064
+ /* @__PURE__ */ jsx6("code", { children: "<figcaption>" }),
1065
+ "."
1066
+ ] }),
1067
+ /* @__PURE__ */ jsxs5("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12, cursor: "pointer", fontFamily: "var(--font-sans)" }, children: [
1068
+ /* @__PURE__ */ jsx6("input", { type: "checkbox", checked: showCaption, onChange: toggleCaption }),
1069
+ " Show caption"
1070
+ ] }),
1071
+ showCaption && /* @__PURE__ */ jsx6("input", { className: "rte-ie-input", value: captionTxt, onChange: (e) => updateCaption(e.target.value), placeholder: "Caption text" })
1072
+ ] }),
1073
+ activeTab === "link" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1074
+ /* @__PURE__ */ jsx6("div", { className: "rte-ie-label", children: "Wrap image in a hyperlink" }),
1075
+ /* @__PURE__ */ jsx6("input", { className: "rte-ie-input", value: linkVal, onChange: (e) => setLinkVal(e.target.value), placeholder: "https://example.com" }),
1076
+ /* @__PURE__ */ jsxs5("div", { className: "rte-ie-row", children: [
1077
+ /* @__PURE__ */ jsx6("button", { className: "rte-ie-apply", onMouseDown: () => applyLink(linkVal), children: "Apply" }),
1078
+ img.closest("a") && /* @__PURE__ */ jsx6("button", { className: "rte-ie-remove", onMouseDown: () => {
1079
+ setLinkVal("");
1080
+ applyLink("");
1081
+ }, children: "Remove" })
1082
+ ] })
1083
+ ] })
1084
+ ] })
1085
+ ] })
1086
+ ] });
347
1087
  }
1088
+
1089
+ // src/RichTextEditor.tsx
1090
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
348
1091
  function RichTextEditor({ value, onChange }) {
349
1092
  var _a;
350
- const editorRef = useRef(null);
351
- const editorAreaRef = useRef(null);
352
- const colorRef = useRef(null);
353
- const savedRangeRef = useRef(null);
354
- const dragStartCell = useRef(null);
355
- const isDragging = useRef(false);
356
- const [isCode, setIsCode] = useState(false);
357
- const [codeVal, setCodeVal] = useState("");
358
- const [fmt, setFmt] = useState({ block: "p" });
359
- const [color, setColor] = useState("#e74c3c");
360
- const [words, setWords] = useState(0);
361
- const [linkBar, setLinkBar] = useState(false);
362
- const [linkUrl, setLinkUrl] = useState("https://");
363
- const [showTable, setShowTable] = useState(false);
364
- const [selCells, setSelCells] = useState([]);
365
- const [tableFP, setTableFP] = useState(null);
366
- const [linkFP, setLinkFP] = useState(null);
367
- const calcFloat = useCallback((el, toolbarW) => {
1093
+ const editorRef = useRef4(null);
1094
+ const editorAreaRef = useRef4(null);
1095
+ const savedRangeRef = useRef4(null);
1096
+ const dragStartCell = useRef4(null);
1097
+ const isDragging = useRef4(false);
1098
+ const [isCode, setIsCode] = useState5(false);
1099
+ const [codeVal, setCodeVal] = useState5("");
1100
+ const [fmt, setFmt] = useState5({ block: "p" });
1101
+ const [color, setColor] = useState5("#000000");
1102
+ const [words, setWords] = useState5(0);
1103
+ const [linkBar, setLinkBar] = useState5(false);
1104
+ const [linkUrl, setLinkUrl] = useState5("https://");
1105
+ const [showTable, setShowTable] = useState5(false);
1106
+ const [showColor, setShowColor] = useState5(false);
1107
+ const [showImagePicker, setShowImagePicker] = useState5(false);
1108
+ const [selCells, setSelCells] = useState5([]);
1109
+ const [tableFP, setTableFP] = useState5(null);
1110
+ const [linkFP, setLinkFP] = useState5(null);
1111
+ const [linkInfoFP, setLinkInfoFP] = useState5(null);
1112
+ const [selectedImg, setSelectedImg] = useState5(null);
1113
+ const calcFloat = useCallback3((el, w) => {
368
1114
  const area = editorAreaRef.current;
369
- const er = el.getBoundingClientRect();
370
- const ar = area.getBoundingClientRect();
371
- const arrowCenter = er.left + er.width / 2 - ar.left;
372
- const rawLeft = arrowCenter - toolbarW / 2;
373
- const left = Math.max(4, Math.min(rawLeft, ar.width - toolbarW - 4));
374
- const arrowLeft = Math.max(8, Math.min(arrowCenter - left - 7, toolbarW - 22));
375
- return { top: er.bottom - ar.top + 10, left, arrowLeft };
1115
+ const er = el.getBoundingClientRect(), ar = area.getBoundingClientRect();
1116
+ const ac = er.left + er.width / 2 - ar.left;
1117
+ const rawL = ac - w / 2;
1118
+ const left = Math.max(4, Math.min(rawL, ar.width - w - 4));
1119
+ return { top: er.bottom - ar.top + 10, left, arrowLeft: Math.max(8, Math.min(ac - left - 7, w - 22)) };
376
1120
  }, []);
377
- const applySelection = useCallback((cells) => {
1121
+ const applySelection = useCallback3((cells) => {
378
1122
  var _a2;
379
1123
  (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll(".rte-sel").forEach((c) => c.classList.remove("rte-sel"));
380
1124
  cells.forEach((c) => c.classList.add("rte-sel"));
381
1125
  setSelCells(cells);
382
1126
  }, []);
383
- const clearSelection = useCallback(() => {
1127
+ const clearSelection = useCallback3(() => {
384
1128
  var _a2;
385
1129
  (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll(".rte-sel").forEach((c) => c.classList.remove("rte-sel"));
386
1130
  setSelCells([]);
387
1131
  dragStartCell.current = null;
388
1132
  }, []);
389
- const fixListNesting = useCallback(() => {
390
- const editor = editorRef.current;
391
- if (!editor) return;
392
- editor.querySelectorAll("ol > ol, ol > ul, ul > ol, ul > ul").forEach((orphan) => {
393
- const prev = orphan.previousElementSibling;
394
- if (prev && prev.tagName === "LI") {
395
- prev.appendChild(orphan);
396
- }
1133
+ const clearImageSel = useCallback3(() => setSelectedImg(null), []);
1134
+ const fixFontTags = useCallback3(() => {
1135
+ var _a2;
1136
+ (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll("font[color]").forEach((font) => {
1137
+ var _a3;
1138
+ const span = document.createElement("span");
1139
+ span.style.color = (_a3 = font.getAttribute("color")) != null ? _a3 : "";
1140
+ while (font.firstChild) span.appendChild(font.firstChild);
1141
+ font.replaceWith(span);
397
1142
  });
398
1143
  }, []);
399
- const exec = useCallback((cmd, val = null) => {
1144
+ const fixListNesting = useCallback3(() => {
400
1145
  var _a2;
401
- (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
402
- document.execCommand(cmd, false, val != null ? val : void 0);
403
- fixListNesting();
404
- refresh();
405
- }, [fixListNesting]);
406
- const refresh = useCallback(() => {
1146
+ (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll("ol > ol, ol > ul, ul > ol, ul > ul").forEach((orphan) => {
1147
+ const prev = orphan.previousElementSibling;
1148
+ if ((prev == null ? void 0 : prev.tagName) === "LI") prev.appendChild(orphan);
1149
+ });
1150
+ }, []);
1151
+ const refresh = useCallback3(() => {
407
1152
  var _a2, _b, _c, _d;
408
1153
  const raw = document.queryCommandValue("formatBlock").toLowerCase().replace(/[<>]/g, "");
409
1154
  const block = ["h1", "h2", "h3"].includes(raw) ? raw : "p";
410
- const c = document.queryCommandValue("foreColor");
411
- if (c && c !== "false") {
412
- const m = c.match(/\d+/g);
413
- if (m) setColor("#" + m.slice(0, 3).map((n) => parseInt(n).toString(16).padStart(2, "0")).join(""));
414
- }
1155
+ setColor(getColorAtCursor(editorRef.current));
415
1156
  const cell = getCurrentCell();
416
1157
  const inTable = !!cell;
417
1158
  const cellMerged = cell ? cell.colSpan > 1 || cell.rowSpan > 1 : false;
1159
+ const anchor = getCurrentLink();
1160
+ const inLink = !!anchor;
418
1161
  setFmt({
419
1162
  bold: document.queryCommandState("bold"),
420
1163
  italic: document.queryCommandState("italic"),
@@ -427,15 +1170,39 @@ function RichTextEditor({ value, onChange }) {
427
1170
  aJ: document.queryCommandState("justifyFull"),
428
1171
  block,
429
1172
  inTable,
430
- cellMerged
1173
+ cellMerged,
1174
+ inLink,
1175
+ linkHref: anchor == null ? void 0 : anchor.href
431
1176
  });
432
1177
  if (cell && editorAreaRef.current) setTableFP(calcFloat(cell, 370));
433
1178
  else setTableFP(null);
1179
+ if (anchor && editorAreaRef.current && !linkBar) setLinkInfoFP(calcFloat(anchor, 290));
1180
+ else setLinkInfoFP(null);
434
1181
  const txt = (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerText) != null ? _b : "";
435
1182
  setWords(txt.trim() ? txt.trim().split(/\s+/).length : 0);
436
1183
  onChange == null ? void 0 : onChange((_d = (_c = editorRef.current) == null ? void 0 : _c.innerHTML) != null ? _d : "");
437
- }, [onChange, calcFloat]);
438
- const handleEditorMouseDown = useCallback((e) => {
1184
+ }, [onChange, calcFloat, linkBar]);
1185
+ const exec = useCallback3((cmd, val = null) => {
1186
+ var _a2;
1187
+ (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
1188
+ document.execCommand(cmd, false, val != null ? val : void 0);
1189
+ fixFontTags();
1190
+ fixListNesting();
1191
+ refresh();
1192
+ }, [fixFontTags, fixListNesting, refresh]);
1193
+ const insertImage = useCallback3((src) => {
1194
+ var _a2;
1195
+ (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
1196
+ document.execCommand("insertHTML", false, `<img src="${src}" alt="" style="max-width:100%;height:auto;display:block;" />`);
1197
+ refresh();
1198
+ }, [refresh]);
1199
+ const handleEditorMouseDown = useCallback3((e) => {
1200
+ if (e.target.tagName === "IMG") {
1201
+ e.preventDefault();
1202
+ setSelectedImg(e.target);
1203
+ return;
1204
+ }
1205
+ clearImageSel();
439
1206
  const target = e.target.closest("td, th");
440
1207
  if (!target) {
441
1208
  clearSelection();
@@ -452,8 +1219,8 @@ function RichTextEditor({ value, onChange }) {
452
1219
  dragStartCell.current = target;
453
1220
  isDragging.current = true;
454
1221
  applySelection([target]);
455
- }, [applySelection, clearSelection]);
456
- const handleEditorMouseMove = useCallback((e) => {
1222
+ }, [applySelection, clearSelection, clearImageSel]);
1223
+ const handleEditorMouseMove = useCallback3((e) => {
457
1224
  if (!isDragging.current || !dragStartCell.current) return;
458
1225
  const target = e.target.closest("td, th");
459
1226
  if (!target || target === dragStartCell.current) return;
@@ -465,12 +1232,10 @@ function RichTextEditor({ value, onChange }) {
465
1232
  applySelection(cells);
466
1233
  }
467
1234
  }, [applySelection]);
468
- const handleEditorMouseUp = useCallback(() => {
469
- isDragging.current = false;
470
- }, []);
471
- const handleKeyDown = useCallback((e) => {
1235
+ const handleKeyDown = useCallback3((e) => {
472
1236
  var _a2, _b;
473
1237
  clearSelection();
1238
+ clearImageSel();
474
1239
  if (e.key === "Tab") {
475
1240
  e.preventDefault();
476
1241
  const cell = getCurrentCell();
@@ -502,21 +1267,21 @@ function RichTextEditor({ value, onChange }) {
502
1267
  exec("underline");
503
1268
  }
504
1269
  }
505
- }, [exec, clearSelection]);
1270
+ }, [exec, clearSelection, clearImageSel]);
506
1271
  const insertTable = (rows, cols) => {
507
1272
  var _a2;
508
1273
  (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
509
- let html = `<table>`;
1274
+ let html = "<table>";
510
1275
  for (let r = 0; r < rows; r++) {
511
1276
  html += "<tr>";
512
- for (let c = 0; c < cols; c++) html += r === 0 ? `<th><br></th>` : `<td><br></td>`;
1277
+ for (let c = 0; c < cols; c++) html += r === 0 ? "<th><br></th>" : "<td><br></td>";
513
1278
  html += "</tr>";
514
1279
  }
515
1280
  html += "</table><p><br></p>";
516
1281
  document.execCommand("insertHTML", false, html);
517
1282
  refresh();
518
1283
  };
519
- const tableOp = useCallback((op) => {
1284
+ const tableOp = useCallback3((op) => {
520
1285
  const cell = getCurrentCell(), table = getCurrentTable();
521
1286
  if (!cell || !table) return;
522
1287
  const row = cell.closest("tr");
@@ -554,7 +1319,7 @@ function RichTextEditor({ value, onChange }) {
554
1319
  }
555
1320
  refresh();
556
1321
  }, [clearSelection, refresh]);
557
- const doMerge = useCallback((cells) => {
1322
+ const doMerge = useCallback3((cells) => {
558
1323
  var _a2;
559
1324
  if (cells.length < 2) return;
560
1325
  const table = cells[0].closest("table");
@@ -572,12 +1337,13 @@ function RichTextEditor({ value, onChange }) {
572
1337
  clearSelection();
573
1338
  refresh();
574
1339
  }, [clearSelection, refresh]);
575
- const doSplit = useCallback(() => {
1340
+ const doSplit = useCallback3(() => {
576
1341
  const cell = getCurrentCell();
577
1342
  if (!cell || cell.colSpan === 1 && cell.rowSpan === 1) return;
578
1343
  const cs = cell.colSpan, rs = cell.rowSpan;
579
1344
  const table = cell.closest("table");
580
- const rows = Array.from(table.rows), row = cell.closest("tr");
1345
+ const rows = Array.from(table.rows);
1346
+ const row = cell.closest("tr");
581
1347
  const ri = rows.indexOf(row), ci = Array.from(row.cells).indexOf(cell);
582
1348
  cell.colSpan = 1;
583
1349
  cell.rowSpan = 1;
@@ -604,11 +1370,9 @@ function RichTextEditor({ value, onChange }) {
604
1370
  savedRangeRef.current = (sel == null ? void 0 : sel.rangeCount) ? sel.getRangeAt(0).cloneRange() : null;
605
1371
  if ((sel == null ? void 0 : sel.rangeCount) && editorAreaRef.current) {
606
1372
  const rect = sel.getRangeAt(0).getBoundingClientRect();
607
- if (rect.width > 0 || rect.height > 0) setLinkFP(calcFloat({ getBoundingClientRect: () => rect }, 360));
608
- else {
609
- const area = editorAreaRef.current.getBoundingClientRect();
610
- setLinkFP({ top: 60, left: 20, arrowLeft: 20 });
611
- }
1373
+ setLinkFP(
1374
+ rect.width > 0 || rect.height > 0 ? calcFloat({ getBoundingClientRect: () => rect }, 360) : { top: 60, left: 20, arrowLeft: 20 }
1375
+ );
612
1376
  }
613
1377
  setLinkUrl("https://");
614
1378
  setLinkBar(true);
@@ -622,8 +1386,9 @@ function RichTextEditor({ value, onChange }) {
622
1386
  sel.removeAllRanges();
623
1387
  sel.addRange(savedRangeRef.current);
624
1388
  }
625
- if ((_b = savedRangeRef.current) == null ? void 0 : _b.toString()) document.execCommand("createLink", false, linkUrl);
626
- else if (savedRangeRef.current) {
1389
+ if ((_b = savedRangeRef.current) == null ? void 0 : _b.toString()) {
1390
+ document.execCommand("createLink", false, linkUrl);
1391
+ } else if (savedRangeRef.current) {
627
1392
  const a = document.createElement("a");
628
1393
  a.href = linkUrl;
629
1394
  a.textContent = linkUrl;
@@ -634,70 +1399,11 @@ function RichTextEditor({ value, onChange }) {
634
1399
  setLinkFP(null);
635
1400
  refresh();
636
1401
  };
637
- const prettifyHtml = (html) => {
638
- const INLINE = /* @__PURE__ */ new Set(["a", "b", "i", "u", "em", "strong", "span", "code", "br", "small", "sub", "sup"]);
639
- let indent = 0;
640
- const pad = () => " ".repeat(indent);
641
- const tokens = html.replace(/>\s+</g, "><").replace(/(<\/?[^>]+>)/g, "\0$1\0").split("\0").filter(Boolean);
642
- const lines = [];
643
- let inline = "";
644
- const flush = () => {
645
- if (inline.trim()) {
646
- lines.push(pad() + inline.trim());
647
- inline = "";
648
- }
649
- };
650
- for (const tok of tokens) {
651
- const ot = tok.match(/^<([a-zA-Z][a-zA-Z0-9]*)[^>]*>$/), ct = tok.match(/^<\/([a-zA-Z][a-zA-Z0-9]*)>$/);
652
- if (tok.match(/^<[^>]+\/>$/)) {
653
- flush();
654
- lines.push(pad() + tok);
655
- continue;
656
- }
657
- if (ot) {
658
- const tag = ot[1].toLowerCase();
659
- if (INLINE.has(tag)) {
660
- inline += tok;
661
- continue;
662
- }
663
- flush();
664
- lines.push(pad() + tok);
665
- indent++;
666
- continue;
667
- }
668
- if (ct) {
669
- const tag = ct[1].toLowerCase();
670
- if (INLINE.has(tag)) {
671
- inline += tok;
672
- continue;
673
- }
674
- flush();
675
- indent = Math.max(0, indent - 1);
676
- const prev = lines[lines.length - 1];
677
- prev && prev.trim().startsWith("<") && !prev.includes("</") ? lines[lines.length - 1] = prev + tok : lines.push(pad() + tok);
678
- continue;
679
- }
680
- inline += tok;
681
- }
682
- flush();
683
- return lines.join("\n");
684
- };
685
- const toCode = () => {
686
- var _a2, _b;
687
- setCodeVal(prettifyHtml((_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b : ""));
688
- setIsCode(true);
689
- };
690
- const toVisual = () => {
691
- if (editorRef.current) editorRef.current.innerHTML = codeVal;
692
- setIsCode(false);
693
- refresh();
694
- };
695
1402
  const insertCodeBlock = () => {
696
1403
  const sel = window.getSelection();
697
1404
  if (!(sel == null ? void 0 : sel.rangeCount)) return;
698
1405
  const range = sel.getRangeAt(0);
699
- const pre = document.createElement("pre");
700
- const code = document.createElement("code");
1406
+ const pre = document.createElement("pre"), code = document.createElement("code");
701
1407
  code.textContent = range.toString() || "// code here";
702
1408
  pre.appendChild(code);
703
1409
  range.deleteContents();
@@ -710,55 +1416,72 @@ function RichTextEditor({ value, onChange }) {
710
1416
  sel.addRange(range);
711
1417
  refresh();
712
1418
  };
1419
+ const toCode = () => {
1420
+ var _a2, _b;
1421
+ setCodeVal(prettifyHtml((_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b : ""));
1422
+ setIsCode(true);
1423
+ };
1424
+ const toVisual = () => {
1425
+ if (editorRef.current) editorRef.current.innerHTML = codeVal;
1426
+ setIsCode(false);
1427
+ refresh();
1428
+ };
713
1429
  const copyHtml = () => {
714
1430
  var _a2, _b;
715
- navigator.clipboard.writeText(isCode ? codeVal : (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b : "").catch(() => {
1431
+ return navigator.clipboard.writeText(isCode ? codeVal : (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b : "").catch(() => {
716
1432
  });
717
1433
  };
718
- useEffect(() => {
1434
+ useEffect3(() => {
719
1435
  if (editorRef.current) {
720
1436
  editorRef.current.innerHTML = value != null ? value : "<p>Start writing here...</p>";
721
1437
  refresh();
722
1438
  }
723
1439
  }, []);
724
- useEffect(() => {
725
- if (!showTable) return;
726
- const h = () => setShowTable(false);
727
- document.addEventListener("mousedown", h);
728
- return () => document.removeEventListener("mousedown", h);
729
- }, [showTable]);
730
1440
  const canMerge = selCells.length >= 2;
731
1441
  const canSplit = fmt.cellMerged;
732
- return /* @__PURE__ */ jsxs("div", { style: { padding: "1rem 0" }, children: [
733
- /* @__PURE__ */ jsx("style", { children: CSS }),
734
- /* @__PURE__ */ jsxs("div", { style: { border: "1px solid #d8d8d8", borderRadius: 4, boxShadow: "0 1px 3px rgba(0,0,0,0.06)" }, children: [
735
- /* @__PURE__ */ jsxs("div", { className: "rte-toolbar", children: [
736
- !isCode && /* @__PURE__ */ jsxs(Fragment, { children: [
737
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("bold"), title: "Bold (Ctrl+B)", active: fmt.bold, style: { fontWeight: 800, fontFamily: "Georgia,serif" }, children: "B" }),
738
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("italic"), title: "Italic (Ctrl+I)", active: fmt.italic, style: { fontStyle: "italic", fontFamily: "Georgia,serif" }, children: "I" }),
739
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("underline"), title: "Underline (Ctrl+U)", active: fmt.underline, style: { textDecoration: "underline" }, children: "U" }),
740
- /* @__PURE__ */ jsxs(Btn, { onClick: () => {
741
- var _a2;
742
- return (_a2 = colorRef.current) == null ? void 0 : _a2.click();
743
- }, title: "Text color", style: { flexDirection: "column", gap: 1, padding: "3px 6px" }, children: [
744
- /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 800, fontFamily: "Georgia,serif", lineHeight: 1, color: "#222" }, children: "A" }),
745
- /* @__PURE__ */ jsx("span", { className: "rte-swatch", style: { background: color } })
1442
+ const backdrop = { position: "fixed", inset: 0, zIndex: 199 };
1443
+ return /* @__PURE__ */ jsxs6("div", { style: { padding: "1rem 0" }, children: [
1444
+ /* @__PURE__ */ jsx7("style", { children: CSS }),
1445
+ /* @__PURE__ */ jsxs6("div", { style: { border: "1px solid #d8d8d8", borderRadius: 4, boxShadow: "0 1px 3px rgba(0,0,0,0.06)" }, children: [
1446
+ /* @__PURE__ */ jsxs6("div", { className: "rte-toolbar", children: [
1447
+ !isCode && /* @__PURE__ */ jsxs6(Fragment4, { children: [
1448
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("bold"), title: "Bold (Ctrl+B)", active: fmt.bold, style: { fontWeight: 800, fontFamily: "Georgia,serif" }, children: "B" }),
1449
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("italic"), title: "Italic (Ctrl+I)", active: fmt.italic, style: { fontStyle: "italic", fontFamily: "Georgia,serif" }, children: "I" }),
1450
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("underline"), title: "Underline (Ctrl+U)", active: fmt.underline, style: { textDecoration: "underline" }, children: "U" }),
1451
+ /* @__PURE__ */ jsxs6("div", { style: { position: "relative", display: "inline-flex" }, children: [
1452
+ showColor && /* @__PURE__ */ jsx7("div", { style: backdrop, onMouseDown: () => setShowColor(false) }),
1453
+ /* @__PURE__ */ jsxs6(
1454
+ Btn,
1455
+ {
1456
+ onClick: () => setShowColor((v) => !v),
1457
+ title: "Text color",
1458
+ active: showColor,
1459
+ style: { flexDirection: "column", gap: 1, padding: "3px 6px" },
1460
+ children: [
1461
+ /* @__PURE__ */ jsx7("span", { style: { fontSize: 13, fontWeight: 800, fontFamily: "Georgia,serif", lineHeight: 1, color: "#222" }, children: "A" }),
1462
+ /* @__PURE__ */ jsx7("span", { className: "rte-swatch", style: { background: color } })
1463
+ ]
1464
+ }
1465
+ ),
1466
+ showColor && /* @__PURE__ */ jsx7("div", { style: { position: "absolute", top: 34, left: -4, zIndex: 200 }, children: /* @__PURE__ */ jsx7(
1467
+ ColorPicker,
1468
+ {
1469
+ color,
1470
+ onColor: (c) => {
1471
+ setColor(c);
1472
+ exec("foreColor", c);
1473
+ setShowColor(false);
1474
+ },
1475
+ onRemove: () => {
1476
+ exec("removeFormat");
1477
+ setShowColor(false);
1478
+ },
1479
+ onClose: () => setShowColor(false)
1480
+ }
1481
+ ) })
746
1482
  ] }),
747
- /* @__PURE__ */ jsx(
748
- "input",
749
- {
750
- ref: colorRef,
751
- type: "color",
752
- value: color,
753
- onChange: (e) => {
754
- setColor(e.target.value);
755
- exec("foreColor", e.target.value);
756
- },
757
- style: { position: "absolute", opacity: 0, width: 0, height: 0, pointerEvents: "none" }
758
- }
759
- ),
760
- /* @__PURE__ */ jsx(Sep, {}),
761
- /* @__PURE__ */ jsxs(
1483
+ /* @__PURE__ */ jsx7(Sep, {}),
1484
+ /* @__PURE__ */ jsxs6(
762
1485
  "select",
763
1486
  {
764
1487
  className: "rte-select",
@@ -774,36 +1497,57 @@ function RichTextEditor({ value, onChange }) {
774
1497
  refresh();
775
1498
  },
776
1499
  children: [
777
- /* @__PURE__ */ jsx("option", { value: "p", children: "Paragraph" }),
778
- /* @__PURE__ */ jsx("option", { value: "h1", children: "Heading 1" }),
779
- /* @__PURE__ */ jsx("option", { value: "h2", children: "Heading 2" }),
780
- /* @__PURE__ */ jsx("option", { value: "h3", children: "Heading 3" })
1500
+ /* @__PURE__ */ jsx7("option", { value: "p", children: "Paragraph" }),
1501
+ /* @__PURE__ */ jsx7("option", { value: "h1", children: "Heading 1" }),
1502
+ /* @__PURE__ */ jsx7("option", { value: "h2", children: "Heading 2" }),
1503
+ /* @__PURE__ */ jsx7("option", { value: "h3", children: "Heading 3" })
781
1504
  ]
782
1505
  }
783
1506
  ),
784
- /* @__PURE__ */ jsx(Sep, {}),
785
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("justifyLeft"), title: "Align left", active: fmt.aL, children: /* @__PURE__ */ jsx(AlignIco, { t: "left" }) }),
786
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("justifyCenter"), title: "Align center", active: fmt.aC, children: /* @__PURE__ */ jsx(AlignIco, { t: "center" }) }),
787
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("justifyRight"), title: "Align right", active: fmt.aR, children: /* @__PURE__ */ jsx(AlignIco, { t: "right" }) }),
788
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("justifyFull"), title: "Justify", active: fmt.aJ, children: /* @__PURE__ */ jsx(AlignIco, { t: "justify" }) }),
789
- /* @__PURE__ */ jsx(Sep, {}),
790
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("insertUnorderedList"), title: "Bullet list", active: fmt.ul, children: /* @__PURE__ */ jsx(IcoUL, {}) }),
791
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("insertOrderedList"), title: "Numbered list", active: fmt.ol, children: /* @__PURE__ */ jsx(IcoOL, {}) }),
792
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("indent"), title: "Indent (Tab)", children: /* @__PURE__ */ jsx(IcoIndent, {}) }),
793
- /* @__PURE__ */ jsx(Btn, { onClick: () => exec("outdent"), title: "Outdent (Shift+Tab)", children: /* @__PURE__ */ jsx(IcoOutdent, {}) }),
794
- /* @__PURE__ */ jsx(Sep, {}),
795
- /* @__PURE__ */ jsx(Btn, { onClick: openLinkBar, title: "Insert link", active: linkBar, children: /* @__PURE__ */ jsx(IcoLink, {}) }),
796
- /* @__PURE__ */ jsx(Btn, { onClick: insertCodeBlock, title: "Code block", children: /* @__PURE__ */ jsx(IcoCode, {}) }),
797
- /* @__PURE__ */ jsxs("div", { className: "rte-tp-wrap", onMouseDown: (e) => e.stopPropagation(), children: [
798
- /* @__PURE__ */ jsx(Btn, { onClick: () => setShowTable((v) => !v), title: "Insert table", active: showTable || !!fmt.inTable, children: /* @__PURE__ */ jsx(IcoTable, {}) }),
799
- showTable && /* @__PURE__ */ jsx(TablePicker, { onInsert: (r, c) => {
800
- insertTable(r, c);
801
- setShowTable(false);
802
- }, onClose: () => setShowTable(false) })
1507
+ /* @__PURE__ */ jsx7(Sep, {}),
1508
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("justifyLeft"), title: "Align left", active: fmt.aL, children: /* @__PURE__ */ jsx7(AlignIco, { t: "left" }) }),
1509
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("justifyCenter"), title: "Align center", active: fmt.aC, children: /* @__PURE__ */ jsx7(AlignIco, { t: "center" }) }),
1510
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("justifyRight"), title: "Align right", active: fmt.aR, children: /* @__PURE__ */ jsx7(AlignIco, { t: "right" }) }),
1511
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("justifyFull"), title: "Justify", active: fmt.aJ, children: /* @__PURE__ */ jsx7(AlignIco, { t: "justify" }) }),
1512
+ /* @__PURE__ */ jsx7(Sep, {}),
1513
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("insertUnorderedList"), title: "Bullet list", active: fmt.ul, children: /* @__PURE__ */ jsx7(IcoUL, {}) }),
1514
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("insertOrderedList"), title: "Numbered list", active: fmt.ol, children: /* @__PURE__ */ jsx7(IcoOL, {}) }),
1515
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("indent"), title: "Indent (Tab)", children: /* @__PURE__ */ jsx7(IcoIndent, {}) }),
1516
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("outdent"), title: "Outdent (Shift+Tab)", children: /* @__PURE__ */ jsx7(IcoOutdent, {}) }),
1517
+ /* @__PURE__ */ jsx7(Sep, {}),
1518
+ /* @__PURE__ */ jsx7(Btn, { onClick: openLinkBar, title: "Insert link", active: linkBar, children: /* @__PURE__ */ jsx7(IcoLink, {}) }),
1519
+ /* @__PURE__ */ jsx7(Btn, { onClick: insertCodeBlock, title: "Code block", children: /* @__PURE__ */ jsx7(IcoCode, {}) }),
1520
+ /* @__PURE__ */ jsxs6("div", { style: { position: "relative", display: "inline-flex" }, children: [
1521
+ showImagePicker && /* @__PURE__ */ jsx7("div", { style: backdrop, onMouseDown: () => setShowImagePicker(false) }),
1522
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => setShowImagePicker((v) => !v), title: "Insert image", active: showImagePicker, children: /* @__PURE__ */ jsx7(IcoImage, {}) }),
1523
+ showImagePicker && /* @__PURE__ */ jsx7("div", { style: { position: "absolute", top: 34, left: 0, zIndex: 200 }, children: /* @__PURE__ */ jsx7(
1524
+ ImagePicker,
1525
+ {
1526
+ onInsert: (src) => {
1527
+ insertImage(src);
1528
+ setShowImagePicker(false);
1529
+ },
1530
+ onClose: () => setShowImagePicker(false)
1531
+ }
1532
+ ) })
1533
+ ] }),
1534
+ /* @__PURE__ */ jsxs6("div", { style: { position: "relative", display: "inline-flex" }, children: [
1535
+ showTable && /* @__PURE__ */ jsx7("div", { style: backdrop, onMouseDown: () => setShowTable(false) }),
1536
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => setShowTable((v) => !v), title: "Insert table", active: showTable || !!fmt.inTable, children: /* @__PURE__ */ jsx7(IcoTable, {}) }),
1537
+ showTable && /* @__PURE__ */ jsx7("div", { style: { position: "absolute", top: 34, left: 0, zIndex: 200 }, children: /* @__PURE__ */ jsx7(
1538
+ TablePicker,
1539
+ {
1540
+ onInsert: (r, c) => {
1541
+ insertTable(r, c);
1542
+ setShowTable(false);
1543
+ },
1544
+ onClose: () => setShowTable(false)
1545
+ }
1546
+ ) })
803
1547
  ] }),
804
- /* @__PURE__ */ jsx(Sep, {})
1548
+ /* @__PURE__ */ jsx7(Sep, {})
805
1549
  ] }),
806
- /* @__PURE__ */ jsx(
1550
+ /* @__PURE__ */ jsx7(
807
1551
  Btn,
808
1552
  {
809
1553
  onClick: isCode ? toVisual : toCode,
@@ -813,16 +1557,16 @@ function RichTextEditor({ value, onChange }) {
813
1557
  children: isCode ? "Visual" : "HTML"
814
1558
  }
815
1559
  ),
816
- /* @__PURE__ */ jsx(Btn, { onClick: copyHtml, title: "Copy HTML", children: /* @__PURE__ */ jsx(IcoCopy, {}) }),
817
- /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
818
- /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "#aaa" }, children: [
1560
+ /* @__PURE__ */ jsx7(Btn, { onClick: copyHtml, title: "Copy HTML", children: /* @__PURE__ */ jsx7(IcoCopy, {}) }),
1561
+ /* @__PURE__ */ jsx7("div", { style: { flex: 1 } }),
1562
+ /* @__PURE__ */ jsxs6("span", { style: { fontSize: 12, color: "#aaa" }, children: [
819
1563
  words,
820
1564
  " ",
821
1565
  words === 1 ? "word" : "words"
822
1566
  ] })
823
1567
  ] }),
824
- /* @__PURE__ */ jsxs("div", { className: "rte-area", ref: editorAreaRef, children: [
825
- /* @__PURE__ */ jsx(
1568
+ /* @__PURE__ */ jsxs6("div", { className: "rte-area", ref: editorAreaRef, children: [
1569
+ /* @__PURE__ */ jsx7(
826
1570
  "textarea",
827
1571
  {
828
1572
  className: "rte-code",
@@ -832,7 +1576,7 @@ function RichTextEditor({ value, onChange }) {
832
1576
  style: { display: isCode ? "block" : "none" }
833
1577
  }
834
1578
  ),
835
- /* @__PURE__ */ jsx(
1579
+ /* @__PURE__ */ jsx7(
836
1580
  "div",
837
1581
  {
838
1582
  ref: editorRef,
@@ -842,32 +1586,103 @@ function RichTextEditor({ value, onChange }) {
842
1586
  "data-ph": "Start typing...",
843
1587
  onMouseDown: handleEditorMouseDown,
844
1588
  onMouseMove: handleEditorMouseMove,
845
- onMouseUp: handleEditorMouseUp,
1589
+ onMouseUp: () => {
1590
+ isDragging.current = false;
1591
+ },
846
1592
  onKeyDown: handleKeyDown,
847
1593
  onKeyUp: refresh,
848
1594
  onSelect: refresh,
1595
+ onDrop: (e) => {
1596
+ var _a2;
1597
+ const file = (_a2 = e.dataTransfer.files) == null ? void 0 : _a2[0];
1598
+ if (!(file == null ? void 0 : file.type.startsWith("image/"))) return;
1599
+ e.preventDefault();
1600
+ const reader = new FileReader();
1601
+ reader.onload = () => insertImage(reader.result);
1602
+ reader.readAsDataURL(file);
1603
+ },
1604
+ onDragOver: (e) => {
1605
+ if (e.dataTransfer.types.includes("Files")) e.preventDefault();
1606
+ },
849
1607
  style: { display: isCode ? "none" : "block" }
850
1608
  }
851
1609
  ),
852
- !isCode && fmt.inTable && tableFP && /* @__PURE__ */ jsxs("div", { className: "rte-float", style: { top: tableFP.top, left: tableFP.left }, children: [
853
- /* @__PURE__ */ jsx("div", { className: "rte-float-arrow", style: { left: tableFP.arrowLeft } }),
854
- /* @__PURE__ */ jsx(Btn, { onClick: () => tableOp("addRowAbove"), title: "Add row above", children: /* @__PURE__ */ jsx(IcoRowAbove, {}) }),
855
- /* @__PURE__ */ jsx(Btn, { onClick: () => tableOp("addRowBelow"), title: "Add row below", children: /* @__PURE__ */ jsx(IcoRowBelow, {}) }),
856
- /* @__PURE__ */ jsx(Btn, { onClick: () => tableOp("deleteRow"), title: "Delete row", danger: true, children: /* @__PURE__ */ jsx(IcoDelRow, {}) }),
857
- /* @__PURE__ */ jsx(Sep, {}),
858
- /* @__PURE__ */ jsx(Btn, { onClick: () => tableOp("addColLeft"), title: "Add column left", children: /* @__PURE__ */ jsx(IcoColLeft, {}) }),
859
- /* @__PURE__ */ jsx(Btn, { onClick: () => tableOp("addColRight"), title: "Add column right", children: /* @__PURE__ */ jsx(IcoColRight, {}) }),
860
- /* @__PURE__ */ jsx(Btn, { onClick: () => tableOp("deleteCol"), title: "Delete column", danger: true, children: /* @__PURE__ */ jsx(IcoDelCol, {}) }),
861
- /* @__PURE__ */ jsx(Sep, {}),
862
- canMerge && /* @__PURE__ */ jsx(Btn, { onClick: () => doMerge(selCells), title: `Merge ${selCells.length} cells`, children: /* @__PURE__ */ jsx(IcoMerge, {}) }),
863
- canSplit && /* @__PURE__ */ jsx(Btn, { onClick: doSplit, title: "Split merged cell", children: /* @__PURE__ */ jsx(IcoSplit, {}) }),
864
- (canMerge || canSplit) && /* @__PURE__ */ jsx(Sep, {}),
865
- /* @__PURE__ */ jsx(Btn, { onClick: () => tableOp("deleteTable"), title: "Delete table", danger: true, children: /* @__PURE__ */ jsx(IcoDelTable, {}) })
1610
+ !isCode && selectedImg && /* @__PURE__ */ jsx7(
1611
+ ImageEditor,
1612
+ {
1613
+ img: selectedImg,
1614
+ containerRef: editorAreaRef,
1615
+ onClose: clearImageSel,
1616
+ onDelete: () => {
1617
+ selectedImg.remove();
1618
+ clearImageSel();
1619
+ refresh();
1620
+ },
1621
+ onChange: refresh
1622
+ }
1623
+ ),
1624
+ !isCode && fmt.inTable && tableFP && /* @__PURE__ */ jsxs6("div", { className: "rte-float", style: { top: tableFP.top, left: tableFP.left }, children: [
1625
+ /* @__PURE__ */ jsx7("div", { className: "rte-float-arrow", style: { left: tableFP.arrowLeft } }),
1626
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => tableOp("addRowAbove"), title: "Add row above", children: /* @__PURE__ */ jsx7(IcoRowAbove, {}) }),
1627
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => tableOp("addRowBelow"), title: "Add row below", children: /* @__PURE__ */ jsx7(IcoRowBelow, {}) }),
1628
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => tableOp("deleteRow"), title: "Delete row", danger: true, children: /* @__PURE__ */ jsx7(IcoDelRow, {}) }),
1629
+ /* @__PURE__ */ jsx7(Sep, {}),
1630
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => tableOp("addColLeft"), title: "Add col left", children: /* @__PURE__ */ jsx7(IcoColLeft, {}) }),
1631
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => tableOp("addColRight"), title: "Add col right", children: /* @__PURE__ */ jsx7(IcoColRight, {}) }),
1632
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => tableOp("deleteCol"), title: "Delete col", danger: true, children: /* @__PURE__ */ jsx7(IcoDelCol, {}) }),
1633
+ /* @__PURE__ */ jsx7(Sep, {}),
1634
+ canMerge && /* @__PURE__ */ jsx7(Btn, { onClick: () => doMerge(selCells), title: `Merge ${selCells.length} cells`, children: /* @__PURE__ */ jsx7(IcoMerge, {}) }),
1635
+ canSplit && /* @__PURE__ */ jsx7(Btn, { onClick: doSplit, title: "Split merged cell", children: /* @__PURE__ */ jsx7(IcoSplit, {}) }),
1636
+ (canMerge || canSplit) && /* @__PURE__ */ jsx7(Sep, {}),
1637
+ /* @__PURE__ */ jsx7(Btn, { onClick: () => tableOp("deleteTable"), title: "Delete table", danger: true, children: /* @__PURE__ */ jsx7(IcoDelTable, {}) })
866
1638
  ] }),
867
- !isCode && linkBar && linkFP && /* @__PURE__ */ jsxs("div", { className: "rte-float rte-link-float", style: { top: linkFP.top, left: linkFP.left }, children: [
868
- /* @__PURE__ */ jsx("div", { className: "rte-float-arrow", style: { left: linkFP.arrowLeft } }),
869
- /* @__PURE__ */ jsx(IcoLink, {}),
870
- /* @__PURE__ */ jsx(
1639
+ !isCode && fmt.inLink && linkInfoFP && !linkBar && /* @__PURE__ */ jsxs6("div", { className: "rte-float", style: { top: linkInfoFP.top, left: linkInfoFP.left, gap: 4 }, children: [
1640
+ /* @__PURE__ */ jsx7("div", { className: "rte-float-arrow", style: { left: linkInfoFP.arrowLeft } }),
1641
+ /* @__PURE__ */ jsx7(IcoLink, {}),
1642
+ /* @__PURE__ */ jsx7(
1643
+ "span",
1644
+ {
1645
+ style: { fontSize: 12, color: "#1a6fc4", maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
1646
+ title: fmt.linkHref,
1647
+ children: fmt.linkHref
1648
+ }
1649
+ ),
1650
+ /* @__PURE__ */ jsx7(Sep, {}),
1651
+ /* @__PURE__ */ jsx7(Btn, { title: "Edit link", onClick: () => {
1652
+ var _a2;
1653
+ const anchor = getCurrentLink();
1654
+ if (!anchor) return;
1655
+ const sel = window.getSelection();
1656
+ const range = document.createRange();
1657
+ range.selectNodeContents(anchor);
1658
+ sel == null ? void 0 : sel.removeAllRanges();
1659
+ sel == null ? void 0 : sel.addRange(range);
1660
+ savedRangeRef.current = range.cloneRange();
1661
+ if (editorAreaRef.current) setLinkFP(calcFloat(anchor, 360));
1662
+ setLinkUrl((_a2 = anchor.getAttribute("href")) != null ? _a2 : "https://");
1663
+ setLinkBar(true);
1664
+ setLinkInfoFP(null);
1665
+ }, children: /* @__PURE__ */ jsx7("svg", { width: "13", height: "13", viewBox: "0 0 13 13", fill: "none", children: /* @__PURE__ */ jsx7("path", { d: "M9 2L11 4L4.5 10.5H2.5V8.5L9 2Z", stroke: "currentColor", strokeWidth: "1.2", strokeLinejoin: "round" }) }) }),
1666
+ /* @__PURE__ */ jsx7(Btn, { title: "Remove link", danger: true, onClick: () => {
1667
+ const anchor = getCurrentLink();
1668
+ if (!anchor) return;
1669
+ const sel = window.getSelection();
1670
+ const range = document.createRange();
1671
+ range.selectNodeContents(anchor);
1672
+ sel == null ? void 0 : sel.removeAllRanges();
1673
+ sel == null ? void 0 : sel.addRange(range);
1674
+ document.execCommand("unlink", false);
1675
+ refresh();
1676
+ }, children: /* @__PURE__ */ jsxs6("svg", { width: "13", height: "13", viewBox: "0 0 13 13", fill: "none", children: [
1677
+ /* @__PURE__ */ jsx7("path", { d: "M4.5 7C4.9 7.6 5.5 8 6.1 8H7.9C8.6 8 9.2 7.7 9.6 7.2", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }),
1678
+ /* @__PURE__ */ jsx7("path", { d: "M8.5 6C8.1 5.4 7.5 5 6.9 5H5.1C4.4 5 3.8 5.3 3.4 5.8", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }),
1679
+ /* @__PURE__ */ jsx7("path", { d: "M2 2L11 11", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
1680
+ ] }) })
1681
+ ] }),
1682
+ !isCode && linkBar && linkFP && /* @__PURE__ */ jsxs6("div", { className: "rte-float rte-linkbar", style: { top: linkFP.top, left: linkFP.left }, children: [
1683
+ /* @__PURE__ */ jsx7("div", { className: "rte-float-arrow", style: { left: linkFP.arrowLeft } }),
1684
+ /* @__PURE__ */ jsx7(IcoLink, {}),
1685
+ /* @__PURE__ */ jsx7(
871
1686
  "input",
872
1687
  {
873
1688
  autoFocus: true,
@@ -884,25 +1699,39 @@ function RichTextEditor({ value, onChange }) {
884
1699
  placeholder: "https://example.com"
885
1700
  }
886
1701
  ),
887
- /* @__PURE__ */ jsx("button", { className: "rte-link-apply", onClick: applyLink, children: "Apply" }),
888
- /* @__PURE__ */ jsx("button", { className: "rte-link-cancel", onClick: () => {
889
- setLinkBar(false);
890
- setLinkFP(null);
891
- }, children: "\u2715" })
1702
+ /* @__PURE__ */ jsx7(
1703
+ "button",
1704
+ {
1705
+ onClick: applyLink,
1706
+ style: { height: 26, padding: "0 10px", background: "#1a6fc4", color: "#fff", border: "none", borderRadius: 3, fontSize: 12, fontWeight: 600, cursor: "pointer" },
1707
+ children: "Apply"
1708
+ }
1709
+ ),
1710
+ /* @__PURE__ */ jsx7(
1711
+ "button",
1712
+ {
1713
+ onClick: () => {
1714
+ setLinkBar(false);
1715
+ setLinkFP(null);
1716
+ },
1717
+ style: { height: 26, padding: "0 8px", background: "transparent", color: "#666", border: "1px solid #ccc", borderRadius: 3, fontSize: 12, cursor: "pointer" },
1718
+ children: "\u2715"
1719
+ }
1720
+ )
892
1721
  ] })
893
1722
  ] }),
894
- !isCode && /* @__PURE__ */ jsxs("div", { className: "rte-footer", children: [
895
- "Table: ",
896
- /* @__PURE__ */ jsx("strong", { children: "drag" }),
1723
+ !isCode && /* @__PURE__ */ jsxs6("div", { className: "rte-footer", children: [
1724
+ "Click image to edit \xA0\xB7\xA0 Drag handles to resize \xA0\xB7\xA0 Drop image file to insert \xA0\xB7\xA0 Table: ",
1725
+ /* @__PURE__ */ jsx7("strong", { children: "drag" }),
897
1726
  " or ",
898
- /* @__PURE__ */ jsx("strong", { children: "Shift+click" }),
899
- " to select cells \xA0\xB7\xA0 ",
900
- /* @__PURE__ */ jsx("strong", { children: "Tab" }),
1727
+ /* @__PURE__ */ jsx7("strong", { children: "Shift+click" }),
1728
+ " to select cells \xA0\xB7\xA0",
1729
+ /* @__PURE__ */ jsx7("strong", { children: "Tab" }),
901
1730
  " moves between cells"
902
1731
  ] })
903
1732
  ] })
904
1733
  ] });
905
1734
  }
906
1735
  export {
907
- RichTextEditor
1736
+ RichTextEditor as default
908
1737
  };