@malaya_jeeva/rich-text-editor 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +711 -224
  2. package/dist/index.mjs +711 -224
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -27,138 +27,171 @@ module.exports = __toCommonJS(index_exports);
27
27
  // src/RichTextEditor.tsx
28
28
  var import_react = require("react");
29
29
  var import_jsx_runtime = require("react/jsx-runtime");
30
- var CSS = `
31
- * { box-sizing: border-box; }
32
- .rte-toolbar {
33
- display: flex; flex-wrap: wrap; align-items: center; gap: 1px;
34
- padding: 4px 8px; background: #f8f8f8; border-bottom: 1px solid #e0e0e0;
35
- }
36
- @media (prefers-color-scheme: dark) { .rte-toolbar { background: #1e1e1e; border-color: #333; } }
37
-
38
- .rte-btn {
39
- background: transparent; border: none; border-radius: 3px; cursor: pointer;
40
- height: 30px; min-width: 30px; padding: 0 6px; color: #444; font-size: 13px;
41
- font-weight: 500; font-family: var(--font-sans); display: inline-flex;
42
- align-items: center; justify-content: center; user-select: none;
43
- white-space: nowrap; transition: background 0.1s; flex-shrink: 0;
44
- }
45
- .rte-btn:hover { background: #e8e8e8; }
46
- .rte-btn.active { background: #d0e4ff; color: #1a5fb4; }
47
- .rte-btn.danger { color: #c0392b; }
48
- .rte-btn.danger:hover { background: #fdecea; }
49
- @media (prefers-color-scheme: dark) {
50
- .rte-btn { color: #ccc; } .rte-btn:hover { background: #2e2e2e; }
51
- .rte-btn.active { background: #1a3a5c; color: #90c4ff; }
52
- .rte-btn.danger { color: #ff6b6b; } .rte-btn.danger:hover { background: #3a1a1a; }
53
- }
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
-
57
- .rte-select {
58
- height: 28px; border: 1px solid #d8d8d8; border-radius: 3px;
59
- background: #fff; color: #333; font-size: 12px; padding: 0 22px 0 7px;
60
- cursor: pointer; outline: none; font-family: var(--font-sans);
61
- appearance: none; -webkit-appearance: none;
62
- 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");
63
- background-repeat: no-repeat; background-position: right 6px center;
64
- }
65
- @media (prefers-color-scheme: dark) { .rte-select { background-color: #2a2a2a; border-color: #444; color: #ccc; } }
66
- .rte-swatch { width: 14px; height: 3px; border-radius: 1px; margin-top: 2px; }
67
-
68
- /* Table picker */
69
- .rte-tp-wrap { position: relative; display: inline-flex; }
70
- .rte-tp {
71
- position: absolute; top: 34px; left: 0; z-index: 100;
72
- background: #fff; border: 1px solid #d8d8d8; border-radius: 6px;
73
- box-shadow: 0 4px 16px rgba(0,0,0,0.12); padding: 10px;
74
- }
75
- @media (prefers-color-scheme: dark) { .rte-tp { background: #222; border-color: #444; } }
76
- .rte-tp-lbl { font-size: 11px; color: #888; text-align: center; margin-bottom: 8px; font-family: var(--font-sans); }
77
- .rte-tp-grid { display: grid; grid-template-columns: repeat(8, 18px); gap: 2px; }
78
- .rte-tp-cell { width: 18px; height: 18px; border-radius: 2px; border: 1px solid #ddd; cursor: pointer; background: #fff; transition: background 0.08s; }
79
- .rte-tp-cell.on { background: #d0e4ff; border-color: #1a5fb4; }
80
- @media (prefers-color-scheme: dark) {
81
- .rte-tp-cell { background: #2a2a2a; border-color: #444; }
82
- .rte-tp-cell.on { background: #1a3a5c; border-color: #90c4ff; }
83
- }
84
-
85
- /* \u2500\u2500 Floating toolbar \u2500\u2500 */
86
- .rte-float {
87
- position: absolute; z-index: 50;
88
- background: #fff; border: 1px solid #d0d0d0;
89
- border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.13);
90
- padding: 4px 6px;
91
- display: flex; align-items: center; gap: 1px; flex-wrap: wrap;
92
- }
93
- @media (prefers-color-scheme: dark) { .rte-float { background: #222; border-color: #444; } }
94
- .rte-float-arrow {
95
- position: absolute; top: -7px; width: 14px; height: 7px;
96
- }
97
- .rte-float-arrow::before,
98
- .rte-float-arrow::after {
99
- content: ''; position: absolute; left: 0;
100
- border-left: 7px solid transparent; border-right: 7px solid transparent;
101
- }
102
- .rte-float-arrow::before { top: 0; border-bottom: 7px solid #d0d0d0; }
103
- .rte-float-arrow::after { top: 1px; border-bottom: 6px solid #fff; }
104
- @media (prefers-color-scheme: dark) {
105
- .rte-float-arrow::before { border-bottom-color: #444; }
106
- .rte-float-arrow::after { border-bottom-color: #222; }
107
- }
108
-
109
- /* Link bar inside float */
110
- .rte-link-float { min-width: 340px; }
111
- .rte-link-float input {
112
- flex: 1; height: 26px; border: 1px solid #ccc; border-radius: 3px;
113
- padding: 0 8px; font-size: 13px; outline: none; font-family: var(--font-sans);
114
- background: #fff; color: #222;
115
- min-width: 0;
116
- }
117
- @media (prefers-color-scheme: dark) { .rte-link-float input { background: #1a1a1a; border-color: #555; color: #ddd; } }
118
- .rte-link-apply {
119
- height: 26px; padding: 0 10px; background: #1a6fc4; color: #fff;
120
- border: none; border-radius: 3px; font-size: 12px; font-weight: 600; cursor: pointer; white-space: nowrap;
121
- }
122
- .rte-link-cancel {
123
- height: 26px; padding: 0 8px; background: transparent; color: #666;
124
- border: 1px solid #ccc; border-radius: 3px; font-size: 12px; cursor: pointer;
125
- }
126
- @media (prefers-color-scheme: dark) { .rte-link-cancel { color: #aaa; border-color: #555; } }
127
-
128
- /* Editor */
129
- .rte-area { position: relative; background: #fff; }
130
- @media (prefers-color-scheme: dark) { .rte-area { background: #141414; } }
131
- .rte-body {
132
- min-height: 280px; outline: none; font-size: 15px; line-height: 1.75;
133
- color: #222; caret-color: #222; padding: 18px 20px;
30
+ function hsv2rgb(h, s, v) {
31
+ const f = (n) => {
32
+ const k = (n + h / 60) % 6;
33
+ return v - v * s * Math.max(0, Math.min(k, 4 - k, 1));
34
+ };
35
+ return [Math.round(f(5) * 255), Math.round(f(3) * 255), Math.round(f(1) * 255)];
36
+ }
37
+ function rgb2hex(r, g, b) {
38
+ return "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join("");
39
+ }
40
+ function hex2hsv(hex) {
41
+ if (hex.length < 7) return [0, 1, 1];
42
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
43
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
44
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
45
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
46
+ let h = 0;
47
+ const s = max === 0 ? 0 : d / max, v = max;
48
+ if (d !== 0) {
49
+ if (max === r) h = (g - b) / d % 6;
50
+ else if (max === g) h = (b - r) / d + 2;
51
+ else h = (r - g) / d + 4;
52
+ h = Math.round(h * 60);
53
+ if (h < 0) h += 360;
134
54
  }
135
- @media (prefers-color-scheme: dark) { .rte-body { color: #ddd; caret-color: #ddd; } }
136
- .rte-body h1 { font-size: 26px; font-weight: 600; margin: 0.6em 0 0.2em; }
137
- .rte-body h2 { font-size: 20px; font-weight: 600; margin: 0.6em 0 0.2em; }
138
- .rte-body h3 { font-size: 16px; font-weight: 600; margin: 0.6em 0 0.2em; }
139
- .rte-body p { margin: 0.2em 0; }
140
- .rte-body ol { list-style-type: decimal; padding-left: 1.6em; margin: 0.3em 0; }
141
- .rte-body ol ol { list-style-type: lower-alpha; padding-left: 1.6em; }
142
- .rte-body ol ol ol { list-style-type: lower-roman; padding-left: 1.6em; }
143
- .rte-body ul { list-style-type: disc; padding-left: 1.6em; margin: 0.3em 0; }
144
- .rte-body ul ul { list-style-type: circle; padding-left: 1.6em; }
145
- .rte-body ul ul ul { list-style-type: square; padding-left: 1.6em; }
146
- .rte-body li { margin: 0.1em 0; }
147
- .rte-body a { color: #1a6fc4; text-decoration: underline; cursor: pointer; }
148
- .rte-body pre { background: #f4f4f4; border: 1px solid #e0e0e0; border-radius: 4px; padding: 12px 14px; margin: 0.6em 0; overflow-x: auto; }
149
- .rte-body code { font-family: var(--font-mono); font-size: 13px; }
150
- .rte-body:empty:before { content: attr(data-ph); color: #aaa; pointer-events: none; }
151
- .rte-body table { border-collapse: collapse; width: 100%; margin: 0.8em 0; }
152
- .rte-body td, .rte-body th { border: 1px solid #ccc; padding: 7px 10px; min-width: 60px; text-align: left; vertical-align: top; }
153
- .rte-body th { background: #f5f5f5; font-weight: 600; }
154
- @media (prefers-color-scheme: dark) {
155
- .rte-body td, .rte-body th { border-color: #444; }
156
- .rte-body th { background: #2a2a2a; }
55
+ return [h, s, v];
56
+ }
57
+ function getColorAtCursor(editor) {
58
+ var _a, _b;
59
+ if (!editor) return "#000000";
60
+ const sel = window.getSelection();
61
+ if (!(sel == null ? void 0 : sel.rangeCount)) return "#000000";
62
+ let el = ((_a = sel.anchorNode) == null ? void 0 : _a.nodeType) === 3 ? sel.anchorNode.parentElement : sel.anchorNode;
63
+ while (el && el !== editor) {
64
+ const c = (_b = el.style) == null ? void 0 : _b.color;
65
+ if (c) {
66
+ const m = c.match(/\d+/g);
67
+ if (m && m.length >= 3) return "#" + m.slice(0, 3).map((n) => parseInt(n).toString(16).padStart(2, "0")).join("");
68
+ if (c.startsWith("#")) return c;
69
+ }
70
+ el = el.parentElement;
157
71
  }
158
- .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; }
159
- .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; }
160
- .rte-footer { font-size: 11px; color: #999; padding: 4px 20px 5px; border-top: 1px solid #e8e8e8; background: #fafafa; }
161
- @media (prefers-color-scheme: dark) { .rte-footer { border-color: #2a2a2a; background: #1a1a1a; color: #555; } }
72
+ return "#000000";
73
+ }
74
+ var PALETTE = [
75
+ "#000000",
76
+ "#434343",
77
+ "#666666",
78
+ "#999999",
79
+ "#b7b7b7",
80
+ "#cccccc",
81
+ "#d9d9d9",
82
+ "#ffffff",
83
+ "#ff0000",
84
+ "#ff4500",
85
+ "#ff9900",
86
+ "#ffff00",
87
+ "#00ff00",
88
+ "#00ffff",
89
+ "#4a86e8",
90
+ "#0000ff",
91
+ "#9900ff",
92
+ "#ff00ff",
93
+ "#e06666",
94
+ "#f6b26b",
95
+ "#ffd966",
96
+ "#93c47d",
97
+ "#76a5af",
98
+ "#6fa8dc",
99
+ "#8e7cc3",
100
+ "#c27ba0",
101
+ "#a61c00",
102
+ "#783f04",
103
+ "#7f6000",
104
+ "#274e13",
105
+ "#0c343d",
106
+ "#073763",
107
+ "#20124d",
108
+ "#4c1130"
109
+ ];
110
+ var CSS = `
111
+ * { box-sizing: border-box; }
112
+ .rte-toolbar { display:flex; flex-wrap:wrap; align-items:center; gap:1px; padding:4px 8px; background:#f8f8f8; border-bottom:1px solid #e0e0e0; }
113
+ @media(prefers-color-scheme:dark){ .rte-toolbar{background:#1e1e1e;border-color:#333;} }
114
+ .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; }
115
+ .rte-btn:hover { background:#e8e8e8; }
116
+ .rte-btn.active { background:#d0e4ff; color:#1a5fb4; }
117
+ .rte-btn.danger { color:#c0392b; } .rte-btn.danger:hover { background:#fdecea; }
118
+ @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;} }
119
+ .rte-sep { width:1px; height:20px; background:#d8d8d8; margin:0 4px; flex-shrink:0; }
120
+ @media(prefers-color-scheme:dark){ .rte-sep{background:#3a3a3a;} }
121
+ .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; }
122
+ @media(prefers-color-scheme:dark){ .rte-select{background-color:#2a2a2a;border-color:#444;color:#ccc;} }
123
+ .rte-swatch { width:14px; height:3px; border-radius:1px; margin-top:2px; }
124
+ .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; }
125
+ @media(prefers-color-scheme:dark){ .rte-cpick{background:#222;border-color:#444;} }
126
+ .rte-cgrid { display:grid; grid-template-columns:repeat(8,20px); gap:2px; margin-bottom:6px; }
127
+ .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; }
128
+ .rte-ccell:hover { transform:scale(1.2); box-shadow:0 1px 4px rgba(0,0,0,0.3); z-index:1; position:relative; }
129
+ .rte-ccell.sel { outline:2px solid #1a5fb4; outline-offset:1px; }
130
+ .rte-cactions { display:flex; align-items:center; gap:2px; border-top:1px solid #eee; padding-top:6px; margin-top:2px; }
131
+ @media(prefers-color-scheme:dark){ .rte-cactions{border-color:#333;} }
132
+ .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; }
133
+ .rte-caction:hover { background:#f0f0f0; border-color:#ddd; }
134
+ @media(prefers-color-scheme:dark){ .rte-caction:hover{background:#2a2a2a;border-color:#444;} }
135
+ .rte-tpick { background:#fff; border:1px solid #d8d8d8; border-radius:6px; box-shadow:0 4px 16px rgba(0,0,0,0.12); padding:10px; }
136
+ @media(prefers-color-scheme:dark){ .rte-tpick{background:#222;border-color:#444;} }
137
+ .rte-tplbl { font-size:11px; color:#888; text-align:center; margin-bottom:8px; font-family:var(--font-sans); }
138
+ .rte-tpgrid { display:grid; grid-template-columns:repeat(8,18px); gap:2px; }
139
+ .rte-tpcell { width:18px; height:18px; border-radius:2px; border:1px solid #ddd; cursor:pointer; background:#fff; transition:background 0.08s; }
140
+ .rte-tpcell.on { background:#d0e4ff; border-color:#1a5fb4; }
141
+ @media(prefers-color-scheme:dark){ .rte-tpcell{background:#2a2a2a;border-color:#444;} .rte-tpcell.on{background:#1a3a5c;border-color:#90c4ff;} }
142
+ .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; }
143
+ @media(prefers-color-scheme:dark){ .rte-ipick{background:#222;border-color:#444;} }
144
+ .rte-itabs { display:flex; border-bottom:1px solid #eee; }
145
+ @media(prefers-color-scheme:dark){ .rte-itabs{border-color:#333;} }
146
+ .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); }
147
+ .rte-itab:hover { color:#444; background:#f8f8f8; }
148
+ .rte-itab.active { color:#1a5fb4; border-bottom-color:#1a5fb4; }
149
+ @media(prefers-color-scheme:dark){ .rte-itab:hover{background:#2a2a2a;} }
150
+ .rte-ipbody { padding:12px; display:flex; flex-direction:column; gap:10px; }
151
+ .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; }
152
+ .rte-idrop:hover,.rte-idrop.drag { border-color:#1a5fb4; background:#f0f4ff; }
153
+ @media(prefers-color-scheme:dark){ .rte-idrop{border-color:#555;} .rte-idrop:hover,.rte-idrop.drag{background:#1a3a5c;border-color:#90c4ff;} }
154
+ .rte-iinsert { width:100%; height:30px; background:#1a6fc4; color:#fff; border:none; border-radius:4px; font-size:12px; font-weight:600; cursor:pointer; }
155
+ .rte-iinsert:disabled { opacity:0.4; cursor:not-allowed; }
156
+ .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; }
157
+ @media(prefers-color-scheme:dark){ .rte-float{background:#222;border-color:#444;} }
158
+ .rte-float-arrow { position:absolute; top:-7px; width:14px; height:7px; }
159
+ .rte-float-arrow::before,.rte-float-arrow::after { content:''; position:absolute; left:0; border-left:7px solid transparent; border-right:7px solid transparent; }
160
+ .rte-float-arrow::before { top:0; border-bottom:7px solid #d0d0d0; }
161
+ .rte-float-arrow::after { top:1px; border-bottom:6px solid #fff; }
162
+ @media(prefers-color-scheme:dark){ .rte-float-arrow::before{border-bottom-color:#444;} .rte-float-arrow::after{border-bottom-color:#222;} }
163
+ .rte-linkbar { min-width:340px; }
164
+ .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; }
165
+ @media(prefers-color-scheme:dark){ .rte-linkbar input{background:#1a1a1a;border-color:#555;color:#ddd;} }
166
+ .rte-area { position:relative; background:#fff; }
167
+ @media(prefers-color-scheme:dark){ .rte-area{background:#141414;} }
168
+ .rte-body { min-height:280px; outline:none; font-size:15px; line-height:1.75; color:#222; caret-color:#222; padding:18px 20px; }
169
+ @media(prefers-color-scheme:dark){ .rte-body{color:#ddd;caret-color:#ddd;} }
170
+ .rte-body h1 { font-size:26px; font-weight:600; margin:0.6em 0 0.2em; }
171
+ .rte-body h2 { font-size:20px; font-weight:600; margin:0.6em 0 0.2em; }
172
+ .rte-body h3 { font-size:16px; font-weight:600; margin:0.6em 0 0.2em; }
173
+ .rte-body p { margin:0.2em 0; }
174
+ .rte-body ol { list-style-type:decimal; padding-left:1.6em; margin:0.3em 0; }
175
+ .rte-body ol ol { list-style-type:lower-alpha; padding-left:1.6em; }
176
+ .rte-body ol ol ol { list-style-type:lower-roman; padding-left:1.6em; }
177
+ .rte-body ul { list-style-type:disc; padding-left:1.6em; margin:0.3em 0; }
178
+ .rte-body ul ul { list-style-type:circle; padding-left:1.6em; }
179
+ .rte-body ul ul ul { list-style-type:square; padding-left:1.6em; }
180
+ .rte-body li { margin:0.1em 0; }
181
+ .rte-body a { color:#1a6fc4; text-decoration:underline; cursor:pointer; }
182
+ .rte-body pre { background:#f4f4f4; border:1px solid #e0e0e0; border-radius:4px; padding:12px 14px; margin:0.6em 0; overflow-x:auto; }
183
+ .rte-body code { font-family:var(--font-mono); font-size:13px; }
184
+ .rte-body:empty:before { content:attr(data-ph); color:#aaa; pointer-events:none; }
185
+ .rte-body table { border-collapse:collapse; width:100%; margin:0.8em 0; }
186
+ .rte-body td,.rte-body th { border:1px solid #ccc; padding:7px 10px; min-width:60px; text-align:left; vertical-align:top; }
187
+ .rte-body th { background:#f5f5f5; font-weight:600; }
188
+ @media(prefers-color-scheme:dark){ .rte-body td,.rte-body th{border-color:#444;} .rte-body th{background:#2a2a2a;} }
189
+ .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; }
190
+ .rte-body img { max-width:100%; height:auto; display:block; margin:4px 0; cursor:pointer; }
191
+ .rte-body img.rte-img-sel { outline:2px solid #1a5fb4; outline-offset:2px; }
192
+ .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; }
193
+ .rte-footer { font-size:11px; color:#999; padding:4px 20px 5px; border-top:1px solid #e8e8e8; background:#fafafa; }
194
+ @media(prefers-color-scheme:dark){ .rte-footer{border-color:#2a2a2a;background:#1a1a1a;color:#555;} }
162
195
  `;
163
196
  function Btn({ onClick, title, active, danger, children, style }) {
164
197
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -178,16 +211,213 @@ function Btn({ onClick, title, active, danger, children, style }) {
178
211
  function Sep() {
179
212
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-sep" });
180
213
  }
214
+ function CustomColorPicker({ initialColor, onApply, onBack }) {
215
+ const [hsv, setHsv] = (0, import_react.useState)(() => hex2hsv(initialColor || "#ff0000"));
216
+ const [hexInput, setHexInput] = (0, import_react.useState)(initialColor || "#ff0000");
217
+ const svRef = (0, import_react.useRef)(null);
218
+ const hueRef = (0, import_react.useRef)(null);
219
+ const svDrag = (0, import_react.useRef)(false);
220
+ const hueDrag = (0, import_react.useRef)(false);
221
+ const [h, s, v] = hsv;
222
+ const currentHex = rgb2hex(...hsv2rgb(h, s, v));
223
+ (0, import_react.useEffect)(() => {
224
+ const c = svRef.current;
225
+ if (!c) return;
226
+ const ctx = c.getContext("2d");
227
+ const W = c.width, H = c.height;
228
+ const [r, g, b] = hsv2rgb(h, 1, 1);
229
+ ctx.fillStyle = `rgb(${r},${g},${b})`;
230
+ ctx.fillRect(0, 0, W, H);
231
+ const wg = ctx.createLinearGradient(0, 0, W, 0);
232
+ wg.addColorStop(0, "rgba(255,255,255,1)");
233
+ wg.addColorStop(1, "rgba(255,255,255,0)");
234
+ ctx.fillStyle = wg;
235
+ ctx.fillRect(0, 0, W, H);
236
+ const bg = ctx.createLinearGradient(0, 0, 0, H);
237
+ bg.addColorStop(0, "rgba(0,0,0,0)");
238
+ bg.addColorStop(1, "rgba(0,0,0,1)");
239
+ ctx.fillStyle = bg;
240
+ ctx.fillRect(0, 0, W, H);
241
+ const cx = s * W, cy = (1 - v) * H;
242
+ ctx.beginPath();
243
+ ctx.arc(cx, cy, 7, 0, Math.PI * 2);
244
+ ctx.strokeStyle = "rgba(0,0,0,0.25)";
245
+ ctx.lineWidth = 2;
246
+ ctx.stroke();
247
+ ctx.beginPath();
248
+ ctx.arc(cx, cy, 6, 0, Math.PI * 2);
249
+ ctx.strokeStyle = "#fff";
250
+ ctx.lineWidth = 2;
251
+ ctx.stroke();
252
+ }, [h, s, v]);
253
+ (0, import_react.useEffect)(() => {
254
+ const c = hueRef.current;
255
+ if (!c) return;
256
+ const ctx = c.getContext("2d");
257
+ const W = c.width, H = c.height;
258
+ const grad = ctx.createLinearGradient(0, 0, W, 0);
259
+ for (let i = 0; i <= 360; i += 30) {
260
+ const [r, g, b] = hsv2rgb(i, 1, 1);
261
+ grad.addColorStop(i / 360, `rgb(${r},${g},${b})`);
262
+ }
263
+ ctx.fillStyle = grad;
264
+ ctx.fillRect(0, 0, W, H);
265
+ const tx = h / 360 * W;
266
+ ctx.beginPath();
267
+ ctx.roundRect(tx - 4, -1, 8, H + 2, 3);
268
+ ctx.fillStyle = "#fff";
269
+ ctx.fill();
270
+ ctx.strokeStyle = "rgba(0,0,0,0.3)";
271
+ ctx.lineWidth = 1;
272
+ ctx.stroke();
273
+ }, [h]);
274
+ const updateSV = (0, import_react.useCallback)((e) => {
275
+ const c = svRef.current;
276
+ if (!c) return;
277
+ const rect = c.getBoundingClientRect();
278
+ const ns = Math.max(0, Math.min(1, (e.clientX - rect.left) / c.width));
279
+ const nv = Math.max(0, Math.min(1, 1 - (e.clientY - rect.top) / c.height));
280
+ const next = [h, ns, nv];
281
+ setHsv(next);
282
+ setHexInput(rgb2hex(...hsv2rgb(...next)));
283
+ }, [h]);
284
+ const updateHue = (0, import_react.useCallback)((e) => {
285
+ const c = hueRef.current;
286
+ if (!c) return;
287
+ const rect = c.getBoundingClientRect();
288
+ const nh = Math.max(0, Math.min(360, (e.clientX - rect.left) / c.width * 360));
289
+ const next = [nh, s, v];
290
+ setHsv(next);
291
+ setHexInput(rgb2hex(...hsv2rgb(...next)));
292
+ }, [s, v]);
293
+ (0, import_react.useEffect)(() => {
294
+ const mm = (e) => {
295
+ if (svDrag.current) updateSV(e);
296
+ if (hueDrag.current) updateHue(e);
297
+ };
298
+ const mu = () => {
299
+ svDrag.current = false;
300
+ hueDrag.current = false;
301
+ };
302
+ window.addEventListener("mousemove", mm);
303
+ window.addEventListener("mouseup", mu);
304
+ return () => {
305
+ window.removeEventListener("mousemove", mm);
306
+ window.removeEventListener("mouseup", mu);
307
+ };
308
+ }, [updateSV, updateHue]);
309
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
310
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
311
+ "button",
312
+ {
313
+ onMouseDown: onBack,
314
+ style: { background: "none", border: "none", cursor: "pointer", color: "#666", fontSize: 12, display: "flex", alignItems: "center", gap: 4, padding: "2px 4px", borderRadius: 3, marginBottom: 8 },
315
+ children: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 2L4 6L8 10", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" }) }),
317
+ "Back"
318
+ ]
319
+ }
320
+ ),
321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
322
+ "canvas",
323
+ {
324
+ ref: svRef,
325
+ width: 180,
326
+ height: 150,
327
+ style: { display: "block", borderRadius: 4, cursor: "crosshair", marginBottom: 8 },
328
+ onMouseDown: (e) => {
329
+ svDrag.current = true;
330
+ updateSV(e);
331
+ }
332
+ }
333
+ ),
334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
335
+ "canvas",
336
+ {
337
+ ref: hueRef,
338
+ width: 180,
339
+ height: 14,
340
+ style: { display: "block", borderRadius: 4, cursor: "ew-resize", marginBottom: 10 },
341
+ onMouseDown: (e) => {
342
+ hueDrag.current = true;
343
+ updateHue(e);
344
+ }
345
+ }
346
+ ),
347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }, children: [
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: 28, height: 28, borderRadius: 4, background: currentHex, border: "1px solid rgba(0,0,0,0.15)", flexShrink: 0 } }),
349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
350
+ "input",
351
+ {
352
+ value: hexInput,
353
+ onChange: (e) => {
354
+ setHexInput(e.target.value);
355
+ if (/^#[0-9a-fA-F]{6}$/.test(e.target.value)) setHsv(hex2hsv(e.target.value));
356
+ },
357
+ style: { flex: 1, height: 28, border: "1px solid #ccc", borderRadius: 4, padding: "0 8px", fontSize: 12, fontFamily: "var(--font-mono)", outline: "none" },
358
+ spellCheck: false,
359
+ placeholder: "#000000"
360
+ }
361
+ )
362
+ ] }),
363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
364
+ "button",
365
+ {
366
+ onMouseDown: () => onApply(currentHex),
367
+ style: { width: "100%", height: 28, background: "#1a6fc4", color: "#fff", border: "none", borderRadius: 4, fontSize: 12, fontWeight: 600, cursor: "pointer" },
368
+ children: "Apply"
369
+ }
370
+ )
371
+ ] });
372
+ }
373
+ function ColorPicker({ color, onColor, onRemove, onClose }) {
374
+ const [custom, setCustom] = (0, import_react.useState)(false);
375
+ const pick = (c) => {
376
+ onColor(c);
377
+ onClose();
378
+ };
379
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-cpick", children: custom ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CustomColorPicker, { initialColor: color, onApply: pick, onBack: () => setCustom(false) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-cgrid", children: PALETTE.map((hex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
381
+ "div",
382
+ {
383
+ className: `rte-ccell${color.toLowerCase() === hex.toLowerCase() ? " sel" : ""}`,
384
+ style: { background: hex },
385
+ title: hex,
386
+ onMouseDown: () => pick(hex)
387
+ },
388
+ hex
389
+ )) }),
390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-cactions", children: [
391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-caction", title: "Black", onMouseDown: () => pick("#000000"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "12", height: "12", rx: "2", fill: "#000" }),
393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 8L7 10L11 6", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
394
+ ] }) }),
395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-caction", title: "Remove color", onMouseDown: () => {
396
+ onRemove();
397
+ onClose();
398
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
399
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "7.5", width: "12", height: "1.2", rx: ".6", fill: "#e74c3c", transform: "rotate(-30 8 8)" }),
400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "1", y: "11", width: "14", height: "2", rx: "1", fill: "#e74c3c" })
401
+ ] }) }),
402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-caction", title: "Custom color", onMouseDown: () => setCustom(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "8", r: "6", stroke: "#888", strokeWidth: "1.2", fill: "none" }),
404
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "5.5", cy: "6", r: "1.5", fill: "#e74c3c" }),
405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "10.5", cy: "6", r: "1.5", fill: "#3498db" }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "10.5", r: "1.5", fill: "#2ecc71" })
407
+ ] }) })
408
+ ] })
409
+ ] }) });
410
+ }
181
411
  function TablePicker({ onInsert, onClose }) {
182
412
  const [hover, setHover] = (0, import_react.useState)([0, 0]);
183
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-tp", children: [
184
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-tp-lbl", children: hover[0] > 0 ? `${hover[0]} \xD7 ${hover[1]}` : "Select table size" }),
185
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-tp-grid", children: Array.from({ length: 64 }, (_, i) => {
413
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-tpick", children: [
414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-tplbl", children: hover[0] > 0 ? `${hover[0]} \xD7 ${hover[1]}` : "Select table size" }),
415
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-tpgrid", children: Array.from({ length: 64 }, (_, i) => {
186
416
  const r = Math.floor(i / 8) + 1, c = i % 8 + 1;
187
417
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
188
418
  "div",
189
419
  {
190
- className: `rte-tp-cell${r <= hover[0] && c <= hover[1] ? " on" : ""}`,
420
+ className: `rte-tpcell${r <= hover[0] && c <= hover[1] ? " on" : ""}`,
191
421
  onMouseEnter: () => setHover([r, c]),
192
422
  onMouseDown: (e) => {
193
423
  e.preventDefault();
@@ -202,6 +432,124 @@ function TablePicker({ onInsert, onClose }) {
202
432
  }) })
203
433
  ] });
204
434
  }
435
+ function ImagePicker({ onInsert, onClose }) {
436
+ const [tab, setTab] = (0, import_react.useState)("upload");
437
+ const [url, setUrl] = (0, import_react.useState)("");
438
+ const [preview, setPreview] = (0, import_react.useState)(null);
439
+ const [dragging, setDragging] = (0, import_react.useState)(false);
440
+ const [urlError, setUrlError] = (0, import_react.useState)(false);
441
+ const fileRef = (0, import_react.useRef)(null);
442
+ const readFile = (file) => {
443
+ if (!file.type.startsWith("image/")) return;
444
+ const reader = new FileReader();
445
+ reader.onload = () => setPreview(reader.result);
446
+ reader.readAsDataURL(file);
447
+ };
448
+ const canInsert = tab === "upload" ? !!preview : !!url.trim() && !urlError;
449
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-ipick", children: [
450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-itabs", children: ["upload", "url"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `rte-itab${tab === t ? " active" : ""}`, onMouseDown: () => setTab(t), children: t === "upload" ? "Upload / Drop" : "Image URL" }, t)) }),
451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-ipbody", children: [
452
+ tab === "upload" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
454
+ "div",
455
+ {
456
+ className: `rte-idrop${dragging ? " drag" : ""}`,
457
+ onDragOver: (e) => {
458
+ e.preventDefault();
459
+ setDragging(true);
460
+ },
461
+ onDragLeave: () => setDragging(false),
462
+ onDrop: (e) => {
463
+ e.preventDefault();
464
+ setDragging(false);
465
+ const f = e.dataTransfer.files[0];
466
+ if (f) readFile(f);
467
+ },
468
+ onMouseDown: () => {
469
+ var _a;
470
+ return (_a = fileRef.current) == null ? void 0 : _a.click();
471
+ },
472
+ children: preview ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: preview, alt: "preview", style: { maxWidth: "100%", maxHeight: 130, objectFit: "contain", borderRadius: 4, border: "1px solid #eee" } }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
473
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", children: [
474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "1", y: "4", width: "26", height: "20", rx: "3", stroke: "#aaa", strokeWidth: "1.5" }),
475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "9", cy: "10", r: "2.5", stroke: "#aaa", strokeWidth: "1.5" }),
476
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M1 20L9 13L14 18L19 13.5L27 21", stroke: "#aaa", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
477
+ ] }),
478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 12, color: "#888" }, children: [
479
+ "Drop image or ",
480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#1a5fb4", textDecoration: "underline" }, children: "browse" })
481
+ ] }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, color: "#bbb" }, children: "PNG, JPG, GIF, WEBP" })
483
+ ] })
484
+ }
485
+ ),
486
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
487
+ "input",
488
+ {
489
+ ref: fileRef,
490
+ type: "file",
491
+ accept: "image/*",
492
+ style: { display: "none" },
493
+ onChange: (e) => {
494
+ var _a;
495
+ const f = (_a = e.target.files) == null ? void 0 : _a[0];
496
+ if (f) readFile(f);
497
+ e.target.value = "";
498
+ }
499
+ }
500
+ ),
501
+ preview && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
502
+ "button",
503
+ {
504
+ onMouseDown: () => setPreview(null),
505
+ style: { fontSize: 11, color: "#e74c3c", background: "none", border: "none", cursor: "pointer", padding: 0, alignSelf: "flex-start" },
506
+ children: "\u2715 Remove"
507
+ }
508
+ )
509
+ ] }),
510
+ tab === "url" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
512
+ "input",
513
+ {
514
+ type: "text",
515
+ value: url,
516
+ onChange: (e) => {
517
+ setUrl(e.target.value);
518
+ setUrlError(false);
519
+ },
520
+ placeholder: "https://example.com/image.png",
521
+ style: { width: "100%", height: 30, border: `1px solid ${urlError ? "#e74c3c" : "#ccc"}`, borderRadius: 4, padding: "0 8px", fontSize: 12, outline: "none", fontFamily: "var(--font-sans)" }
522
+ }
523
+ ),
524
+ url && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
525
+ "img",
526
+ {
527
+ src: url,
528
+ alt: "preview",
529
+ style: { maxWidth: "100%", maxHeight: 130, objectFit: "contain", borderRadius: 4, border: "1px solid #eee", display: "block" },
530
+ onError: () => setUrlError(true)
531
+ }
532
+ ),
533
+ urlError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, color: "#e74c3c" }, children: "Could not load image from this URL." })
534
+ ] }),
535
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
536
+ "button",
537
+ {
538
+ className: "rte-iinsert",
539
+ disabled: !canInsert,
540
+ onMouseDown: () => {
541
+ const src = tab === "url" ? url.trim() : preview;
542
+ if (src) {
543
+ onInsert(src);
544
+ onClose();
545
+ }
546
+ },
547
+ children: "Insert Image"
548
+ }
549
+ )
550
+ ] })
551
+ ] });
552
+ }
205
553
  function IcoUL() {
206
554
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
207
555
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "2", cy: "4", r: "1.3", fill: "currentColor" }),
@@ -266,6 +614,13 @@ function IcoTable() {
266
614
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "10", y1: "5.5", x2: "10", y2: "14", stroke: "currentColor", strokeWidth: "1.1" })
267
615
  ] });
268
616
  }
617
+ function IcoImage() {
618
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "1", y: "2", width: "13", height: "11", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "5", cy: "5.5", r: "1.2", fill: "currentColor" }),
621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M1 10L4.5 7L7 9.5L9.5 7L14 11", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
622
+ ] });
623
+ }
269
624
  function IcoRowAbove() {
270
625
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
271
626
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "1", y: "7", width: "14", height: "8", rx: "1", stroke: "currentColor", strokeWidth: "1.1" }),
@@ -343,10 +698,15 @@ function getCurrentTable() {
343
698
  var _a;
344
699
  return (_a = getCurrentCell()) == null ? void 0 : _a.closest("table");
345
700
  }
701
+ function getCurrentLink() {
702
+ var _a;
703
+ const node = (_a = window.getSelection()) == null ? void 0 : _a.anchorNode;
704
+ const el = (node == null ? void 0 : node.nodeType) === 1 ? node : node == null ? void 0 : node.parentElement;
705
+ return el == null ? void 0 : el.closest("a");
706
+ }
346
707
  function getCellCoords(table, cell) {
347
- const rows = Array.from(table.rows);
348
- for (let r = 0; r < rows.length; r++) {
349
- const cells = Array.from(rows[r].cells);
708
+ for (let r = 0; r < table.rows.length; r++) {
709
+ const cells = Array.from(table.rows[r].cells);
350
710
  for (let c = 0; c < cells.length; c++) {
351
711
  if (cells[c] === cell) return [r, c];
352
712
  }
@@ -360,9 +720,10 @@ function getCellsInRange(table, a, b) {
360
720
  const [r2, c2] = [Math.max(ca[0], cb[0]), Math.max(ca[1], cb[1])];
361
721
  const result = [];
362
722
  Array.from(table.rows).forEach((row, ri) => {
363
- if (ri >= r1 && ri <= r2) Array.from(row.cells).forEach((cell, ci) => {
364
- if (ci >= c1 && ci <= c2) result.push(cell);
365
- });
723
+ if (ri >= r1 && ri <= r2)
724
+ Array.from(row.cells).forEach((cell, ci) => {
725
+ if (ci >= c1 && ci <= c2) result.push(cell);
726
+ });
366
727
  });
367
728
  return result;
368
729
  }
@@ -370,30 +731,32 @@ function RichTextEditor({ value, onChange }) {
370
731
  var _a;
371
732
  const editorRef = (0, import_react.useRef)(null);
372
733
  const editorAreaRef = (0, import_react.useRef)(null);
373
- const colorRef = (0, import_react.useRef)(null);
374
734
  const savedRangeRef = (0, import_react.useRef)(null);
375
735
  const dragStartCell = (0, import_react.useRef)(null);
376
736
  const isDragging = (0, import_react.useRef)(false);
377
737
  const [isCode, setIsCode] = (0, import_react.useState)(false);
378
738
  const [codeVal, setCodeVal] = (0, import_react.useState)("");
379
739
  const [fmt, setFmt] = (0, import_react.useState)({ block: "p" });
380
- const [color, setColor] = (0, import_react.useState)("#e74c3c");
740
+ const [color, setColor] = (0, import_react.useState)("#000000");
381
741
  const [words, setWords] = (0, import_react.useState)(0);
382
742
  const [linkBar, setLinkBar] = (0, import_react.useState)(false);
383
743
  const [linkUrl, setLinkUrl] = (0, import_react.useState)("https://");
384
744
  const [showTable, setShowTable] = (0, import_react.useState)(false);
745
+ const [showColor, setShowColor] = (0, import_react.useState)(false);
746
+ const [showImagePicker, setShowImagePicker] = (0, import_react.useState)(false);
385
747
  const [selCells, setSelCells] = (0, import_react.useState)([]);
386
748
  const [tableFP, setTableFP] = (0, import_react.useState)(null);
387
749
  const [linkFP, setLinkFP] = (0, import_react.useState)(null);
388
- const calcFloat = (0, import_react.useCallback)((el, toolbarW) => {
750
+ const [linkInfoFP, setLinkInfoFP] = (0, import_react.useState)(null);
751
+ const [selectedImg, setSelectedImg] = (0, import_react.useState)(null);
752
+ const [imgFP, setImgFP] = (0, import_react.useState)(null);
753
+ const calcFloat = (0, import_react.useCallback)((el, w) => {
389
754
  const area = editorAreaRef.current;
390
- const er = el.getBoundingClientRect();
391
- const ar = area.getBoundingClientRect();
392
- const arrowCenter = er.left + er.width / 2 - ar.left;
393
- const rawLeft = arrowCenter - toolbarW / 2;
394
- const left = Math.max(4, Math.min(rawLeft, ar.width - toolbarW - 4));
395
- const arrowLeft = Math.max(8, Math.min(arrowCenter - left - 7, toolbarW - 22));
396
- return { top: er.bottom - ar.top + 10, left, arrowLeft };
755
+ const er = el.getBoundingClientRect(), ar = area.getBoundingClientRect();
756
+ const ac = er.left + er.width / 2 - ar.left;
757
+ const rawL = ac - w / 2;
758
+ const left = Math.max(4, Math.min(rawL, ar.width - w - 4));
759
+ return { top: er.bottom - ar.top + 10, left, arrowLeft: Math.max(8, Math.min(ac - left - 7, w - 22)) };
397
760
  }, []);
398
761
  const applySelection = (0, import_react.useCallback)((cells) => {
399
762
  var _a2;
@@ -407,35 +770,44 @@ function RichTextEditor({ value, onChange }) {
407
770
  setSelCells([]);
408
771
  dragStartCell.current = null;
409
772
  }, []);
773
+ const selectImage = (0, import_react.useCallback)((img) => {
774
+ var _a2;
775
+ (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll("img.rte-img-sel").forEach((i) => i.classList.remove("rte-img-sel"));
776
+ img.classList.add("rte-img-sel");
777
+ setSelectedImg(img);
778
+ if (editorAreaRef.current) setImgFP(calcFloat(img, 260));
779
+ }, [calcFloat]);
780
+ const clearImageSelection = (0, import_react.useCallback)(() => {
781
+ var _a2;
782
+ (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll("img.rte-img-sel").forEach((i) => i.classList.remove("rte-img-sel"));
783
+ setSelectedImg(null);
784
+ setImgFP(null);
785
+ }, []);
786
+ const fixFontTags = (0, import_react.useCallback)(() => {
787
+ var _a2;
788
+ (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll("font[color]").forEach((font) => {
789
+ var _a3;
790
+ const span = document.createElement("span");
791
+ span.style.color = (_a3 = font.getAttribute("color")) != null ? _a3 : "";
792
+ while (font.firstChild) span.appendChild(font.firstChild);
793
+ font.replaceWith(span);
794
+ });
795
+ }, []);
410
796
  const fixListNesting = (0, import_react.useCallback)(() => {
411
- const editor = editorRef.current;
412
- if (!editor) return;
413
- editor.querySelectorAll("ol > ol, ol > ul, ul > ol, ul > ul").forEach((orphan) => {
797
+ var _a2;
798
+ (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll("ol > ol, ol > ul, ul > ol, ul > ul").forEach((orphan) => {
414
799
  const prev = orphan.previousElementSibling;
415
- if (prev && prev.tagName === "LI") {
416
- prev.appendChild(orphan);
417
- }
800
+ if ((prev == null ? void 0 : prev.tagName) === "LI") prev.appendChild(orphan);
418
801
  });
419
802
  }, []);
420
- const exec = (0, import_react.useCallback)((cmd, val = null) => {
421
- var _a2;
422
- (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
423
- document.execCommand(cmd, false, val != null ? val : void 0);
424
- fixListNesting();
425
- refresh();
426
- }, [fixListNesting]);
427
803
  const refresh = (0, import_react.useCallback)(() => {
428
804
  var _a2, _b, _c, _d;
429
805
  const raw = document.queryCommandValue("formatBlock").toLowerCase().replace(/[<>]/g, "");
430
806
  const block = ["h1", "h2", "h3"].includes(raw) ? raw : "p";
431
- const c = document.queryCommandValue("foreColor");
432
- if (c && c !== "false") {
433
- const m = c.match(/\d+/g);
434
- if (m) setColor("#" + m.slice(0, 3).map((n) => parseInt(n).toString(16).padStart(2, "0")).join(""));
435
- }
436
- const cell = getCurrentCell();
437
- const inTable = !!cell;
807
+ setColor(getColorAtCursor(editorRef.current));
808
+ const cell = getCurrentCell(), inTable = !!cell;
438
809
  const cellMerged = cell ? cell.colSpan > 1 || cell.rowSpan > 1 : false;
810
+ const anchor = getCurrentLink(), inLink = !!anchor;
439
811
  setFmt({
440
812
  bold: document.queryCommandState("bold"),
441
813
  italic: document.queryCommandState("italic"),
@@ -448,15 +820,39 @@ function RichTextEditor({ value, onChange }) {
448
820
  aJ: document.queryCommandState("justifyFull"),
449
821
  block,
450
822
  inTable,
451
- cellMerged
823
+ cellMerged,
824
+ inLink,
825
+ linkHref: anchor == null ? void 0 : anchor.href
452
826
  });
453
827
  if (cell && editorAreaRef.current) setTableFP(calcFloat(cell, 370));
454
828
  else setTableFP(null);
829
+ if (anchor && editorAreaRef.current && !linkBar) setLinkInfoFP(calcFloat(anchor, 290));
830
+ else setLinkInfoFP(null);
455
831
  const txt = (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerText) != null ? _b : "";
456
832
  setWords(txt.trim() ? txt.trim().split(/\s+/).length : 0);
457
833
  onChange == null ? void 0 : onChange((_d = (_c = editorRef.current) == null ? void 0 : _c.innerHTML) != null ? _d : "");
458
- }, [onChange, calcFloat]);
834
+ }, [onChange, calcFloat, linkBar]);
835
+ const exec = (0, import_react.useCallback)((cmd, val = null) => {
836
+ var _a2;
837
+ (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
838
+ document.execCommand(cmd, false, val != null ? val : void 0);
839
+ fixFontTags();
840
+ fixListNesting();
841
+ refresh();
842
+ }, [fixFontTags, fixListNesting, refresh]);
843
+ const insertImage = (0, import_react.useCallback)((src) => {
844
+ var _a2;
845
+ (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
846
+ document.execCommand("insertHTML", false, `<img src="${src}" alt="" style="max-width:100%;height:auto;" />`);
847
+ refresh();
848
+ }, [refresh]);
459
849
  const handleEditorMouseDown = (0, import_react.useCallback)((e) => {
850
+ if (e.target.tagName === "IMG") {
851
+ e.preventDefault();
852
+ selectImage(e.target);
853
+ return;
854
+ }
855
+ clearImageSelection();
460
856
  const target = e.target.closest("td, th");
461
857
  if (!target) {
462
858
  clearSelection();
@@ -473,7 +869,7 @@ function RichTextEditor({ value, onChange }) {
473
869
  dragStartCell.current = target;
474
870
  isDragging.current = true;
475
871
  applySelection([target]);
476
- }, [applySelection, clearSelection]);
872
+ }, [applySelection, clearSelection, selectImage, clearImageSelection]);
477
873
  const handleEditorMouseMove = (0, import_react.useCallback)((e) => {
478
874
  if (!isDragging.current || !dragStartCell.current) return;
479
875
  const target = e.target.closest("td, th");
@@ -486,12 +882,10 @@ function RichTextEditor({ value, onChange }) {
486
882
  applySelection(cells);
487
883
  }
488
884
  }, [applySelection]);
489
- const handleEditorMouseUp = (0, import_react.useCallback)(() => {
490
- isDragging.current = false;
491
- }, []);
492
885
  const handleKeyDown = (0, import_react.useCallback)((e) => {
493
886
  var _a2, _b;
494
887
  clearSelection();
888
+ clearImageSelection();
495
889
  if (e.key === "Tab") {
496
890
  e.preventDefault();
497
891
  const cell = getCurrentCell();
@@ -523,14 +917,14 @@ function RichTextEditor({ value, onChange }) {
523
917
  exec("underline");
524
918
  }
525
919
  }
526
- }, [exec, clearSelection]);
920
+ }, [exec, clearSelection, clearImageSelection]);
527
921
  const insertTable = (rows, cols) => {
528
922
  var _a2;
529
923
  (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
530
- let html = `<table>`;
924
+ let html = "<table>";
531
925
  for (let r = 0; r < rows; r++) {
532
926
  html += "<tr>";
533
- for (let c = 0; c < cols; c++) html += r === 0 ? `<th><br></th>` : `<td><br></td>`;
927
+ for (let c = 0; c < cols; c++) html += r === 0 ? "<th><br></th>" : "<td><br></td>";
534
928
  html += "</tr>";
535
929
  }
536
930
  html += "</table><p><br></p>";
@@ -625,11 +1019,7 @@ function RichTextEditor({ value, onChange }) {
625
1019
  savedRangeRef.current = (sel == null ? void 0 : sel.rangeCount) ? sel.getRangeAt(0).cloneRange() : null;
626
1020
  if ((sel == null ? void 0 : sel.rangeCount) && editorAreaRef.current) {
627
1021
  const rect = sel.getRangeAt(0).getBoundingClientRect();
628
- if (rect.width > 0 || rect.height > 0) setLinkFP(calcFloat({ getBoundingClientRect: () => rect }, 360));
629
- else {
630
- const area = editorAreaRef.current.getBoundingClientRect();
631
- setLinkFP({ top: 60, left: 20, arrowLeft: 20 });
632
- }
1022
+ setLinkFP(rect.width > 0 || rect.height > 0 ? calcFloat({ getBoundingClientRect: () => rect }, 360) : { top: 60, left: 20, arrowLeft: 20 });
633
1023
  }
634
1024
  setLinkUrl("https://");
635
1025
  setLinkBar(true);
@@ -733,7 +1123,7 @@ function RichTextEditor({ value, onChange }) {
733
1123
  };
734
1124
  const copyHtml = () => {
735
1125
  var _a2, _b;
736
- navigator.clipboard.writeText(isCode ? codeVal : (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b : "").catch(() => {
1126
+ return navigator.clipboard.writeText(isCode ? codeVal : (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b : "").catch(() => {
737
1127
  });
738
1128
  };
739
1129
  (0, import_react.useEffect)(() => {
@@ -742,14 +1132,8 @@ function RichTextEditor({ value, onChange }) {
742
1132
  refresh();
743
1133
  }
744
1134
  }, []);
745
- (0, import_react.useEffect)(() => {
746
- if (!showTable) return;
747
- const h = () => setShowTable(false);
748
- document.addEventListener("mousedown", h);
749
- return () => document.removeEventListener("mousedown", h);
750
- }, [showTable]);
751
- const canMerge = selCells.length >= 2;
752
- const canSplit = fmt.cellMerged;
1135
+ const canMerge = selCells.length >= 2, canSplit = fmt.cellMerged;
1136
+ const backdrop = { position: "fixed", inset: 0, zIndex: 199 };
753
1137
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "1rem 0" }, children: [
754
1138
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: CSS }),
755
1139
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { border: "1px solid #d8d8d8", borderRadius: 4, boxShadow: "0 1px 3px rgba(0,0,0,0.06)" }, children: [
@@ -758,26 +1142,29 @@ function RichTextEditor({ value, onChange }) {
758
1142
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => exec("bold"), title: "Bold (Ctrl+B)", active: fmt.bold, style: { fontWeight: 800, fontFamily: "Georgia,serif" }, children: "B" }),
759
1143
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => exec("italic"), title: "Italic (Ctrl+I)", active: fmt.italic, style: { fontStyle: "italic", fontFamily: "Georgia,serif" }, children: "I" }),
760
1144
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => exec("underline"), title: "Underline (Ctrl+U)", active: fmt.underline, style: { textDecoration: "underline" }, children: "U" }),
761
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Btn, { onClick: () => {
762
- var _a2;
763
- return (_a2 = colorRef.current) == null ? void 0 : _a2.click();
764
- }, title: "Text color", style: { flexDirection: "column", gap: 1, padding: "3px 6px" }, children: [
765
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 13, fontWeight: 800, fontFamily: "Georgia,serif", lineHeight: 1, color: "#222" }, children: "A" }),
766
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rte-swatch", style: { background: color } })
1145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative", display: "inline-flex" }, children: [
1146
+ showColor && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: backdrop, onMouseDown: () => setShowColor(false) }),
1147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Btn, { onClick: () => setShowColor((v) => !v), title: "Text color", active: showColor, style: { flexDirection: "column", gap: 1, padding: "3px 6px" }, children: [
1148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 13, fontWeight: 800, fontFamily: "Georgia,serif", lineHeight: 1, color: "#222" }, children: "A" }),
1149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rte-swatch", style: { background: color } })
1150
+ ] }),
1151
+ showColor && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", top: 34, left: -4, zIndex: 200 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1152
+ ColorPicker,
1153
+ {
1154
+ color,
1155
+ onColor: (c) => {
1156
+ setColor(c);
1157
+ exec("foreColor", c);
1158
+ setShowColor(false);
1159
+ },
1160
+ onRemove: () => {
1161
+ exec("removeFormat");
1162
+ setShowColor(false);
1163
+ },
1164
+ onClose: () => setShowColor(false)
1165
+ }
1166
+ ) })
767
1167
  ] }),
768
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
769
- "input",
770
- {
771
- ref: colorRef,
772
- type: "color",
773
- value: color,
774
- onChange: (e) => {
775
- setColor(e.target.value);
776
- exec("foreColor", e.target.value);
777
- },
778
- style: { position: "absolute", opacity: 0, width: 0, height: 0, pointerEvents: "none" }
779
- }
780
- ),
781
1168
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
782
1169
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
783
1170
  "select",
@@ -815,12 +1202,21 @@ function RichTextEditor({ value, onChange }) {
815
1202
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
816
1203
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: openLinkBar, title: "Insert link", active: linkBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoLink, {}) }),
817
1204
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: insertCodeBlock, title: "Code block", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoCode, {}) }),
818
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-tp-wrap", onMouseDown: (e) => e.stopPropagation(), children: [
1205
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative", display: "inline-flex" }, children: [
1206
+ showImagePicker && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: backdrop, onMouseDown: () => setShowImagePicker(false) }),
1207
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => setShowImagePicker((v) => !v), title: "Insert image", active: showImagePicker, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoImage, {}) }),
1208
+ showImagePicker && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", top: 34, left: 0, zIndex: 200 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImagePicker, { onInsert: (src) => {
1209
+ insertImage(src);
1210
+ setShowImagePicker(false);
1211
+ }, onClose: () => setShowImagePicker(false) }) })
1212
+ ] }),
1213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative", display: "inline-flex" }, children: [
1214
+ showTable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: backdrop, onMouseDown: () => setShowTable(false) }),
819
1215
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => setShowTable((v) => !v), title: "Insert table", active: showTable || !!fmt.inTable, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoTable, {}) }),
820
- showTable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TablePicker, { onInsert: (r, c) => {
1216
+ showTable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", top: 34, left: 0, zIndex: 200 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TablePicker, { onInsert: (r, c) => {
821
1217
  insertTable(r, c);
822
1218
  setShowTable(false);
823
- }, onClose: () => setShowTable(false) })
1219
+ }, onClose: () => setShowTable(false) }) })
824
1220
  ] }),
825
1221
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {})
826
1222
  ] }),
@@ -863,29 +1259,120 @@ function RichTextEditor({ value, onChange }) {
863
1259
  "data-ph": "Start typing...",
864
1260
  onMouseDown: handleEditorMouseDown,
865
1261
  onMouseMove: handleEditorMouseMove,
866
- onMouseUp: handleEditorMouseUp,
1262
+ onMouseUp: () => {
1263
+ isDragging.current = false;
1264
+ },
867
1265
  onKeyDown: handleKeyDown,
868
1266
  onKeyUp: refresh,
869
1267
  onSelect: refresh,
1268
+ onDrop: (e) => {
1269
+ var _a2;
1270
+ const file = (_a2 = e.dataTransfer.files) == null ? void 0 : _a2[0];
1271
+ if (!(file == null ? void 0 : file.type.startsWith("image/"))) return;
1272
+ e.preventDefault();
1273
+ const reader = new FileReader();
1274
+ reader.onload = () => insertImage(reader.result);
1275
+ reader.readAsDataURL(file);
1276
+ },
1277
+ onDragOver: (e) => {
1278
+ if (e.dataTransfer.types.includes("Files")) e.preventDefault();
1279
+ },
870
1280
  style: { display: isCode ? "none" : "block" }
871
1281
  }
872
1282
  ),
1283
+ !isCode && selectedImg && imgFP && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-float", style: { top: imgFP.top, left: imgFP.left, gap: 2 }, children: [
1284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-float-arrow", style: { left: imgFP.arrowLeft } }),
1285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { title: "Align left", onClick: () => {
1286
+ selectedImg.style.marginLeft = "0";
1287
+ selectedImg.style.marginRight = "auto";
1288
+ selectImage(selectedImg);
1289
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlignIco, { t: "left" }) }),
1290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { title: "Align center", onClick: () => {
1291
+ selectedImg.style.marginLeft = "auto";
1292
+ selectedImg.style.marginRight = "auto";
1293
+ selectImage(selectedImg);
1294
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlignIco, { t: "center" }) }),
1295
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { title: "Align right", onClick: () => {
1296
+ selectedImg.style.marginLeft = "auto";
1297
+ selectedImg.style.marginRight = "0";
1298
+ selectImage(selectedImg);
1299
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlignIco, { t: "right" }) }),
1300
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
1301
+ [25, 50, 75, 100].map((pct) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1302
+ Btn,
1303
+ {
1304
+ title: `Width ${pct}%`,
1305
+ onClick: () => {
1306
+ selectedImg.style.width = `${pct}%`;
1307
+ selectImage(selectedImg);
1308
+ },
1309
+ style: { fontSize: 10, minWidth: 28, padding: "0 4px" },
1310
+ children: [
1311
+ pct,
1312
+ "%"
1313
+ ]
1314
+ },
1315
+ pct
1316
+ )),
1317
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
1318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { title: "Delete image", danger: true, onClick: () => {
1319
+ selectedImg.remove();
1320
+ clearImageSelection();
1321
+ refresh();
1322
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "13", height: "13", viewBox: "0 0 13 13", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 2L11 11M11 2L2 11", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" }) }) })
1323
+ ] }),
873
1324
  !isCode && fmt.inTable && tableFP && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-float", style: { top: tableFP.top, left: tableFP.left }, children: [
874
1325
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-float-arrow", style: { left: tableFP.arrowLeft } }),
875
1326
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addRowAbove"), title: "Add row above", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoRowAbove, {}) }),
876
1327
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addRowBelow"), title: "Add row below", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoRowBelow, {}) }),
877
1328
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("deleteRow"), title: "Delete row", danger: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoDelRow, {}) }),
878
1329
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
879
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addColLeft"), title: "Add column left", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoColLeft, {}) }),
880
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addColRight"), title: "Add column right", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoColRight, {}) }),
881
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("deleteCol"), title: "Delete column", danger: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoDelCol, {}) }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addColLeft"), title: "Add col left", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoColLeft, {}) }),
1331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addColRight"), title: "Add col right", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoColRight, {}) }),
1332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("deleteCol"), title: "Delete col", danger: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoDelCol, {}) }),
882
1333
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
883
1334
  canMerge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => doMerge(selCells), title: `Merge ${selCells.length} cells`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoMerge, {}) }),
884
1335
  canSplit && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: doSplit, title: "Split merged cell", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoSplit, {}) }),
885
1336
  (canMerge || canSplit) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
886
1337
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("deleteTable"), title: "Delete table", danger: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoDelTable, {}) })
887
1338
  ] }),
888
- !isCode && linkBar && linkFP && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-float rte-link-float", style: { top: linkFP.top, left: linkFP.left }, children: [
1339
+ !isCode && fmt.inLink && linkInfoFP && !linkBar && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-float", style: { top: linkInfoFP.top, left: linkInfoFP.left, gap: 4 }, children: [
1340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-float-arrow", style: { left: linkInfoFP.arrowLeft } }),
1341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoLink, {}),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: "#1a6fc4", maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title: fmt.linkHref, children: fmt.linkHref }),
1343
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { title: "Edit link", onClick: () => {
1345
+ var _a2;
1346
+ const anchor = getCurrentLink();
1347
+ if (!anchor) return;
1348
+ const sel = window.getSelection();
1349
+ const range = document.createRange();
1350
+ range.selectNodeContents(anchor);
1351
+ sel == null ? void 0 : sel.removeAllRanges();
1352
+ sel == null ? void 0 : sel.addRange(range);
1353
+ savedRangeRef.current = range.cloneRange();
1354
+ if (editorAreaRef.current) setLinkFP(calcFloat(anchor, 360));
1355
+ setLinkUrl((_a2 = anchor.getAttribute("href")) != null ? _a2 : "https://");
1356
+ setLinkBar(true);
1357
+ setLinkInfoFP(null);
1358
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "13", height: "13", viewBox: "0 0 13 13", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 2L11 4L4.5 10.5H2.5V8.5L9 2Z", stroke: "currentColor", strokeWidth: "1.2", strokeLinejoin: "round" }) }) }),
1359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { title: "Remove link", danger: true, onClick: () => {
1360
+ const anchor = getCurrentLink();
1361
+ if (!anchor) return;
1362
+ const sel = window.getSelection();
1363
+ const range = document.createRange();
1364
+ range.selectNodeContents(anchor);
1365
+ sel == null ? void 0 : sel.removeAllRanges();
1366
+ sel == null ? void 0 : sel.addRange(range);
1367
+ document.execCommand("unlink", false);
1368
+ refresh();
1369
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "13", height: "13", viewBox: "0 0 13 13", fill: "none", children: [
1370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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" }),
1371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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" }),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 2L11 11", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
1373
+ ] }) })
1374
+ ] }),
1375
+ !isCode && linkBar && linkFP && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-float rte-linkbar", style: { top: linkFP.top, left: linkFP.left }, children: [
889
1376
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-float-arrow", style: { left: linkFP.arrowLeft } }),
890
1377
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoLink, {}),
891
1378
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -905,11 +1392,11 @@ function RichTextEditor({ value, onChange }) {
905
1392
  placeholder: "https://example.com"
906
1393
  }
907
1394
  ),
908
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "rte-link-apply", onClick: applyLink, children: "Apply" }),
909
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "rte-link-cancel", onClick: () => {
1395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: applyLink, style: { height: 26, padding: "0 10px", background: "#1a6fc4", color: "#fff", border: "none", borderRadius: 3, fontSize: 12, fontWeight: 600, cursor: "pointer" }, children: "Apply" }),
1396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => {
910
1397
  setLinkBar(false);
911
1398
  setLinkFP(null);
912
- }, children: "\u2715" })
1399
+ }, style: { height: 26, padding: "0 8px", background: "transparent", color: "#666", border: "1px solid #ccc", borderRadius: 3, fontSize: 12, cursor: "pointer" }, children: "\u2715" })
913
1400
  ] })
914
1401
  ] }),
915
1402
  !isCode && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-footer", children: [
@@ -919,7 +1406,7 @@ function RichTextEditor({ value, onChange }) {
919
1406
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Shift+click" }),
920
1407
  " to select cells \xA0\xB7\xA0 ",
921
1408
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Tab" }),
922
- " moves between cells"
1409
+ " moves between cells \xA0\xB7\xA0 Drop an image file to insert"
923
1410
  ] })
924
1411
  ] })
925
1412
  ] });