@malaya_jeeva/rich-text-editor 1.0.3 → 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 +713 -215
  2. package/dist/index.mjs +713 -215
  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,24 +770,44 @@ function RichTextEditor({ value, onChange }) {
407
770
  setSelCells([]);
408
771
  dragStartCell.current = null;
409
772
  }, []);
410
- const exec = (0, import_react.useCallback)((cmd, val = null) => {
773
+ const selectImage = (0, import_react.useCallback)((img) => {
411
774
  var _a2;
412
- (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
413
- document.execCommand(cmd, false, val != null ? val : void 0);
414
- refresh();
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
+ }, []);
796
+ const fixListNesting = (0, import_react.useCallback)(() => {
797
+ var _a2;
798
+ (_a2 = editorRef.current) == null ? void 0 : _a2.querySelectorAll("ol > ol, ol > ul, ul > ol, ul > ul").forEach((orphan) => {
799
+ const prev = orphan.previousElementSibling;
800
+ if ((prev == null ? void 0 : prev.tagName) === "LI") prev.appendChild(orphan);
801
+ });
415
802
  }, []);
416
803
  const refresh = (0, import_react.useCallback)(() => {
417
804
  var _a2, _b, _c, _d;
418
805
  const raw = document.queryCommandValue("formatBlock").toLowerCase().replace(/[<>]/g, "");
419
806
  const block = ["h1", "h2", "h3"].includes(raw) ? raw : "p";
420
- const c = document.queryCommandValue("foreColor");
421
- if (c && c !== "false") {
422
- const m = c.match(/\d+/g);
423
- if (m) setColor("#" + m.slice(0, 3).map((n) => parseInt(n).toString(16).padStart(2, "0")).join(""));
424
- }
425
- const cell = getCurrentCell();
426
- const inTable = !!cell;
807
+ setColor(getColorAtCursor(editorRef.current));
808
+ const cell = getCurrentCell(), inTable = !!cell;
427
809
  const cellMerged = cell ? cell.colSpan > 1 || cell.rowSpan > 1 : false;
810
+ const anchor = getCurrentLink(), inLink = !!anchor;
428
811
  setFmt({
429
812
  bold: document.queryCommandState("bold"),
430
813
  italic: document.queryCommandState("italic"),
@@ -437,15 +820,39 @@ function RichTextEditor({ value, onChange }) {
437
820
  aJ: document.queryCommandState("justifyFull"),
438
821
  block,
439
822
  inTable,
440
- cellMerged
823
+ cellMerged,
824
+ inLink,
825
+ linkHref: anchor == null ? void 0 : anchor.href
441
826
  });
442
827
  if (cell && editorAreaRef.current) setTableFP(calcFloat(cell, 370));
443
828
  else setTableFP(null);
829
+ if (anchor && editorAreaRef.current && !linkBar) setLinkInfoFP(calcFloat(anchor, 290));
830
+ else setLinkInfoFP(null);
444
831
  const txt = (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerText) != null ? _b : "";
445
832
  setWords(txt.trim() ? txt.trim().split(/\s+/).length : 0);
446
833
  onChange == null ? void 0 : onChange((_d = (_c = editorRef.current) == null ? void 0 : _c.innerHTML) != null ? _d : "");
447
- }, [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]);
448
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();
449
856
  const target = e.target.closest("td, th");
450
857
  if (!target) {
451
858
  clearSelection();
@@ -462,7 +869,7 @@ function RichTextEditor({ value, onChange }) {
462
869
  dragStartCell.current = target;
463
870
  isDragging.current = true;
464
871
  applySelection([target]);
465
- }, [applySelection, clearSelection]);
872
+ }, [applySelection, clearSelection, selectImage, clearImageSelection]);
466
873
  const handleEditorMouseMove = (0, import_react.useCallback)((e) => {
467
874
  if (!isDragging.current || !dragStartCell.current) return;
468
875
  const target = e.target.closest("td, th");
@@ -475,12 +882,10 @@ function RichTextEditor({ value, onChange }) {
475
882
  applySelection(cells);
476
883
  }
477
884
  }, [applySelection]);
478
- const handleEditorMouseUp = (0, import_react.useCallback)(() => {
479
- isDragging.current = false;
480
- }, []);
481
885
  const handleKeyDown = (0, import_react.useCallback)((e) => {
482
886
  var _a2, _b;
483
887
  clearSelection();
888
+ clearImageSelection();
484
889
  if (e.key === "Tab") {
485
890
  e.preventDefault();
486
891
  const cell = getCurrentCell();
@@ -512,14 +917,14 @@ function RichTextEditor({ value, onChange }) {
512
917
  exec("underline");
513
918
  }
514
919
  }
515
- }, [exec, clearSelection]);
920
+ }, [exec, clearSelection, clearImageSelection]);
516
921
  const insertTable = (rows, cols) => {
517
922
  var _a2;
518
923
  (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
519
- let html = `<table>`;
924
+ let html = "<table>";
520
925
  for (let r = 0; r < rows; r++) {
521
926
  html += "<tr>";
522
- 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>";
523
928
  html += "</tr>";
524
929
  }
525
930
  html += "</table><p><br></p>";
@@ -614,11 +1019,7 @@ function RichTextEditor({ value, onChange }) {
614
1019
  savedRangeRef.current = (sel == null ? void 0 : sel.rangeCount) ? sel.getRangeAt(0).cloneRange() : null;
615
1020
  if ((sel == null ? void 0 : sel.rangeCount) && editorAreaRef.current) {
616
1021
  const rect = sel.getRangeAt(0).getBoundingClientRect();
617
- if (rect.width > 0 || rect.height > 0) setLinkFP(calcFloat({ getBoundingClientRect: () => rect }, 360));
618
- else {
619
- const area = editorAreaRef.current.getBoundingClientRect();
620
- setLinkFP({ top: 60, left: 20, arrowLeft: 20 });
621
- }
1022
+ setLinkFP(rect.width > 0 || rect.height > 0 ? calcFloat({ getBoundingClientRect: () => rect }, 360) : { top: 60, left: 20, arrowLeft: 20 });
622
1023
  }
623
1024
  setLinkUrl("https://");
624
1025
  setLinkBar(true);
@@ -722,7 +1123,7 @@ function RichTextEditor({ value, onChange }) {
722
1123
  };
723
1124
  const copyHtml = () => {
724
1125
  var _a2, _b;
725
- 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(() => {
726
1127
  });
727
1128
  };
728
1129
  (0, import_react.useEffect)(() => {
@@ -731,14 +1132,8 @@ function RichTextEditor({ value, onChange }) {
731
1132
  refresh();
732
1133
  }
733
1134
  }, []);
734
- (0, import_react.useEffect)(() => {
735
- if (!showTable) return;
736
- const h = () => setShowTable(false);
737
- document.addEventListener("mousedown", h);
738
- return () => document.removeEventListener("mousedown", h);
739
- }, [showTable]);
740
- const canMerge = selCells.length >= 2;
741
- const canSplit = fmt.cellMerged;
1135
+ const canMerge = selCells.length >= 2, canSplit = fmt.cellMerged;
1136
+ const backdrop = { position: "fixed", inset: 0, zIndex: 199 };
742
1137
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "1rem 0" }, children: [
743
1138
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: CSS }),
744
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: [
@@ -747,26 +1142,29 @@ function RichTextEditor({ value, onChange }) {
747
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" }),
748
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" }),
749
1144
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => exec("underline"), title: "Underline (Ctrl+U)", active: fmt.underline, style: { textDecoration: "underline" }, children: "U" }),
750
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Btn, { onClick: () => {
751
- var _a2;
752
- return (_a2 = colorRef.current) == null ? void 0 : _a2.click();
753
- }, title: "Text color", style: { flexDirection: "column", gap: 1, padding: "3px 6px" }, children: [
754
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 13, fontWeight: 800, fontFamily: "Georgia,serif", lineHeight: 1, color: "#222" }, children: "A" }),
755
- /* @__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
+ ) })
756
1167
  ] }),
757
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
758
- "input",
759
- {
760
- ref: colorRef,
761
- type: "color",
762
- value: color,
763
- onChange: (e) => {
764
- setColor(e.target.value);
765
- exec("foreColor", e.target.value);
766
- },
767
- style: { position: "absolute", opacity: 0, width: 0, height: 0, pointerEvents: "none" }
768
- }
769
- ),
770
1168
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
771
1169
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
772
1170
  "select",
@@ -804,12 +1202,21 @@ function RichTextEditor({ value, onChange }) {
804
1202
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
805
1203
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: openLinkBar, title: "Insert link", active: linkBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoLink, {}) }),
806
1204
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: insertCodeBlock, title: "Code block", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoCode, {}) }),
807
- /* @__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) }),
808
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, {}) }),
809
- 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) => {
810
1217
  insertTable(r, c);
811
1218
  setShowTable(false);
812
- }, onClose: () => setShowTable(false) })
1219
+ }, onClose: () => setShowTable(false) }) })
813
1220
  ] }),
814
1221
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {})
815
1222
  ] }),
@@ -852,29 +1259,120 @@ function RichTextEditor({ value, onChange }) {
852
1259
  "data-ph": "Start typing...",
853
1260
  onMouseDown: handleEditorMouseDown,
854
1261
  onMouseMove: handleEditorMouseMove,
855
- onMouseUp: handleEditorMouseUp,
1262
+ onMouseUp: () => {
1263
+ isDragging.current = false;
1264
+ },
856
1265
  onKeyDown: handleKeyDown,
857
1266
  onKeyUp: refresh,
858
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
+ },
859
1280
  style: { display: isCode ? "none" : "block" }
860
1281
  }
861
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
+ ] }),
862
1324
  !isCode && fmt.inTable && tableFP && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-float", style: { top: tableFP.top, left: tableFP.left }, children: [
863
1325
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-float-arrow", style: { left: tableFP.arrowLeft } }),
864
1326
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addRowAbove"), title: "Add row above", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoRowAbove, {}) }),
865
1327
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addRowBelow"), title: "Add row below", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoRowBelow, {}) }),
866
1328
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("deleteRow"), title: "Delete row", danger: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoDelRow, {}) }),
867
1329
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
868
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addColLeft"), title: "Add column left", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoColLeft, {}) }),
869
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("addColRight"), title: "Add column right", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoColRight, {}) }),
870
- /* @__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, {}) }),
871
1333
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
872
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, {}) }),
873
1335
  canSplit && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: doSplit, title: "Split merged cell", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoSplit, {}) }),
874
1336
  (canMerge || canSplit) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sep, {}),
875
1337
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Btn, { onClick: () => tableOp("deleteTable"), title: "Delete table", danger: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoDelTable, {}) })
876
1338
  ] }),
877
- !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: [
878
1376
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rte-float-arrow", style: { left: linkFP.arrowLeft } }),
879
1377
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IcoLink, {}),
880
1378
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -894,11 +1392,11 @@ function RichTextEditor({ value, onChange }) {
894
1392
  placeholder: "https://example.com"
895
1393
  }
896
1394
  ),
897
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "rte-link-apply", onClick: applyLink, children: "Apply" }),
898
- /* @__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: () => {
899
1397
  setLinkBar(false);
900
1398
  setLinkFP(null);
901
- }, 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" })
902
1400
  ] })
903
1401
  ] }),
904
1402
  !isCode && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rte-footer", children: [
@@ -908,7 +1406,7 @@ function RichTextEditor({ value, onChange }) {
908
1406
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Shift+click" }),
909
1407
  " to select cells \xA0\xB7\xA0 ",
910
1408
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Tab" }),
911
- " moves between cells"
1409
+ " moves between cells \xA0\xB7\xA0 Drop an image file to insert"
912
1410
  ] })
913
1411
  ] })
914
1412
  ] });