framepexls-ui-lib 2.2.8 → 2.2.11

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.
@@ -29,13 +29,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
30
  var MarkdownEditor_exports = {};
31
31
  __export(MarkdownEditor_exports, {
32
- default: () => MarkdownEditor
32
+ MarkdownPreview: () => MarkdownPreview,
33
+ default: () => MarkdownEditor,
34
+ renderMarkdown: () => renderMarkdown
33
35
  });
34
36
  module.exports = __toCommonJS(MarkdownEditor_exports);
35
37
  var import_jsx_runtime = require("react/jsx-runtime");
36
38
  var import_react = __toESM(require("react"));
39
+ var import_react_dom = require("react-dom");
40
+ var import_ActionIconButton = __toESM(require("./ActionIconButton"));
37
41
  var import_Button = __toESM(require("./Button"));
42
+ var import_Dropdown = __toESM(require("./Dropdown"));
38
43
  var import_Textarea = __toESM(require("./Textarea"));
44
+ var import_Tooltip = __toESM(require("./Tooltip"));
45
+ var import_iconos = require("./iconos");
39
46
  function cx(...a) {
40
47
  return a.filter(Boolean).join(" ");
41
48
  }
@@ -60,11 +67,61 @@ function getSelectedLines(value, start, end) {
60
67
  const lineEnd = lineEndIdx === -1 ? value.length : lineEndIdx;
61
68
  return { from, to, lineStart, lineEnd, block: value.slice(lineStart, lineEnd) };
62
69
  }
70
+ function getLineDeleteRange(value, start, end) {
71
+ const { lineStart, lineEnd } = getSelectedLines(value, start, end);
72
+ return { start: lineStart, end: lineEnd };
73
+ }
74
+ function getFenceDeleteRange(value, position) {
75
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
76
+ const lines = getMarkdownLines(value);
77
+ const index = lines.findIndex((line) => position >= line.start && position <= line.end);
78
+ if (index < 0) return null;
79
+ let openIndex = -1;
80
+ for (let i = index; i >= 0; i -= 1) {
81
+ if (/^```/.test((_b = (_a = lines[i]) == null ? void 0 : _a.text.trim()) != null ? _b : "")) {
82
+ openIndex = i;
83
+ break;
84
+ }
85
+ }
86
+ if (openIndex < 0) return null;
87
+ for (let i = openIndex + 1; i < lines.length; i += 1) {
88
+ if (/^```/.test((_d = (_c = lines[i]) == null ? void 0 : _c.text.trim()) != null ? _d : "")) {
89
+ return { start: (_f = (_e = lines[openIndex]) == null ? void 0 : _e.start) != null ? _f : 0, end: (_j = (_i = (_g = lines[i]) == null ? void 0 : _g.end) != null ? _i : (_h = lines[openIndex]) == null ? void 0 : _h.end) != null ? _j : 0 };
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ function getDeleteBlockRange(value, range) {
95
+ if (range.kind === "code" || range.kind === "diagram") {
96
+ const fenced = getFenceDeleteRange(value, range.start);
97
+ if (fenced) return fenced;
98
+ const block = getCurrentBlockRange(value, range.start);
99
+ return { start: block.from, end: block.to };
100
+ }
101
+ if (range.kind === "paragraph") {
102
+ const block = getCurrentBlockRange(value, range.start);
103
+ return { start: block.from, end: block.to };
104
+ }
105
+ return getLineDeleteRange(value, range.start, range.end);
106
+ }
107
+ function removeMarkdownBlock(value, range) {
108
+ if (!value) return { next: value, cursor: 0 };
109
+ let { start, end } = getDeleteBlockRange(value, range);
110
+ start = Math.max(0, Math.min(value.length, start));
111
+ end = Math.max(start, Math.min(value.length, end));
112
+ if (start === end && start < value.length) end += 1;
113
+ let removeFrom = start;
114
+ let removeTo = end;
115
+ if (removeTo < value.length && value[removeTo] === "\n") {
116
+ removeTo += 1;
117
+ } else if (removeFrom > 0 && value[removeFrom - 1] === "\n") {
118
+ removeFrom -= 1;
119
+ }
120
+ const next = (value.slice(0, removeFrom) + value.slice(removeTo)).replace(/\n{4,}/g, "\n\n\n");
121
+ return { next, cursor: Math.max(0, Math.min(next.length, removeFrom)) };
122
+ }
63
123
  function wrapSelection(value, start, end, left, right, fallbackText) {
64
- const a = Math.max(0, Math.min(value.length, start));
65
- const b = Math.max(0, Math.min(value.length, end));
66
- const from = Math.min(a, b);
67
- const to = Math.max(a, b);
124
+ const { from, to } = normalizeRange(value, start, end);
68
125
  const sel = value.slice(from, to);
69
126
  const inner = sel || fallbackText;
70
127
  const next = value.slice(0, from) + left + inner + right + value.slice(to);
@@ -72,10 +129,1208 @@ function wrapSelection(value, start, end, left, right, fallbackText) {
72
129
  const nextTo = from + left.length + inner.length;
73
130
  return { next, selectFrom: nextFrom, selectTo: nextTo };
74
131
  }
132
+ function normalizeRange(value, start, end) {
133
+ const a = Math.max(0, Math.min(value.length, start));
134
+ const b = Math.max(0, Math.min(value.length, end));
135
+ return { from: Math.min(a, b), to: Math.max(a, b) };
136
+ }
137
+ function trimRange(value, from, to) {
138
+ var _a, _b;
139
+ let nextFrom = from;
140
+ let nextTo = to;
141
+ while (nextFrom < nextTo && /\s/.test((_a = value[nextFrom]) != null ? _a : "")) nextFrom += 1;
142
+ while (nextTo > nextFrom && /\s/.test((_b = value[nextTo - 1]) != null ? _b : "")) nextTo -= 1;
143
+ return { from: nextFrom, to: nextTo };
144
+ }
145
+ function getCurrentBlockRange(value, cursor) {
146
+ const pos = Math.max(0, Math.min(value.length, cursor));
147
+ let start = value.lastIndexOf("\n\n", Math.max(0, pos - 1));
148
+ start = start === -1 ? 0 : start + 2;
149
+ let end = value.indexOf("\n\n", pos);
150
+ end = end === -1 ? value.length : end;
151
+ return trimRange(value, start, end);
152
+ }
153
+ function getSmartTextRange(value, start, end) {
154
+ const selected = normalizeRange(value, start, end);
155
+ if (selected.from !== selected.to) return trimRange(value, selected.from, selected.to);
156
+ const block = getCurrentBlockRange(value, selected.from);
157
+ if (block.from !== block.to) return block;
158
+ return selected;
159
+ }
160
+ function unwrapFullMarker(value, left, right) {
161
+ if (!value.startsWith(left) || !value.endsWith(right)) return value;
162
+ return value.slice(left.length, value.length - right.length);
163
+ }
164
+ function mapRenderedOffsetToMarkdown(raw, renderedOffset) {
165
+ var _a, _b;
166
+ const boundaries = [];
167
+ let visible = 0;
168
+ const addVisible = (rawIndex) => {
169
+ if (boundaries[visible] === void 0) boundaries[visible] = rawIndex;
170
+ visible += 1;
171
+ boundaries[visible] = rawIndex + 1;
172
+ };
173
+ for (let index = 0; index < raw.length; index += 1) {
174
+ if (raw.startsWith("**", index)) {
175
+ index += 1;
176
+ continue;
177
+ }
178
+ if (raw[index] === "*" || raw[index] === "`") continue;
179
+ const colorOpen = (_a = raw.slice(index).match(/^\{color:#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?(?:[0-9a-fA-F]{2})?\}/)) == null ? void 0 : _a[0];
180
+ if (colorOpen) {
181
+ index += colorOpen.length - 1;
182
+ continue;
183
+ }
184
+ if (raw.startsWith("{/color}", index)) {
185
+ index += "{/color}".length - 1;
186
+ continue;
187
+ }
188
+ if (raw[index] === "[") {
189
+ const labelEnd = raw.indexOf("]", index + 1);
190
+ if (labelEnd > index && raw[labelEnd + 1] === "(") {
191
+ const urlEnd = raw.indexOf(")", labelEnd + 2);
192
+ if (urlEnd > labelEnd) {
193
+ for (let labelIndex = index + 1; labelIndex < labelEnd; labelIndex += 1) {
194
+ addVisible(labelIndex);
195
+ }
196
+ index = urlEnd;
197
+ continue;
198
+ }
199
+ }
200
+ }
201
+ addVisible(index);
202
+ }
203
+ if (!boundaries.length) return Math.max(0, Math.min(raw.length, renderedOffset));
204
+ const offset = Math.max(0, Math.min(renderedOffset, boundaries.length - 1));
205
+ return (_b = boundaries[offset]) != null ? _b : raw.length;
206
+ }
207
+ function findMarkdownWrapper(value, from, to, left, right) {
208
+ let index = 0;
209
+ while (index < value.length) {
210
+ const open = value.indexOf(left, index);
211
+ if (open < 0) return null;
212
+ const contentStart = open + left.length;
213
+ const close = value.indexOf(right, contentStart);
214
+ if (close < 0) return null;
215
+ const wrapperEnd = close + right.length;
216
+ if (from >= contentStart && to <= close) {
217
+ return { wrapperStart: open, wrapperEnd, contentStart, contentEnd: close };
218
+ }
219
+ index = wrapperEnd;
220
+ }
221
+ return null;
222
+ }
223
+ function findLinkWrapper(value, from, to) {
224
+ var _a, _b, _c;
225
+ const pattern = /\[([^\]]+)\]\((https?:\/\/[^\s)]+|mailto:[^\s)]+|\/[^\s)]+)\)/g;
226
+ let match;
227
+ while (match = pattern.exec(value)) {
228
+ const wrapperStart = match.index;
229
+ const labelStart = wrapperStart + 1;
230
+ const labelEnd = labelStart + ((_b = (_a = match[1]) == null ? void 0 : _a.length) != null ? _b : 0);
231
+ if (from >= labelStart && to <= labelEnd) {
232
+ return { wrapperStart, wrapperEnd: pattern.lastIndex, contentStart: labelStart, contentEnd: labelEnd, url: (_c = match[2]) != null ? _c : "" };
233
+ }
234
+ }
235
+ return null;
236
+ }
237
+ function findItalicWrapper(value, from, to) {
238
+ var _a, _b, _c, _d;
239
+ const pattern = /(^|[^*])\*([^*\n]+)\*/g;
240
+ let match;
241
+ while (match = pattern.exec(value)) {
242
+ const prefixLength = (_b = (_a = match[1]) == null ? void 0 : _a.length) != null ? _b : 0;
243
+ const wrapperStart = match.index + prefixLength;
244
+ const contentStart = wrapperStart + 1;
245
+ const contentEnd = contentStart + ((_d = (_c = match[2]) == null ? void 0 : _c.length) != null ? _d : 0);
246
+ const wrapperEnd = contentEnd + 1;
247
+ if (from >= contentStart && to <= contentEnd) {
248
+ return { wrapperStart, wrapperEnd, contentStart, contentEnd };
249
+ }
250
+ }
251
+ return null;
252
+ }
253
+ function findInlineWrapperAt(value, offset, left, right) {
254
+ return left === "*" && right === "*" ? findItalicWrapper(value, offset, offset) : findMarkdownWrapper(value, offset, offset, left, right);
255
+ }
256
+ function expandRangeToTouchedInlineWrappers(value, range, left, right) {
257
+ const startWrapper = findInlineWrapperAt(value, range.from, left, right);
258
+ const endOffset = Math.max(range.from, range.to - 1);
259
+ const endWrapper = findInlineWrapperAt(value, endOffset, left, right);
260
+ return {
261
+ from: startWrapper ? Math.min(range.from, startWrapper.wrapperStart) : range.from,
262
+ to: endWrapper ? Math.max(range.to, endWrapper.wrapperEnd) : range.to
263
+ };
264
+ }
265
+ function stripInlineStyleMarkers(value, left, right) {
266
+ if (left === "**" && right === "**") return value.replace(/\*{2,}/g, "");
267
+ if (left === "*" && right === "*") return value.replace(/(^|[^*])\*(?!\*)/g, "$1");
268
+ if (left === "`" && right === "`") return value.replace(/`+/g, "");
269
+ return unwrapFullMarker(value, left, right);
270
+ }
271
+ function normalizeMarkerPairs(value, marker, selectFrom, selectTo) {
272
+ const positions = [];
273
+ for (let index = 0; index < value.length; index += 1) {
274
+ if (!value.startsWith(marker, index)) continue;
275
+ if (marker === "*" && (value[index - 1] === "*" || value[index + 1] === "*")) continue;
276
+ positions.push(index);
277
+ index += marker.length - 1;
278
+ }
279
+ if (positions.length < 3 || positions.length % 2 === 0) return { next: value, selectFrom, selectTo };
280
+ const keep = /* @__PURE__ */ new Set([positions[0], positions[positions.length - 1]]);
281
+ let next = "";
282
+ let cursor = 0;
283
+ let nextSelectFrom = selectFrom;
284
+ let nextSelectTo = selectTo;
285
+ const adjust = (position, removalStart) => {
286
+ if (position <= removalStart) return position;
287
+ if (position <= removalStart + marker.length) return removalStart;
288
+ return position - marker.length;
289
+ };
290
+ for (const position of positions) {
291
+ next += value.slice(cursor, position);
292
+ if (keep.has(position)) {
293
+ next += marker;
294
+ } else {
295
+ nextSelectFrom = adjust(nextSelectFrom, position);
296
+ nextSelectTo = adjust(nextSelectTo, position);
297
+ }
298
+ cursor = position + marker.length;
299
+ }
300
+ next += value.slice(cursor);
301
+ return {
302
+ next,
303
+ selectFrom: Math.max(0, Math.min(next.length, nextSelectFrom)),
304
+ selectTo: Math.max(0, Math.min(next.length, nextSelectTo))
305
+ };
306
+ }
307
+ function normalizeStyleNoise(value, selectFrom, selectTo) {
308
+ let markerResult = normalizeMarkerPairs(value, "**", selectFrom, selectTo);
309
+ markerResult = normalizeMarkerPairs(markerResult.next, "*", markerResult.selectFrom, markerResult.selectTo);
310
+ markerResult = normalizeMarkerPairs(markerResult.next, "`", markerResult.selectFrom, markerResult.selectTo);
311
+ if (markerResult.next !== value) {
312
+ return normalizeStyleNoise(markerResult.next, markerResult.selectFrom, markerResult.selectTo);
313
+ }
314
+ const removals = [];
315
+ const collect = (pattern) => {
316
+ let match;
317
+ while (match = pattern.exec(value)) {
318
+ removals.push({ start: match.index, end: match.index + match[0].length });
319
+ }
320
+ };
321
+ collect(/\*{3,}/g);
322
+ collect(/\{color:#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?(?:[0-9a-fA-F]{2})?\}\s*\{\/color\}/g);
323
+ if (!removals.length) return { next: value, selectFrom, selectTo };
324
+ const merged = removals.sort((a, b) => a.start - b.start).reduce((items, item) => {
325
+ const previous = items[items.length - 1];
326
+ if (previous && item.start <= previous.end) {
327
+ previous.end = Math.max(previous.end, item.end);
328
+ } else {
329
+ items.push({ ...item });
330
+ }
331
+ return items;
332
+ }, []);
333
+ let next = "";
334
+ let cursor = 0;
335
+ let nextSelectFrom = selectFrom;
336
+ let nextSelectTo = selectTo;
337
+ const adjust = (position, removal) => {
338
+ const length = removal.end - removal.start;
339
+ if (position <= removal.start) return position;
340
+ if (position >= removal.end) return position - length;
341
+ return removal.start;
342
+ };
343
+ for (const removal of merged) {
344
+ next += value.slice(cursor, removal.start);
345
+ nextSelectFrom = adjust(nextSelectFrom, removal);
346
+ nextSelectTo = adjust(nextSelectTo, removal);
347
+ cursor = removal.end;
348
+ }
349
+ next += value.slice(cursor);
350
+ const result = {
351
+ next,
352
+ selectFrom: Math.max(0, Math.min(next.length, nextSelectFrom)),
353
+ selectTo: Math.max(0, Math.min(next.length, nextSelectTo))
354
+ };
355
+ if (/\{color:#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?(?:[0-9a-fA-F]{2})?\}\s*\{\/color\}/.test(result.next)) {
356
+ return normalizeStyleNoise(result.next, result.selectFrom, result.selectTo);
357
+ }
358
+ return result;
359
+ }
360
+ function splitSimpleWrapper(value, wrapper, range, left, right) {
361
+ const before = value.slice(wrapper.contentStart, range.from);
362
+ const selected = value.slice(range.from, range.to);
363
+ const after = value.slice(range.to, wrapper.contentEnd);
364
+ const wrap = (text) => text ? `${left}${text}${right}` : "";
365
+ const replacement = `${wrap(before)}${selected}${wrap(after)}`;
366
+ const selectFrom = wrapper.wrapperStart + wrap(before).length;
367
+ const next = value.slice(0, wrapper.wrapperStart) + replacement + value.slice(wrapper.wrapperEnd);
368
+ return normalizeStyleNoise(next, selectFrom, selectFrom + selected.length);
369
+ }
370
+ function splitLinkWrapper(value, wrapper, range) {
371
+ if (!wrapper) return null;
372
+ const before = value.slice(wrapper.contentStart, range.from);
373
+ const selected = value.slice(range.from, range.to);
374
+ const after = value.slice(range.to, wrapper.contentEnd);
375
+ const wrap = (text) => text ? `[${text}](${wrapper.url})` : "";
376
+ const replacement = `${wrap(before)}${selected}${wrap(after)}`;
377
+ const selectFrom = wrapper.wrapperStart + wrap(before).length;
378
+ const next = value.slice(0, wrapper.wrapperStart) + replacement + value.slice(wrapper.wrapperEnd);
379
+ return { next, selectFrom, selectTo: selectFrom + selected.length };
380
+ }
381
+ function smartWrapSelection(value, start, end, left, right, fallbackText) {
382
+ const range = getSmartTextRange(value, start, end);
383
+ if (range.from === range.to) {
384
+ const result = wrapSelection(value, start, end, left, right, fallbackText);
385
+ return normalizeStyleNoise(result.next, result.selectFrom, result.selectTo);
386
+ }
387
+ const wrapper = left === "*" && right === "*" ? findItalicWrapper(value, range.from, range.to) : findMarkdownWrapper(value, range.from, range.to, left, right);
388
+ if (wrapper) {
389
+ return splitSimpleWrapper(value, wrapper, range, left, right);
390
+ }
391
+ const expanded = expandRangeToTouchedInlineWrappers(value, range, left, right);
392
+ const selected = value.slice(expanded.from, expanded.to);
393
+ const inner = stripInlineStyleMarkers(selected, left, right);
394
+ const next = value.slice(0, expanded.from) + left + inner + right + value.slice(expanded.to);
395
+ const selectFrom = expanded.from + left.length;
396
+ const selectTo = selectFrom + inner.length;
397
+ return normalizeStyleNoise(next, selectFrom, selectTo);
398
+ }
399
+ function smartLinkSelection(value, start, end, url) {
400
+ const range = getSmartTextRange(value, start, end);
401
+ const wrapper = findLinkWrapper(value, range.from, range.to);
402
+ if (wrapper) return splitLinkWrapper(value, wrapper, range);
403
+ const selected = range.from === range.to ? "texto" : value.slice(range.from, range.to);
404
+ const inner = selected.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+|mailto:[^\s)]+|\/[^\s)]+)\)/g, "$1");
405
+ const next = value.slice(0, range.from) + `[${inner}](${url})` + value.slice(range.to);
406
+ const selectFrom = range.from + 1;
407
+ return { next, selectFrom, selectTo: selectFrom + inner.length };
408
+ }
409
+ function readColorOpen(value, index) {
410
+ var _a;
411
+ const match = value.slice(index).match(/^\{color:(#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?(?:[0-9a-fA-F]{2})?)\}/);
412
+ return match ? { color: (_a = match[1]) != null ? _a : null, length: match[0].length } : null;
413
+ }
414
+ function collectColorWrappers(value) {
415
+ const wrappers = [];
416
+ const stack = [];
417
+ for (let index = 0; index < value.length; index += 1) {
418
+ const open = readColorOpen(value, index);
419
+ if (open) {
420
+ stack.push({
421
+ wrapperStart: index,
422
+ contentStart: index + open.length,
423
+ color: open.color
424
+ });
425
+ index += open.length - 1;
426
+ continue;
427
+ }
428
+ if (value.startsWith("{/color}", index)) {
429
+ const current = stack.pop();
430
+ if (current) {
431
+ wrappers.push({
432
+ ...current,
433
+ contentEnd: index,
434
+ wrapperEnd: index + "{/color}".length,
435
+ inner: value.slice(current.contentStart, index)
436
+ });
437
+ }
438
+ index += "{/color}".length - 1;
439
+ }
440
+ }
441
+ return wrappers.sort((a, b) => a.wrapperStart - b.wrapperStart);
442
+ }
443
+ function parseColorUnits(value) {
444
+ var _a, _b;
445
+ const units = [];
446
+ const stack = [];
447
+ for (let index = 0; index < value.length; index += 1) {
448
+ const open = readColorOpen(value, index);
449
+ if (open) {
450
+ stack.push(open.color);
451
+ index += open.length - 1;
452
+ continue;
453
+ }
454
+ if (value.startsWith("{/color}", index)) {
455
+ stack.pop();
456
+ index += "{/color}".length - 1;
457
+ continue;
458
+ }
459
+ units.push({
460
+ text: (_a = value[index]) != null ? _a : "",
461
+ color: (_b = stack[stack.length - 1]) != null ? _b : null,
462
+ rawStart: index,
463
+ rawEnd: index + 1
464
+ });
465
+ }
466
+ return units;
467
+ }
468
+ function rawOffsetToColorUnitIndex(units, offset) {
469
+ for (let index = 0; index < units.length; index += 1) {
470
+ const unit = units[index];
471
+ if (!unit) continue;
472
+ if (offset <= unit.rawStart) return index;
473
+ if (offset < unit.rawEnd) return index;
474
+ }
475
+ return units.length;
476
+ }
477
+ function serializeColorUnits(units, selectFromIndex, selectToIndex) {
478
+ var _a;
479
+ let next = "";
480
+ let activeColor = null;
481
+ let selectFrom;
482
+ let selectTo;
483
+ const closeColor = () => {
484
+ if (!activeColor) return;
485
+ next += "{/color}";
486
+ activeColor = null;
487
+ };
488
+ const openColor = (color) => {
489
+ if (!color) return;
490
+ next += `{color:${color}}`;
491
+ activeColor = color;
492
+ };
493
+ for (let index = 0; index < units.length; index += 1) {
494
+ const unit = units[index];
495
+ if (!unit) continue;
496
+ if (unit.color !== activeColor) {
497
+ closeColor();
498
+ openColor(unit.color);
499
+ }
500
+ if (index === selectFromIndex) selectFrom = next.length;
501
+ next += unit.text;
502
+ if (index + 1 === selectToIndex) selectTo = next.length;
503
+ }
504
+ closeColor();
505
+ return {
506
+ next,
507
+ selectFrom: selectFrom != null ? selectFrom : next.length,
508
+ selectTo: (_a = selectTo != null ? selectTo : selectFrom) != null ? _a : next.length
509
+ };
510
+ }
511
+ function findSelectionColor(value, from, to, activeOffset) {
512
+ var _a, _b;
513
+ const wrappers = collectColorWrappers(value);
514
+ if (!wrappers.length) return null;
515
+ const normalizedFrom = Math.max(0, Math.min(value.length, from));
516
+ const normalizedTo = Math.max(normalizedFrom, Math.min(value.length, to));
517
+ const preferredOffsets = [
518
+ typeof activeOffset === "number" ? activeOffset : null,
519
+ normalizedTo > normalizedFrom ? normalizedTo - 1 : normalizedFrom,
520
+ normalizedFrom
521
+ ];
522
+ for (const offset of preferredOffsets) {
523
+ if (offset === null) continue;
524
+ const point = Math.max(normalizedFrom, Math.min(normalizedTo, offset));
525
+ const match = wrappers.filter((wrapper) => point >= wrapper.contentStart && point <= wrapper.contentEnd).sort((a, b) => a.contentEnd - a.contentStart - (b.contentEnd - b.contentStart) || b.contentStart - a.contentStart)[0];
526
+ if (match == null ? void 0 : match.color) return match.color;
527
+ }
528
+ const overlapping = wrappers.filter((wrapper) => Math.max(wrapper.contentStart, normalizedFrom) < Math.min(wrapper.contentEnd, normalizedTo)).sort((a, b) => b.contentStart - a.contentStart || b.wrapperStart - a.wrapperStart);
529
+ return (_b = (_a = overlapping[0]) == null ? void 0 : _a.color) != null ? _b : null;
530
+ }
531
+ function applyColorToSelection(value, start, end, color) {
532
+ const range = getSmartTextRange(value, start, end);
533
+ if (range.from === range.to) {
534
+ return wrapSelection(value, start, end, `{color:${color}}`, "{/color}", "texto");
535
+ }
536
+ const units = parseColorUnits(value);
537
+ const fromIndex = rawOffsetToColorUnitIndex(units, range.from);
538
+ const toIndex = rawOffsetToColorUnitIndex(units, range.to);
539
+ const selectFromIndex = Math.min(fromIndex, toIndex);
540
+ const selectToIndex = Math.max(fromIndex, toIndex);
541
+ for (let index = selectFromIndex; index < selectToIndex; index += 1) {
542
+ if (units[index]) units[index].color = color;
543
+ }
544
+ return serializeColorUnits(units, selectFromIndex, selectToIndex);
545
+ }
546
+ function detectSelectionMarks(value, range) {
547
+ const normalized = normalizeRange(value, range.start, range.end);
548
+ const color = findSelectionColor(value, normalized.from, normalized.to, range.activeOffset);
549
+ return {
550
+ bold: Boolean(findMarkdownWrapper(value, normalized.from, normalized.to, "**", "**")),
551
+ italic: Boolean(findItalicWrapper(value, normalized.from, normalized.to)),
552
+ code: Boolean(findMarkdownWrapper(value, normalized.from, normalized.to, "`", "`")),
553
+ link: Boolean(findLinkWrapper(value, normalized.from, normalized.to)),
554
+ color
555
+ };
556
+ }
75
557
  function prefixLines(block, prefix) {
76
558
  const lines = block.split("\n");
77
559
  return lines.map((l) => l.trim().length ? prefix + l : l).join("\n");
78
560
  }
561
+ function escapeHtml(value) {
562
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
563
+ }
564
+ function escapeAttr(value) {
565
+ return escapeHtml(value).replace(/`/g, "&#096;");
566
+ }
567
+ function decodeBasicEntities(value) {
568
+ return String(value || "").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#039;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
569
+ }
570
+ function safeMediaUrl(value, kind) {
571
+ const raw = decodeBasicEntities(value).trim();
572
+ if (!raw) return null;
573
+ const lower = raw.toLowerCase();
574
+ if (lower.startsWith("http://") || lower.startsWith("https://")) return raw;
575
+ if (kind === "image" && /^data:image\/(png|jpe?g|gif|webp);base64,/i.test(raw)) return raw;
576
+ if (kind === "video" && /^data:video\/(mp4|webm|ogg);base64,/i.test(raw)) return raw;
577
+ return null;
578
+ }
579
+ function safeColor(value) {
580
+ const color = decodeBasicEntities(value).trim();
581
+ return /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?([0-9a-fA-F]{2})?$/.test(color) ? color : null;
582
+ }
583
+ function videoEmbedUrl(value) {
584
+ var _a, _b, _c;
585
+ try {
586
+ const url = new URL(decodeBasicEntities(value).trim());
587
+ const host = url.hostname.replace(/^www\./, "").replace(/^m\./, "").toLowerCase();
588
+ if (host === "youtu.be") {
589
+ const id = url.pathname.split("/").filter(Boolean)[0];
590
+ return id ? `https://www.youtube-nocookie.com/embed/${encodeURIComponent(id)}` : null;
591
+ }
592
+ if (host === "youtube.com" || host === "music.youtube.com") {
593
+ const embed = (_a = url.pathname.match(/^\/embed\/([^/?#]+)/)) == null ? void 0 : _a[1];
594
+ const shorts = (_b = url.pathname.match(/^\/shorts\/([^/?#]+)/)) == null ? void 0 : _b[1];
595
+ const id = embed || shorts || url.searchParams.get("v");
596
+ return id ? `https://www.youtube-nocookie.com/embed/${encodeURIComponent(id)}` : null;
597
+ }
598
+ if (host === "vimeo.com") {
599
+ const id = url.pathname.split("/").filter(Boolean).find((part) => /^\d+$/.test(part));
600
+ return id ? `https://player.vimeo.com/video/${encodeURIComponent(id)}` : null;
601
+ }
602
+ if (host === "player.vimeo.com") {
603
+ const id = (_c = url.pathname.match(/\/video\/(\d+)/)) == null ? void 0 : _c[1];
604
+ return id ? `https://player.vimeo.com/video/${encodeURIComponent(id)}` : null;
605
+ }
606
+ } catch {
607
+ }
608
+ return null;
609
+ }
610
+ function renderImageMarkdown(rawUrl, alt, attrs = "") {
611
+ const url = safeMediaUrl(rawUrl, "image");
612
+ if (!url) return null;
613
+ return `<figure${attrs}><img src="${escapeAttr(url)}" alt="${escapeAttr(decodeBasicEntities(alt != null ? alt : ""))}" loading="lazy" /></figure>`;
614
+ }
615
+ function renderVideoMarkdown(rawUrl, title, attrs = "") {
616
+ const url = safeMediaUrl(rawUrl, "video");
617
+ if (!url) return null;
618
+ const label = title ? `<figcaption>${escapeHtml(decodeBasicEntities(title))}</figcaption>` : "";
619
+ const embed = videoEmbedUrl(url);
620
+ if (embed) {
621
+ return `<figure${attrs}><iframe src="${escapeAttr(embed)}" title="${escapeAttr(decodeBasicEntities(title || "Video"))}" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>${label}</figure>`;
622
+ }
623
+ return `<figure${attrs}><video controls preload="metadata" src="${escapeAttr(url)}"></video>${label}</figure>`;
624
+ }
625
+ function parseMarkdownTableRow(value) {
626
+ const trimmed = String(value || "").trim();
627
+ if (!trimmed.includes("|")) return null;
628
+ const normalized = trimmed.replace(/^\|/, "").replace(/\|$/, "");
629
+ const cells = normalized.split("|").map((cell) => cell.trim());
630
+ return cells.length >= 2 ? cells : null;
631
+ }
632
+ function isMarkdownTableSeparator(cells) {
633
+ return Boolean((cells == null ? void 0 : cells.length) && cells.every((cell) => /^:?-{3,}:?$/.test(cell)));
634
+ }
635
+ function getMarkdownLines(value) {
636
+ const normalized = value.replace(/\r\n/g, "\n");
637
+ const parts = normalized.split("\n");
638
+ let cursor = 0;
639
+ return parts.map((text) => {
640
+ const line = { text, start: cursor, end: cursor + text.length };
641
+ cursor = line.end + 1;
642
+ return line;
643
+ });
644
+ }
645
+ function rangesOverlap(a, start, end) {
646
+ if (!a) return false;
647
+ return a.start < end && a.end > start;
648
+ }
649
+ function rangeAttrs(options, kind, start, end) {
650
+ if (!(options == null ? void 0 : options.includeRanges)) return "";
651
+ const selected = rangesOverlap(options.selectedRange, start, end) ? ' data-md-selected="true"' : "";
652
+ return ` data-md-kind="${escapeAttr(kind)}" data-md-start="${start}" data-md-end="${end}"${selected}`;
653
+ }
654
+ function applyInlineMarks(html, marks) {
655
+ let next = html;
656
+ if (marks.code) next = `<code>${next}</code>`;
657
+ if (marks.italic) next = `<em>${next}</em>`;
658
+ if (marks.bold) next = `<strong>${next}</strong>`;
659
+ if (marks.color) next = `<span style="color:${marks.color}">${next}</span>`;
660
+ if (marks.link) next = `<a href="${escapeAttr(marks.link)}" target="_blank" rel="noopener noreferrer">${next}</a>`;
661
+ return next;
662
+ }
663
+ function renderInlineMarkdown(value) {
664
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
665
+ const source = normalizeStyleNoise(value, 0, value.length).next;
666
+ const html = [];
667
+ const state = {
668
+ bold: false,
669
+ italic: false,
670
+ code: false,
671
+ color: null
672
+ };
673
+ const colorStack = [];
674
+ let buffer = "";
675
+ const flush = () => {
676
+ if (!buffer) return;
677
+ html.push(applyInlineMarks(escapeHtml(buffer), state));
678
+ buffer = "";
679
+ };
680
+ for (let index = 0; index < source.length; index += 1) {
681
+ const rest = source.slice(index);
682
+ const image = /^!\[([^\]]*)\]\(([^)\s]+)\)/.exec(rest);
683
+ if (image) {
684
+ const rendered = renderImageMarkdown((_a = image[2]) != null ? _a : "", (_b = image[1]) != null ? _b : "");
685
+ if (rendered) {
686
+ flush();
687
+ html.push(rendered);
688
+ index += image[0].length - 1;
689
+ continue;
690
+ }
691
+ }
692
+ const video = /^@\[video(?::([^\]]*))?\]\(([^)\s]+)\)/.exec(rest);
693
+ if (video) {
694
+ const rendered = renderVideoMarkdown((_c = video[2]) != null ? _c : "", (_d = video[1]) != null ? _d : "");
695
+ if (rendered) {
696
+ flush();
697
+ html.push(rendered);
698
+ index += video[0].length - 1;
699
+ continue;
700
+ }
701
+ }
702
+ const colorOpen = /^\{color:([^}]+)\}/.exec(rest);
703
+ if (colorOpen) {
704
+ const color = safeColor((_e = colorOpen[1]) != null ? _e : "");
705
+ flush();
706
+ colorStack.push(state.color);
707
+ state.color = color != null ? color : state.color;
708
+ index += colorOpen[0].length - 1;
709
+ continue;
710
+ }
711
+ if (rest.startsWith("{/color}")) {
712
+ flush();
713
+ state.color = colorStack.length ? (_f = colorStack.pop()) != null ? _f : null : null;
714
+ index += "{/color}".length - 1;
715
+ continue;
716
+ }
717
+ const link = /^\[([^\]]+)\]\((https?:\/\/[^\s)]+|mailto:[^\s)]+|\/[^\s)]+)\)/.exec(rest);
718
+ if (link) {
719
+ flush();
720
+ html.push(applyInlineMarks(renderInlineMarkdown((_g = link[1]) != null ? _g : ""), { ...state, link: (_h = link[2]) != null ? _h : "" }));
721
+ index += link[0].length - 1;
722
+ continue;
723
+ }
724
+ if (rest.startsWith("**")) {
725
+ flush();
726
+ state.bold = !state.bold;
727
+ index += 1;
728
+ continue;
729
+ }
730
+ const current = (_i = source[index]) != null ? _i : "";
731
+ if (current === "`") {
732
+ flush();
733
+ state.code = !state.code;
734
+ continue;
735
+ }
736
+ if (current === "*") {
737
+ flush();
738
+ state.italic = !state.italic;
739
+ continue;
740
+ }
741
+ buffer += current;
742
+ }
743
+ flush();
744
+ return html.join("");
745
+ }
746
+ function renderMarkdown(value, options) {
747
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
748
+ const lines = getMarkdownLines(value);
749
+ const html = [];
750
+ const paragraph = [];
751
+ const code = [];
752
+ let codeOpen = false;
753
+ let codeLang = "";
754
+ let listType = null;
755
+ let listItem = null;
756
+ const closeListItem = () => {
757
+ if (!listItem) return;
758
+ html.push(`<li${rangeAttrs(options, "list-item", listItem.contentStart, listItem.end)}>${listItem.parts.join("<br />")}</li>`);
759
+ listItem = null;
760
+ };
761
+ const closeList = () => {
762
+ if (!listType) return;
763
+ closeListItem();
764
+ html.push(`</${listType}>`);
765
+ listType = null;
766
+ };
767
+ const flushParagraph = () => {
768
+ var _a2, _b2, _c2, _d2;
769
+ if (!paragraph.length) return;
770
+ closeList();
771
+ const start = (_b2 = (_a2 = paragraph[0]) == null ? void 0 : _a2.start) != null ? _b2 : 0;
772
+ const end = (_d2 = (_c2 = paragraph[paragraph.length - 1]) == null ? void 0 : _c2.end) != null ? _d2 : start;
773
+ const content = (options == null ? void 0 : options.includeRanges) ? paragraph.map((line) => `<span${rangeAttrs(options, "line", line.start, line.end)}>${renderInlineMarkdown(line.text.trimEnd())}</span>`).join("<br />") : paragraph.map((line) => renderInlineMarkdown(line.text.trimEnd())).join("<br />");
774
+ html.push(`<p${rangeAttrs(options, "paragraph", start, end)}>${content}</p>`);
775
+ paragraph.length = 0;
776
+ };
777
+ const renderCodeBlock = () => {
778
+ var _a2, _b2, _c2, _d2;
779
+ const raw = code.map((line) => line.text).join("\n");
780
+ const lang = codeLang.trim().toLowerCase();
781
+ const start = (_b2 = (_a2 = code[0]) == null ? void 0 : _a2.start) != null ? _b2 : 0;
782
+ const end = (_d2 = (_c2 = code[code.length - 1]) == null ? void 0 : _c2.end) != null ? _d2 : start;
783
+ if (lang === "mermaid" || lang === "uml") {
784
+ html.push(`<div class="fp-markdown-diagram"${rangeAttrs(options, "diagram", start, end)} data-diagram="${escapeAttr(raw)}"><pre><code class="language-${escapeAttr(lang)}">${escapeHtml(raw)}</code></pre></div>`);
785
+ return;
786
+ }
787
+ html.push(`<pre${rangeAttrs(options, "code", start, end)}><code>${escapeHtml(raw)}</code></pre>`);
788
+ };
789
+ const openList = (nextType) => {
790
+ flushParagraph();
791
+ if (listType === nextType) return;
792
+ closeList();
793
+ html.push(`<${nextType}>`);
794
+ listType = nextType;
795
+ };
796
+ const appendListItem = (line) => {
797
+ if (!listItem) return false;
798
+ listItem.parts.push(renderInlineMarkdown(line.text.trim()));
799
+ listItem.end = line.end;
800
+ return true;
801
+ };
802
+ const appendListItemGap = (line) => {
803
+ if (!listItem) return false;
804
+ listItem.parts.push('<span class="fp-markdown-list-gap" aria-hidden="true"></span>');
805
+ listItem.end = line.end;
806
+ return true;
807
+ };
808
+ const nextNonEmptyLine = (fromIndex) => {
809
+ for (let index = fromIndex; index < lines.length; index += 1) {
810
+ const nextLine = lines[index];
811
+ if (!(nextLine == null ? void 0 : nextLine.text.trim())) continue;
812
+ return nextLine.text.trim();
813
+ }
814
+ return "";
815
+ };
816
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
817
+ const currentLine = (_a = lines[lineIndex]) != null ? _a : { text: "", start: 0, end: 0 };
818
+ const line = currentLine.text;
819
+ const trimmed = line.trim();
820
+ const fence = /^```([A-Za-z0-9_-]+)?/.exec(trimmed);
821
+ if (fence) {
822
+ if (codeOpen) {
823
+ renderCodeBlock();
824
+ code.length = 0;
825
+ codeOpen = false;
826
+ codeLang = "";
827
+ } else {
828
+ flushParagraph();
829
+ closeList();
830
+ codeOpen = true;
831
+ codeLang = (_b = fence[1]) != null ? _b : "";
832
+ }
833
+ continue;
834
+ }
835
+ if (codeOpen) {
836
+ code.push(currentLine);
837
+ continue;
838
+ }
839
+ if (!trimmed) {
840
+ flushParagraph();
841
+ if (line.length > 0 && appendListItemGap(currentLine)) {
842
+ continue;
843
+ }
844
+ const nextTrimmed = nextNonEmptyLine(lineIndex + 1);
845
+ const keepsOrderedList = listType === "ol" && /^\d+\.\s+/.test(nextTrimmed);
846
+ const keepsUnorderedList = listType === "ul" && /^[-*]\s+/.test(nextTrimmed);
847
+ if ((keepsOrderedList || keepsUnorderedList) && appendListItemGap(currentLine)) {
848
+ continue;
849
+ }
850
+ closeList();
851
+ html.push(`<div class="fp-markdown-empty-line"${rangeAttrs(options, "empty-line", currentLine.start, currentLine.end)}></div>`);
852
+ continue;
853
+ }
854
+ if (/^---+$/.test(trimmed)) {
855
+ flushParagraph();
856
+ closeList();
857
+ html.push(`<hr${rangeAttrs(options, "divider", currentLine.start, currentLine.end)} />`);
858
+ continue;
859
+ }
860
+ const imageBlock = /^!\[([^\]]*)\]\(([^)\s]+)\)$/.exec(trimmed);
861
+ if (imageBlock) {
862
+ const rendered = renderImageMarkdown(imageBlock[2], imageBlock[1], rangeAttrs(options, "image", currentLine.start, currentLine.end));
863
+ if (rendered) {
864
+ flushParagraph();
865
+ closeList();
866
+ html.push(rendered);
867
+ continue;
868
+ }
869
+ }
870
+ const videoBlock = /^@\[video(?::([^\]]*))?\]\(([^)\s]+)\)$/.exec(trimmed);
871
+ if (videoBlock) {
872
+ const rendered = renderVideoMarkdown(videoBlock[2], videoBlock[1], rangeAttrs(options, "video", currentLine.start, currentLine.end));
873
+ if (rendered) {
874
+ flushParagraph();
875
+ closeList();
876
+ html.push(rendered);
877
+ continue;
878
+ }
879
+ }
880
+ const tableHead = parseMarkdownTableRow(trimmed);
881
+ const tableSeparator = parseMarkdownTableRow((_d = (_c = lines[lineIndex + 1]) == null ? void 0 : _c.text) != null ? _d : "");
882
+ if (tableHead && isMarkdownTableSeparator(tableSeparator)) {
883
+ flushParagraph();
884
+ closeList();
885
+ const rows = [];
886
+ let tableEnd = (_f = (_e = lines[lineIndex + 1]) == null ? void 0 : _e.end) != null ? _f : currentLine.end;
887
+ let nextIndex = lineIndex + 2;
888
+ while (nextIndex < lines.length) {
889
+ const row = parseMarkdownTableRow((_h = (_g = lines[nextIndex]) == null ? void 0 : _g.text) != null ? _h : "");
890
+ if (!row || isMarkdownTableSeparator(row)) break;
891
+ rows.push(row);
892
+ tableEnd = (_j = (_i = lines[nextIndex]) == null ? void 0 : _i.end) != null ? _j : tableEnd;
893
+ nextIndex += 1;
894
+ }
895
+ const thead = `<thead><tr>${tableHead.map((cell) => `<th>${renderInlineMarkdown(cell)}</th>`).join("")}</tr></thead>`;
896
+ const tbody = rows.length ? `<tbody>${rows.map((row) => `<tr>${tableHead.map((_, index) => {
897
+ var _a2;
898
+ return `<td>${renderInlineMarkdown((_a2 = row[index]) != null ? _a2 : "")}</td>`;
899
+ }).join("")}</tr>`).join("")}</tbody>` : "";
900
+ html.push(`<table${rangeAttrs(options, "table", currentLine.start, tableEnd)}>${thead}${tbody}</table>`);
901
+ lineIndex = nextIndex - 1;
902
+ continue;
903
+ }
904
+ const heading = /^(#{1,4})\s+(.+)$/.exec(trimmed);
905
+ if (heading) {
906
+ flushParagraph();
907
+ closeList();
908
+ const level = heading[1].length;
909
+ const contentStart = currentLine.start + heading[1].length + ((_l = (_k = line.slice(heading[1].length).match(/^\s+/)) == null ? void 0 : _k[0].length) != null ? _l : 0);
910
+ html.push(`<h${level}${rangeAttrs(options, "heading", contentStart, currentLine.end)}>${renderInlineMarkdown(heading[2])}</h${level}>`);
911
+ continue;
912
+ }
913
+ const quote = /^>\s?(.+)$/.exec(trimmed);
914
+ if (quote) {
915
+ flushParagraph();
916
+ closeList();
917
+ const markerIndex = line.indexOf(">");
918
+ const contentStart = currentLine.start + markerIndex + 1 + (line.slice(markerIndex + 1).startsWith(" ") ? 1 : 0);
919
+ html.push(`<blockquote${rangeAttrs(options, "quote", contentStart, currentLine.end)}>${renderInlineMarkdown(quote[1])}</blockquote>`);
920
+ continue;
921
+ }
922
+ const unordered = /^[-*]\s+(.+)$/.exec(trimmed);
923
+ if (unordered) {
924
+ openList("ul");
925
+ closeListItem();
926
+ const markerIndex = Math.max(line.indexOf("-"), line.indexOf("*"));
927
+ const contentStart = currentLine.start + markerIndex + 2;
928
+ listItem = { start: currentLine.start, end: currentLine.end, contentStart, parts: [renderInlineMarkdown(unordered[1])] };
929
+ continue;
930
+ }
931
+ const ordered = /^\d+\.\s+(.+)$/.exec(trimmed);
932
+ if (ordered) {
933
+ openList("ol");
934
+ closeListItem();
935
+ const marker = line.match(/\d+\.\s+/);
936
+ const contentStart = currentLine.start + ((_m = marker == null ? void 0 : marker.index) != null ? _m : 0) + ((_n = marker == null ? void 0 : marker[0].length) != null ? _n : 0);
937
+ listItem = { start: currentLine.start, end: currentLine.end, contentStart, parts: [renderInlineMarkdown(ordered[1])] };
938
+ continue;
939
+ }
940
+ if (listType && appendListItem(currentLine)) {
941
+ continue;
942
+ }
943
+ paragraph.push(currentLine);
944
+ }
945
+ if (codeOpen) {
946
+ renderCodeBlock();
947
+ }
948
+ flushParagraph();
949
+ closeList();
950
+ return html.join("");
951
+ }
952
+ const COLOR_SWATCHES = [
953
+ "#111827",
954
+ "#6b7280",
955
+ "#dc2626",
956
+ "#ea580c",
957
+ "#ca8a04",
958
+ "#16a34a",
959
+ "#0891b2",
960
+ "#2563eb",
961
+ "#7c3aed",
962
+ "#db2777"
963
+ ];
964
+ const MARKDOWN_EDITOR_SAMPLE = [
965
+ "# P\xE1gina de ejemplo",
966
+ "",
967
+ "{color:#2563eb}Este contenido muestra varios bloques que se pueden construir con el editor visual.{/color}",
968
+ "",
969
+ "## Texto enriquecido",
970
+ "",
971
+ "Puedes combinar **negritas**, *cursivas*, `c\xF3digo`, enlaces como [ui-lib](https://example.com) y colores para destacar informaci\xF3n importante.",
972
+ "",
973
+ "- Bloques de texto",
974
+ "- Listas y citas",
975
+ "- Im\xE1genes, videos, tablas y diagramas",
976
+ "",
977
+ "> Este bloque sirve para resaltar una nota, advertencia o resumen dentro del contenido.",
978
+ "",
979
+ "## Imagen",
980
+ "",
981
+ "![Imagen de ejemplo](https://picsum.photos/1200/640)",
982
+ "",
983
+ "## Video",
984
+ "",
985
+ "@[video:Video de ejemplo](https://www.youtube.com/watch?v=jNQXAC9IVRw)",
986
+ "",
987
+ "## Tabla",
988
+ "",
989
+ "| Bloque | Uso | Resultado |",
990
+ "| --- | --- | --- |",
991
+ "| T\xEDtulo | Separar secciones | Lectura m\xE1s clara |",
992
+ "| Imagen | Mostrar contexto visual | Contenido m\xE1s atractivo |",
993
+ "| Video | Explicar procesos | Mejor comprensi\xF3n |",
994
+ "",
995
+ "## Diagrama UML",
996
+ "",
997
+ "```mermaid",
998
+ "classDiagram",
999
+ " class Pagina {",
1000
+ " +string titulo",
1001
+ " +string contenido",
1002
+ " +publicar()",
1003
+ " }",
1004
+ " class Medio {",
1005
+ " +string tipo",
1006
+ " +string url",
1007
+ " }",
1008
+ " Pagina --> Medio",
1009
+ "```",
1010
+ "",
1011
+ "---",
1012
+ "",
1013
+ "Este ejemplo se puede editar, reorganizar o eliminar desde las acciones de cada bloque."
1014
+ ].join("\n");
1015
+ const MARKDOWN_EDITOR_SAMPLE_WITHOUT_DIAGRAMS = [
1016
+ "# P\xE1gina de ejemplo",
1017
+ "",
1018
+ "{color:#2563eb}Este contenido muestra varios bloques que se pueden construir con el editor visual.{/color}",
1019
+ "",
1020
+ "## Texto enriquecido",
1021
+ "",
1022
+ "Puedes combinar **negritas**, *cursivas*, `c\xF3digo`, enlaces como [ui-lib](https://example.com) y colores para destacar informaci\xF3n importante.",
1023
+ "",
1024
+ "- Bloques de texto",
1025
+ "- Listas y citas",
1026
+ "- Im\xE1genes, videos y tablas",
1027
+ "",
1028
+ "> Este bloque sirve para resaltar una nota, advertencia o resumen dentro del contenido.",
1029
+ "",
1030
+ "## Imagen",
1031
+ "",
1032
+ "![Imagen de ejemplo](https://picsum.photos/1200/640)",
1033
+ "",
1034
+ "## Video",
1035
+ "",
1036
+ "@[video:Video de ejemplo](https://www.youtube.com/watch?v=jNQXAC9IVRw)",
1037
+ "",
1038
+ "## Tabla",
1039
+ "",
1040
+ "| Bloque | Uso | Resultado |",
1041
+ "| --- | --- | --- |",
1042
+ "| T\xEDtulo | Separar secciones | Lectura m\xE1s clara |",
1043
+ "| Imagen | Mostrar contexto visual | Contenido m\xE1s atractivo |",
1044
+ "| Video | Explicar procesos | Mejor comprensi\xF3n |",
1045
+ "",
1046
+ "---",
1047
+ "",
1048
+ "Este ejemplo se puede editar, reorganizar o eliminar desde las acciones de cada bloque."
1049
+ ].join("\n");
1050
+ function readFileAsDataUrl(file) {
1051
+ return new Promise((resolve, reject) => {
1052
+ const reader = new FileReader();
1053
+ reader.onload = () => {
1054
+ var _a;
1055
+ return resolve(String((_a = reader.result) != null ? _a : ""));
1056
+ };
1057
+ reader.onerror = () => {
1058
+ var _a;
1059
+ return reject((_a = reader.error) != null ? _a : new Error("No se pudo leer el archivo."));
1060
+ };
1061
+ reader.readAsDataURL(file);
1062
+ });
1063
+ }
1064
+ const MarkdownPreviewContent = import_react.default.memo(function MarkdownPreviewContent2({
1065
+ html,
1066
+ className,
1067
+ interactive,
1068
+ selectable,
1069
+ showBlockControls,
1070
+ rootRef,
1071
+ onSelectPreviewRange,
1072
+ onUpdateHoverBlock
1073
+ }) {
1074
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1075
+ "div",
1076
+ {
1077
+ ref: rootRef,
1078
+ className: cx(
1079
+ "text-base leading-7 text-[var(--foreground)]",
1080
+ "[&>*]:my-0 [&_.fp-markdown-empty-line]:h-5 [&_.fp-markdown-empty-line]:leading-none [&_.fp-markdown-list-gap]:block [&_.fp-markdown-list-gap]:h-3",
1081
+ "[&_a]:font-medium [&_a]:text-[var(--primary)] [&_blockquote]:m-0 [&_blockquote]:border-l-4 [&_blockquote]:border-[var(--border)] [&_blockquote]:pl-3 [&_blockquote]:text-[var(--muted)]",
1082
+ "[&_code]:rounded-md [&_code]:bg-[color-mix(in_oklab,var(--muted)_14%,transparent)] [&_code]:px-1.5 [&_code]:py-0.5",
1083
+ interactive && "[&_[data-md-kind=line]]:block [&_[data-md-start]]:rounded-md [&_[data-md-start]]:transition [&_[data-md-start]:hover]:bg-[color-mix(in_oklab,var(--primary)_6%,transparent)]",
1084
+ selectable && "[&_[data-md-start]]:cursor-text",
1085
+ "[&_figcaption]:mt-2 [&_figcaption]:text-sm [&_figcaption]:text-[var(--muted)] [&_figure]:m-0",
1086
+ showBlockControls && "[&_iframe]:pointer-events-none",
1087
+ "[&_.fp-markdown-diagram]:overflow-auto [&_.fp-markdown-diagram]:rounded-xl [&_.fp-markdown-diagram]:border [&_.fp-markdown-diagram]:border-[var(--border)] [&_.fp-markdown-diagram]:bg-[color-mix(in_oklab,var(--card)_96%,var(--background))] [&_.fp-markdown-diagram]:p-4 [&_.fp-markdown-diagram-error]:border-[var(--danger)]",
1088
+ "[&_h1]:m-0 [&_h1]:text-3xl [&_h1]:font-semibold [&_h2]:m-0 [&_h2]:text-2xl [&_h2]:font-semibold [&_h3]:m-0 [&_h3]:text-xl [&_h3]:font-semibold [&_h4]:m-0 [&_h4]:text-lg [&_h4]:font-semibold",
1089
+ "[&_hr]:m-0 [&_hr]:border-[var(--border)] [&_iframe]:aspect-video [&_iframe]:w-full [&_iframe]:rounded-xl [&_iframe]:border [&_iframe]:border-[var(--border)] [&_img]:block [&_img]:max-h-[520px] [&_img]:w-auto [&_img]:max-w-full [&_img]:rounded-xl [&_img]:border [&_img]:border-[var(--border)] [&_img]:object-contain",
1090
+ "[&_ol]:m-0 [&_ol]:list-decimal [&_ol]:pl-5 [&_p]:m-0 [&_pre]:m-0 [&_pre]:overflow-auto [&_pre]:rounded-xl [&_pre]:border [&_pre]:border-[var(--border)] [&_pre]:bg-[color-mix(in_oklab,var(--foreground)_6%,transparent)] [&_pre]:p-3",
1091
+ "[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_strong]:font-semibold [&_table]:m-0 [&_table]:w-full [&_table]:border-collapse [&_table]:overflow-hidden [&_table]:rounded-xl [&_td]:border [&_td]:border-[var(--border)] [&_td]:px-3 [&_td]:py-2 [&_th]:border [&_th]:border-[var(--border)] [&_th]:bg-[color-mix(in_oklab,var(--muted)_10%,transparent)] [&_th]:px-3 [&_th]:py-2 [&_th]:text-left [&_th]:font-semibold [&_ul]:m-0 [&_ul]:list-disc [&_ul]:pl-5 [&_video]:max-h-[520px] [&_video]:w-full [&_video]:rounded-xl [&_video]:border [&_video]:border-[var(--border)]",
1092
+ className
1093
+ ),
1094
+ onClick: interactive ? onSelectPreviewRange : void 0,
1095
+ onMouseUp: interactive ? onSelectPreviewRange : void 0,
1096
+ onMouseMove: showBlockControls ? onUpdateHoverBlock : void 0,
1097
+ dangerouslySetInnerHTML: { __html: html }
1098
+ }
1099
+ );
1100
+ });
1101
+ function MarkdownPreview({
1102
+ value,
1103
+ className,
1104
+ emptyLabel = "Sin contenido.",
1105
+ selectable = false,
1106
+ selectedRange,
1107
+ onSelectRange,
1108
+ onOpenSelectionMenu,
1109
+ showBlockControls = false,
1110
+ onOpenBlockMenu
1111
+ }) {
1112
+ const source = String(value != null ? value : "");
1113
+ const interactive = selectable || showBlockControls || Boolean(onSelectRange) || Boolean(onOpenSelectionMenu) || Boolean(onOpenBlockMenu);
1114
+ const html = import_react.default.useMemo(() => renderMarkdown(source, { includeRanges: interactive, selectedRange }), [interactive, selectedRange, source]);
1115
+ const idPrefix = import_react.default.useId().replace(/[^a-zA-Z0-9_-]/g, "");
1116
+ const shellRef = import_react.default.useRef(null);
1117
+ const rootRef = import_react.default.useRef(null);
1118
+ const hoverBlockRef = import_react.default.useRef(null);
1119
+ const hoverFrameRef = import_react.default.useRef(null);
1120
+ const [hoverBlock, setHoverBlock] = import_react.default.useState(null);
1121
+ const readElementRange = import_react.default.useCallback((element) => {
1122
+ const target = element == null ? void 0 : element.closest("[data-md-start][data-md-end]");
1123
+ if (!target) return null;
1124
+ const start = Number(target.dataset.mdStart);
1125
+ const end = Number(target.dataset.mdEnd);
1126
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start > end) return null;
1127
+ return { start, end, kind: target.dataset.mdKind };
1128
+ }, []);
1129
+ const readSelectionBoundary = import_react.default.useCallback(
1130
+ (node, offset) => {
1131
+ var _a;
1132
+ const element = node instanceof Element ? node : node.parentElement;
1133
+ const target = element == null ? void 0 : element.closest("[data-md-start][data-md-end]");
1134
+ if (!target || !((_a = rootRef.current) == null ? void 0 : _a.contains(target))) return null;
1135
+ const start = Number(target.dataset.mdStart);
1136
+ const end = Number(target.dataset.mdEnd);
1137
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start > end) return null;
1138
+ const range = document.createRange();
1139
+ range.selectNodeContents(target);
1140
+ try {
1141
+ range.setEnd(node, offset);
1142
+ } catch {
1143
+ return null;
1144
+ }
1145
+ const renderedOffset = range.toString().length;
1146
+ const raw = source.slice(start, end);
1147
+ const markdownOffset = start + mapRenderedOffsetToMarkdown(raw, renderedOffset);
1148
+ return { offset: Math.max(start, Math.min(end, markdownOffset)), kind: target.dataset.mdKind };
1149
+ },
1150
+ [source]
1151
+ );
1152
+ const readExactDomSelectionRange = import_react.default.useCallback(
1153
+ (selection) => {
1154
+ if (!selection.rangeCount) return null;
1155
+ const root = rootRef.current;
1156
+ if (!root) return null;
1157
+ const range = selection.getRangeAt(0);
1158
+ if (!root.contains(range.startContainer) || !root.contains(range.endContainer)) return null;
1159
+ const start = readSelectionBoundary(range.startContainer, range.startOffset);
1160
+ const end = readSelectionBoundary(range.endContainer, range.endOffset);
1161
+ const focus = selection.focusNode ? readSelectionBoundary(selection.focusNode, selection.focusOffset) : null;
1162
+ if (!start || !end) return null;
1163
+ return {
1164
+ start: Math.min(start.offset, end.offset),
1165
+ end: Math.max(start.offset, end.offset),
1166
+ activeOffset: focus == null ? void 0 : focus.offset,
1167
+ kind: start.kind === end.kind ? start.kind : "selection"
1168
+ };
1169
+ },
1170
+ [readSelectionBoundary]
1171
+ );
1172
+ const selectPreviewRange = import_react.default.useCallback(
1173
+ (event) => {
1174
+ var _a;
1175
+ const root = rootRef.current;
1176
+ const selection = (_a = window.getSelection) == null ? void 0 : _a.call(window);
1177
+ if (root && selection && !selection.isCollapsed && root.contains(selection.anchorNode) && root.contains(selection.focusNode)) {
1178
+ const range2 = readExactDomSelectionRange(selection);
1179
+ if (range2 && range2.start !== range2.end) {
1180
+ onSelectRange == null ? void 0 : onSelectRange(range2);
1181
+ onOpenSelectionMenu == null ? void 0 : onOpenSelectionMenu(event, range2);
1182
+ return;
1183
+ }
1184
+ }
1185
+ const range = readElementRange(event.target);
1186
+ if (!range) return;
1187
+ },
1188
+ [onOpenSelectionMenu, onSelectRange, readElementRange, readExactDomSelectionRange]
1189
+ );
1190
+ const updateHoverBlock = import_react.default.useCallback(
1191
+ (event) => {
1192
+ var _a;
1193
+ if (!showBlockControls) return;
1194
+ const root = rootRef.current;
1195
+ const shell = shellRef.current;
1196
+ if (!root || !shell) return;
1197
+ const eventTarget = event.target;
1198
+ const target = eventTarget == null ? void 0 : eventTarget.closest("[data-md-start][data-md-end]");
1199
+ if (!eventTarget || !target || !root.contains(target)) {
1200
+ if (hoverBlockRef.current) {
1201
+ hoverBlockRef.current = null;
1202
+ if (hoverFrameRef.current !== null) {
1203
+ cancelAnimationFrame(hoverFrameRef.current);
1204
+ hoverFrameRef.current = null;
1205
+ }
1206
+ setHoverBlock((current) => current ? null : current);
1207
+ }
1208
+ return;
1209
+ }
1210
+ const start = Number(target.dataset.mdStart);
1211
+ const end = Number(target.dataset.mdEnd);
1212
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return;
1213
+ const visualTarget = (_a = eventTarget.closest("figure, img, video, iframe, table, pre, blockquote, h1, h2, h3, h4")) != null ? _a : target;
1214
+ const rect = visualTarget.getBoundingClientRect();
1215
+ const shellRect = shell.getBoundingClientRect();
1216
+ const next = {
1217
+ start,
1218
+ end,
1219
+ kind: target.dataset.mdKind,
1220
+ top: Math.round(rect.top - shellRect.top),
1221
+ height: Math.round(Math.max(28, rect.height))
1222
+ };
1223
+ const previous = hoverBlockRef.current;
1224
+ if (previous && previous.start === next.start && previous.end === next.end && previous.kind === next.kind && previous.top === next.top && previous.height === next.height) {
1225
+ return;
1226
+ }
1227
+ hoverBlockRef.current = next;
1228
+ if (hoverFrameRef.current !== null) {
1229
+ cancelAnimationFrame(hoverFrameRef.current);
1230
+ }
1231
+ hoverFrameRef.current = requestAnimationFrame(() => {
1232
+ hoverFrameRef.current = null;
1233
+ setHoverBlock(next);
1234
+ });
1235
+ },
1236
+ [showBlockControls]
1237
+ );
1238
+ const clearHoverBlock = import_react.default.useCallback(() => {
1239
+ hoverBlockRef.current = null;
1240
+ if (hoverFrameRef.current !== null) {
1241
+ cancelAnimationFrame(hoverFrameRef.current);
1242
+ hoverFrameRef.current = null;
1243
+ }
1244
+ setHoverBlock((current) => current ? null : current);
1245
+ }, []);
1246
+ import_react.default.useEffect(() => {
1247
+ return () => {
1248
+ if (hoverFrameRef.current !== null) {
1249
+ cancelAnimationFrame(hoverFrameRef.current);
1250
+ }
1251
+ };
1252
+ }, []);
1253
+ import_react.default.useEffect(() => {
1254
+ const root = rootRef.current;
1255
+ if (!root) return;
1256
+ const nodes = Array.from(root.querySelectorAll(".fp-markdown-diagram[data-diagram]"));
1257
+ if (!nodes.length) return;
1258
+ let cancelled = false;
1259
+ void import("mermaid").then((module2) => {
1260
+ const mermaid = module2.default;
1261
+ mermaid.initialize({
1262
+ startOnLoad: false,
1263
+ securityLevel: "strict",
1264
+ theme: document.documentElement.classList.contains("dark") ? "dark" : "default"
1265
+ });
1266
+ nodes.forEach((node, index) => {
1267
+ var _a;
1268
+ const diagram = (_a = node.dataset.diagram) != null ? _a : "";
1269
+ if (!diagram.trim()) return;
1270
+ void mermaid.render(`fpMarkdownDiagram${idPrefix}${index}`, diagram).then(({ svg }) => {
1271
+ if (cancelled) return;
1272
+ node.innerHTML = svg;
1273
+ }).catch(() => {
1274
+ node.classList.add("fp-markdown-diagram-error");
1275
+ });
1276
+ });
1277
+ }).catch(() => {
1278
+ nodes.forEach((node) => node.classList.add("fp-markdown-diagram-error"));
1279
+ });
1280
+ return () => {
1281
+ cancelled = true;
1282
+ };
1283
+ }, [html, idPrefix]);
1284
+ if (!source.trim()) {
1285
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("text-sm text-[var(--muted)]", className), children: emptyLabel });
1286
+ }
1287
+ const content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1288
+ MarkdownPreviewContent,
1289
+ {
1290
+ html,
1291
+ className,
1292
+ interactive,
1293
+ selectable,
1294
+ showBlockControls,
1295
+ rootRef,
1296
+ onSelectPreviewRange: selectPreviewRange,
1297
+ onUpdateHoverBlock: updateHoverBlock
1298
+ }
1299
+ );
1300
+ if (!showBlockControls) return content;
1301
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: shellRef, className: "relative pl-9", onMouseLeave: clearHoverBlock, children: [
1302
+ content,
1303
+ hoverBlock ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1304
+ "div",
1305
+ {
1306
+ className: "pointer-events-none absolute inset-x-0",
1307
+ style: { top: hoverBlock.top, height: hoverBlock.height },
1308
+ children: [
1309
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute bottom-0 left-9 right-0 top-0 rounded-lg border border-dashed border-[color-mix(in_oklab,var(--primary)_34%,transparent)] bg-[color-mix(in_oklab,var(--primary)_5%,transparent)]" }),
1310
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1311
+ "button",
1312
+ {
1313
+ type: "button",
1314
+ title: "Acciones del bloque",
1315
+ "aria-label": "Acciones del bloque",
1316
+ className: "pointer-events-auto absolute left-0 top-1/2 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-lg border border-[var(--border)] bg-[var(--card)] text-[var(--foreground)] shadow-lg transition hover:border-[var(--primary)] hover:text-[var(--primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]",
1317
+ onMouseDown: (event) => {
1318
+ event.preventDefault();
1319
+ event.stopPropagation();
1320
+ },
1321
+ onClick: (event) => {
1322
+ event.stopPropagation();
1323
+ const range = { start: hoverBlock.start, end: hoverBlock.end, kind: hoverBlock.kind };
1324
+ onOpenBlockMenu == null ? void 0 : onOpenBlockMenu(event, range);
1325
+ },
1326
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.MenuPuntosVerticalIcon, { className: "h-4 w-4" })
1327
+ }
1328
+ )
1329
+ ]
1330
+ }
1331
+ ) : null
1332
+ ] });
1333
+ }
79
1334
  function MarkdownEditor({
80
1335
  value,
81
1336
  onChange,
@@ -84,12 +1339,129 @@ function MarkdownEditor({
84
1339
  height,
85
1340
  className,
86
1341
  templates,
87
- disabled = false
1342
+ disabled = false,
1343
+ fullscreenHeight = "calc(100vh - 196px)",
1344
+ showPreview = true,
1345
+ enablePreview = true,
1346
+ enableFullscreen = true,
1347
+ enableDiagrams = true,
1348
+ labels,
1349
+ maxImageBytes = 6 * 1024 * 1024,
1350
+ maxVideoBytes = 24 * 1024 * 1024
88
1351
  }) {
89
1352
  const ref = import_react.default.useRef(null);
1353
+ const imageInputRef = import_react.default.useRef(null);
1354
+ const videoInputRef = import_react.default.useRef(null);
1355
+ const uploadInsertPositionRef = import_react.default.useRef(null);
1356
+ const blockMenuRef = import_react.default.useRef(null);
1357
+ const selectionMenuRef = import_react.default.useRef(null);
1358
+ const undoStackRef = import_react.default.useRef([]);
1359
+ const redoStackRef = import_react.default.useRef([]);
1360
+ const selectionRef = import_react.default.useRef({ start: 0, end: 0 });
1361
+ const [blockMenu, setBlockMenu] = import_react.default.useState(null);
1362
+ const [selectionMenu, setSelectionMenu] = import_react.default.useState(null);
1363
+ const [visibleViews, setVisibleViews] = import_react.default.useState(() => ({
1364
+ blocks: showPreview && enablePreview,
1365
+ preview: false,
1366
+ markdown: !(showPreview && enablePreview)
1367
+ }));
1368
+ const [fullscreen, setFullscreen] = import_react.default.useState(false);
1369
+ const [portalTarget, setPortalTarget] = import_react.default.useState(null);
1370
+ const L = {
1371
+ editor: "Editor",
1372
+ preview: "Preview",
1373
+ emptyPreview: "El preview aparecer\xE1 cuando escribas contenido.",
1374
+ headings: "Formato",
1375
+ paragraph: "Texto",
1376
+ h1: "Encabezado 1",
1377
+ h2: "Encabezado 2",
1378
+ h3: "Encabezado 3",
1379
+ quote: "Cita",
1380
+ undo: "Deshacer",
1381
+ redo: "Rehacer",
1382
+ bold: "Negrita",
1383
+ italic: "Cursiva",
1384
+ code: "C\xF3digo",
1385
+ bullets: "Lista",
1386
+ numbers: "Lista numerada",
1387
+ link: "Enlace",
1388
+ color: "Color de texto",
1389
+ insert: "Insertar",
1390
+ imageUrl: "Imagen desde URL",
1391
+ imageUpload: "Subir imagen",
1392
+ videoUrl: "Video desde URL",
1393
+ videoUpload: "Subir video",
1394
+ table: "Tabla",
1395
+ lineBreak: "Salto de l\xEDnea",
1396
+ diagram: "Diagrama UML",
1397
+ umlClass: "Diagrama de clases",
1398
+ umlSequence: "Diagrama de secuencia",
1399
+ divider: "Separador",
1400
+ blocksView: "Construcci\xF3n",
1401
+ markdownView: "Markdown",
1402
+ previewView: "Preview",
1403
+ sampleButton: "Construir ejemplo",
1404
+ showPreview: "Mostrar preview",
1405
+ hidePreview: "Ocultar preview",
1406
+ fullscreen: "Pantalla completa",
1407
+ exitFullscreen: "Salir de pantalla completa",
1408
+ ...labels
1409
+ };
1410
+ import_react.default.useEffect(() => {
1411
+ if (typeof document === "undefined") return;
1412
+ setPortalTarget(document.body);
1413
+ }, []);
1414
+ import_react.default.useEffect(() => {
1415
+ setVisibleViews((current) => {
1416
+ if (!enablePreview) {
1417
+ return current.markdown && !current.blocks && !current.preview ? current : { blocks: false, preview: false, markdown: true };
1418
+ }
1419
+ if (current.blocks || current.preview || current.markdown) return current;
1420
+ return { blocks: true, preview: false, markdown: false };
1421
+ });
1422
+ }, [enablePreview]);
1423
+ import_react.default.useEffect(() => {
1424
+ if (!blockMenu && !selectionMenu) return;
1425
+ const close = () => {
1426
+ setBlockMenu(null);
1427
+ setSelectionMenu(null);
1428
+ };
1429
+ const onPointerDown = (event) => {
1430
+ var _a, _b;
1431
+ const target = event.target;
1432
+ if (target && (((_a = blockMenuRef.current) == null ? void 0 : _a.contains(target)) || ((_b = selectionMenuRef.current) == null ? void 0 : _b.contains(target)))) return;
1433
+ close();
1434
+ };
1435
+ const onKey = (event) => {
1436
+ if (event.key === "Escape") close();
1437
+ };
1438
+ window.addEventListener("pointerdown", onPointerDown, true);
1439
+ window.addEventListener("scroll", close, true);
1440
+ window.addEventListener("resize", close);
1441
+ window.addEventListener("keydown", onKey);
1442
+ return () => {
1443
+ window.removeEventListener("pointerdown", onPointerDown, true);
1444
+ window.removeEventListener("scroll", close, true);
1445
+ window.removeEventListener("resize", close);
1446
+ window.removeEventListener("keydown", onKey);
1447
+ };
1448
+ }, [blockMenu, selectionMenu]);
1449
+ const pushUndo = import_react.default.useCallback((snapshot) => {
1450
+ const stack = undoStackRef.current;
1451
+ if (stack[stack.length - 1] === snapshot) return;
1452
+ stack.push(snapshot);
1453
+ if (stack.length > 80) stack.shift();
1454
+ }, []);
90
1455
  const apply = import_react.default.useCallback(
91
- (next, selectFrom, selectTo) => {
1456
+ (next, selectFrom, selectTo, recordHistory = true) => {
1457
+ if (recordHistory && next !== value) {
1458
+ pushUndo(value);
1459
+ redoStackRef.current = [];
1460
+ }
92
1461
  onChange(next);
1462
+ if (typeof selectFrom === "number" && typeof selectTo === "number") {
1463
+ selectionRef.current = { start: selectFrom, end: selectTo };
1464
+ }
93
1465
  const el = ref.current;
94
1466
  if (!el) return;
95
1467
  requestAnimationFrame(() => {
@@ -102,46 +1474,267 @@ function MarkdownEditor({
102
1474
  }
103
1475
  });
104
1476
  },
105
- [onChange]
1477
+ [onChange, pushUndo, value]
106
1478
  );
1479
+ const rememberSelection = import_react.default.useCallback(() => {
1480
+ var _a, _b, _c;
1481
+ const el = ref.current;
1482
+ if (!el) return selectionRef.current;
1483
+ selectionRef.current = {
1484
+ start: (_a = el.selectionStart) != null ? _a : 0,
1485
+ end: (_c = (_b = el.selectionEnd) != null ? _b : el.selectionStart) != null ? _c : 0
1486
+ };
1487
+ return selectionRef.current;
1488
+ }, []);
1489
+ const activeSelection = import_react.default.useCallback(() => {
1490
+ var _a, _b;
1491
+ const el = ref.current;
1492
+ if (!el) return selectionRef.current;
1493
+ if (typeof document !== "undefined" && document.activeElement !== el) {
1494
+ return selectionRef.current;
1495
+ }
1496
+ const start = (_a = el.selectionStart) != null ? _a : selectionRef.current.start;
1497
+ const end = (_b = el.selectionEnd) != null ? _b : selectionRef.current.end;
1498
+ selectionRef.current = { start, end };
1499
+ return selectionRef.current;
1500
+ }, []);
1501
+ const selectMarkdownRange = import_react.default.useCallback((range) => {
1502
+ const start = Math.max(0, Math.min(value.length, range.start));
1503
+ const end = Math.max(0, Math.min(value.length, range.end));
1504
+ const next = { start: Math.min(start, end), end: Math.max(start, end) };
1505
+ selectionRef.current = next;
1506
+ requestAnimationFrame(() => {
1507
+ var _a;
1508
+ try {
1509
+ (_a = ref.current) == null ? void 0 : _a.setSelectionRange(next.start, next.end);
1510
+ } catch {
1511
+ }
1512
+ });
1513
+ }, [value.length]);
1514
+ const handleTextChange = import_react.default.useCallback(
1515
+ (next) => {
1516
+ if (next !== value) {
1517
+ pushUndo(value);
1518
+ redoStackRef.current = [];
1519
+ }
1520
+ onChange(next);
1521
+ },
1522
+ [onChange, pushUndo, value]
1523
+ );
1524
+ const doUndo = import_react.default.useCallback(() => {
1525
+ const previous = undoStackRef.current.pop();
1526
+ if (previous === void 0) return;
1527
+ redoStackRef.current.push(value);
1528
+ apply(previous, previous.length, previous.length, false);
1529
+ }, [apply, value]);
1530
+ const doRedo = import_react.default.useCallback(() => {
1531
+ const next = redoStackRef.current.pop();
1532
+ if (next === void 0) return;
1533
+ pushUndo(value);
1534
+ apply(next, next.length, next.length, false);
1535
+ }, [apply, pushUndo, value]);
107
1536
  const actWrap = import_react.default.useCallback(
108
1537
  (left, right, fallbackText) => {
109
1538
  var _a, _b;
110
- const el = ref.current;
111
- if (!el) return;
112
- const { next, selectFrom, selectTo } = wrapSelection(el.value, (_a = el.selectionStart) != null ? _a : 0, (_b = el.selectionEnd) != null ? _b : 0, left, right, fallbackText);
1539
+ const current = (_b = (_a = ref.current) == null ? void 0 : _a.value) != null ? _b : value;
1540
+ const selection = activeSelection();
1541
+ const { next, selectFrom, selectTo } = smartWrapSelection(current, selection.start, selection.end, left, right, fallbackText);
113
1542
  apply(next, selectFrom, selectTo);
114
1543
  },
115
- [apply]
1544
+ [activeSelection, apply, value]
116
1545
  );
1546
+ const actLink = import_react.default.useCallback(() => {
1547
+ var _a, _b;
1548
+ const current = (_b = (_a = ref.current) == null ? void 0 : _a.value) != null ? _b : value;
1549
+ const selection = activeSelection();
1550
+ const range = getSmartTextRange(current, selection.start, selection.end);
1551
+ const wrapper = findLinkWrapper(current, range.from, range.to);
1552
+ if (wrapper) {
1553
+ const result2 = splitLinkWrapper(current, wrapper, range);
1554
+ if (result2) apply(result2.next, result2.selectFrom, result2.selectTo);
1555
+ return;
1556
+ }
1557
+ const url = safeWindowPrompt("URL del enlace", "https://");
1558
+ if (!url) return;
1559
+ const result = smartLinkSelection(current, selection.start, selection.end, url);
1560
+ if (result) apply(result.next, result.selectFrom, result.selectTo);
1561
+ }, [activeSelection, apply, value]);
117
1562
  const actPrefix = import_react.default.useCallback(
118
1563
  (prefix) => {
119
1564
  var _a, _b;
120
- const el = ref.current;
121
- if (!el) return;
122
- const { lineStart, lineEnd, block } = getSelectedLines(el.value, (_a = el.selectionStart) != null ? _a : 0, (_b = el.selectionEnd) != null ? _b : 0);
1565
+ const current = (_b = (_a = ref.current) == null ? void 0 : _a.value) != null ? _b : value;
1566
+ const selection = activeSelection();
1567
+ const { lineStart, lineEnd, block } = getSelectedLines(current, selection.start, selection.end);
123
1568
  const replaced = prefixLines(block, prefix);
124
- const next = el.value.slice(0, lineStart) + replaced + el.value.slice(lineEnd);
1569
+ const next = current.slice(0, lineStart) + replaced + current.slice(lineEnd);
125
1570
  apply(next, lineStart, lineStart + replaced.length);
126
1571
  },
127
- [apply]
1572
+ [activeSelection, apply, value]
128
1573
  );
129
1574
  const actInsert = import_react.default.useCallback(
130
1575
  (text) => {
131
1576
  var _a, _b;
132
- const el = ref.current;
133
- if (!el) return;
134
- const start = (_a = el.selectionStart) != null ? _a : 0;
135
- const end = (_b = el.selectionEnd) != null ? _b : 0;
136
- const a = Math.max(0, Math.min(el.value.length, start));
137
- const b = Math.max(0, Math.min(el.value.length, end));
1577
+ const current = (_b = (_a = ref.current) == null ? void 0 : _a.value) != null ? _b : value;
1578
+ const selection = activeSelection();
1579
+ const start = selection.start;
1580
+ const end = selection.end;
1581
+ const a = Math.max(0, Math.min(current.length, start));
1582
+ const b = Math.max(0, Math.min(current.length, end));
138
1583
  const from = Math.min(a, b);
139
1584
  const to = Math.max(a, b);
140
- const next = el.value.slice(0, from) + text + el.value.slice(to);
1585
+ const next = current.slice(0, from) + text + current.slice(to);
141
1586
  apply(next, from + text.length, from + text.length);
142
1587
  },
1588
+ [activeSelection, apply, value]
1589
+ );
1590
+ const actLineBreak = import_react.default.useCallback(() => {
1591
+ actInsert("\n");
1592
+ }, [actInsert]);
1593
+ const actNewParagraph = import_react.default.useCallback(() => {
1594
+ actInsert("\n\n");
1595
+ }, [actInsert]);
1596
+ const handleMarkdownKeyDown = import_react.default.useCallback(
1597
+ (event) => {
1598
+ var _a, _b, _c, _d, _e, _f;
1599
+ if (event.key !== "Enter" || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing) return;
1600
+ const target = event.currentTarget;
1601
+ const current = target.value;
1602
+ const start = (_a = target.selectionStart) != null ? _a : 0;
1603
+ const end = (_b = target.selectionEnd) != null ? _b : start;
1604
+ if (start !== end) return;
1605
+ const lineStart = current.lastIndexOf("\n", Math.max(0, start - 1)) + 1;
1606
+ const lineEndIndex = current.indexOf("\n", start);
1607
+ const lineEnd = lineEndIndex === -1 ? current.length : lineEndIndex;
1608
+ const line = current.slice(lineStart, lineEnd);
1609
+ const ordered = /^(\s*)(\d+)\.\s*(.*)$/.exec(line);
1610
+ const unordered = /^(\s*)([-*])\s*(.*)$/.exec(line);
1611
+ const isInListContext = () => {
1612
+ var _a2;
1613
+ if (ordered || unordered) return true;
1614
+ const previousLines = current.slice(0, lineStart).split("\n");
1615
+ for (let index = previousLines.length - 1; index >= 0; index -= 1) {
1616
+ const previous = (_a2 = previousLines[index]) != null ? _a2 : "";
1617
+ if (!previous.trim()) continue;
1618
+ if (/^\s*\d+\.\s+/.test(previous) || /^\s*[-*]\s+/.test(previous)) return true;
1619
+ if (/^#{1,4}\s+/.test(previous.trim()) || /^---+$/.test(previous.trim()) || /^>\s?/.test(previous.trim())) return false;
1620
+ }
1621
+ return false;
1622
+ };
1623
+ if (event.shiftKey) {
1624
+ if (!isInListContext()) return;
1625
+ event.preventDefault();
1626
+ const insert = " \n";
1627
+ const next = current.slice(0, start) + insert + current.slice(start);
1628
+ const cursor = start + insert.length;
1629
+ apply(next, cursor, cursor);
1630
+ return;
1631
+ }
1632
+ if (ordered && !((_c = ordered[3]) == null ? void 0 : _c.trim())) {
1633
+ event.preventDefault();
1634
+ const next = current.slice(0, lineStart) + current.slice(lineEnd);
1635
+ apply(next, lineStart, lineStart);
1636
+ return;
1637
+ }
1638
+ if (unordered && !((_d = unordered[3]) == null ? void 0 : _d.trim())) {
1639
+ event.preventDefault();
1640
+ const next = current.slice(0, lineStart) + current.slice(lineEnd);
1641
+ apply(next, lineStart, lineStart);
1642
+ return;
1643
+ }
1644
+ if (ordered) {
1645
+ event.preventDefault();
1646
+ const nextNumber = Number((_e = ordered[2]) != null ? _e : "0") + 1;
1647
+ const insert = `
1648
+ ${nextNumber}. `;
1649
+ const next = current.slice(0, start) + insert + current.slice(start);
1650
+ const cursor = start + insert.length;
1651
+ apply(next, cursor, cursor);
1652
+ return;
1653
+ }
1654
+ if (unordered) {
1655
+ event.preventDefault();
1656
+ const insert = `
1657
+ ${(_f = unordered[2]) != null ? _f : "-"} `;
1658
+ const next = current.slice(0, start) + insert + current.slice(start);
1659
+ const cursor = start + insert.length;
1660
+ apply(next, cursor, cursor);
1661
+ }
1662
+ },
143
1663
  [apply]
144
1664
  );
1665
+ const actInsertAt = import_react.default.useCallback(
1666
+ (position, text) => {
1667
+ const safePosition = Math.max(0, Math.min(value.length, position));
1668
+ const next = value.slice(0, safePosition) + text + value.slice(safePosition);
1669
+ const cursor = safePosition + text.length;
1670
+ apply(next, cursor, cursor);
1671
+ },
1672
+ [apply, value]
1673
+ );
1674
+ const actDeleteBlock = import_react.default.useCallback(
1675
+ (range) => {
1676
+ const { next, cursor } = removeMarkdownBlock(value, range);
1677
+ if (next === value) return;
1678
+ apply(next, cursor, cursor);
1679
+ },
1680
+ [apply, value]
1681
+ );
1682
+ const actInsertBlockAt = import_react.default.useCallback(
1683
+ (position, text) => {
1684
+ const block = text.trim();
1685
+ if (!block) return;
1686
+ const safePosition = Math.max(0, Math.min(value.length, position));
1687
+ const before = value.slice(0, safePosition);
1688
+ const after = value.slice(safePosition);
1689
+ const prefix = before.trim().length && !before.endsWith("\n\n") ? before.endsWith("\n") ? "\n" : "\n\n" : "";
1690
+ const suffix = after.trim().length && !after.startsWith("\n\n") ? after.startsWith("\n") ? "\n" : "\n\n" : "";
1691
+ const next = before + prefix + block + suffix + after;
1692
+ const cursor = before.length + prefix.length + block.length;
1693
+ apply(next, cursor, cursor);
1694
+ },
1695
+ [apply, value]
1696
+ );
1697
+ const openBlockMenu = import_react.default.useCallback((event, range) => {
1698
+ const position = range.end;
1699
+ setSelectionMenu(null);
1700
+ setBlockMenu({ x: event.clientX, y: event.clientY, position, range });
1701
+ }, []);
1702
+ const openSelectionMenu = import_react.default.useCallback((event, range) => {
1703
+ if (range.start === range.end) return;
1704
+ setBlockMenu(null);
1705
+ setSelectionMenu({ x: event.clientX, y: event.clientY, range, marks: detectSelectionMarks(value, range) });
1706
+ }, [value]);
1707
+ const actInsertBlock = import_react.default.useCallback(
1708
+ (text) => {
1709
+ var _a, _b, _c, _d;
1710
+ const current = (_b = (_a = ref.current) == null ? void 0 : _a.value) != null ? _b : value;
1711
+ const selection = activeSelection();
1712
+ const start = (_c = selection.start) != null ? _c : current.length;
1713
+ const end = (_d = selection.end) != null ? _d : start;
1714
+ const a = Math.max(0, Math.min(current.length, start));
1715
+ const b = Math.max(0, Math.min(current.length, end));
1716
+ const from = Math.min(a, b);
1717
+ const to = Math.max(a, b);
1718
+ const before = current.slice(0, from);
1719
+ const after = current.slice(to);
1720
+ const prefix = before.trim().length && !before.endsWith("\n\n") ? "\n\n" : "";
1721
+ const suffix = after.trim().length && !after.startsWith("\n\n") ? "\n\n" : "";
1722
+ const next = before + prefix + text.trim() + suffix + after;
1723
+ const cursor = before.length + prefix.length + text.trim().length;
1724
+ apply(next, cursor, cursor);
1725
+ },
1726
+ [activeSelection, apply, value]
1727
+ );
1728
+ const actColor = import_react.default.useCallback(
1729
+ (color) => {
1730
+ var _a, _b;
1731
+ const current = (_b = (_a = ref.current) == null ? void 0 : _a.value) != null ? _b : value;
1732
+ const selection = activeSelection();
1733
+ const { next, selectFrom, selectTo } = applyColorToSelection(current, selection.start, selection.end, color);
1734
+ apply(next, selectFrom, selectTo);
1735
+ },
1736
+ [activeSelection, apply, value]
1737
+ );
145
1738
  const insertTemplate = import_react.default.useCallback(
146
1739
  (t) => {
147
1740
  if (!t.content.trim()) return;
@@ -150,50 +1743,762 @@ function MarkdownEditor({
150
1743
  },
151
1744
  [apply, value]
152
1745
  );
153
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cx("overflow-hidden rounded-2xl border border-[var(--border)] bg-[var(--card)]", className), children: [
154
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-center gap-1 border-b border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-2", children: [
155
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actWrap("**", "**", "texto"), children: "B" }),
156
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actWrap("*", "*", "texto"), children: "I" }),
157
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actWrap("`", "`", "c\xF3digo"), children: "</>" }),
158
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mx-1 h-5 w-px bg-[var(--border)]" }),
159
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actPrefix("## "), children: "H2" }),
160
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actPrefix("### "), children: "H3" }),
161
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actPrefix("- "), children: "\u2022 Lista" }),
162
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actPrefix("1. "), children: "1." }),
163
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actPrefix("> "), children: "\u201C\u201D" }),
164
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mx-1 h-5 w-px bg-[var(--border)]" }),
165
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
166
- import_Button.default,
1746
+ const insertExample = import_react.default.useCallback(() => {
1747
+ const sep = value.trim().length ? "\n\n---\n\n" : "";
1748
+ const next = value + sep + (enableDiagrams ? MARKDOWN_EDITOR_SAMPLE : MARKDOWN_EDITOR_SAMPLE_WITHOUT_DIAGRAMS) + "\n";
1749
+ apply(next, next.length, next.length);
1750
+ if (enablePreview) {
1751
+ setVisibleViews((current) => ({ ...current, blocks: true, preview: true }));
1752
+ }
1753
+ }, [apply, enableDiagrams, enablePreview, value]);
1754
+ const alertMessage = import_react.default.useCallback((message) => {
1755
+ try {
1756
+ window.alert(message);
1757
+ } catch {
1758
+ }
1759
+ }, []);
1760
+ const renderSelectionMenu = () => {
1761
+ if (!selectionMenu || !portalTarget) return null;
1762
+ const activeButtonClass = "bg-[color-mix(in_oklab,var(--primary)_14%,transparent)] text-[var(--primary)] ring-1 ring-[color-mix(in_oklab,var(--primary)_35%,transparent)]";
1763
+ const menuButtonClass = (active) => cx(
1764
+ "flex h-8 w-8 items-center justify-center rounded-lg hover:bg-[color-mix(in_oklab,var(--primary)_10%,transparent)]",
1765
+ active && activeButtonClass
1766
+ );
1767
+ return (0, import_react_dom.createPortal)(
1768
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1769
+ "div",
1770
+ {
1771
+ ref: selectionMenuRef,
1772
+ className: "fixed z-[2147483647] flex max-w-[min(92vw,520px)] flex-wrap items-center gap-1 rounded-xl border border-[var(--border)] bg-[var(--card)] p-1 text-sm text-[var(--foreground)] shadow-2xl",
1773
+ style: { left: Math.min(selectionMenu.x, Math.max(8, window.innerWidth - 540)), top: Math.min(selectionMenu.y + 10, Math.max(8, window.innerHeight - 110)) },
1774
+ children: [
1775
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1776
+ "button",
1777
+ {
1778
+ type: "button",
1779
+ className: menuButtonClass(selectionMenu.marks.bold),
1780
+ title: L.bold,
1781
+ onClick: () => {
1782
+ actWrap("**", "**", "texto");
1783
+ setSelectionMenu(null);
1784
+ },
1785
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TextBIcon, { className: "h-4 w-4" })
1786
+ }
1787
+ ),
1788
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1789
+ "button",
1790
+ {
1791
+ type: "button",
1792
+ className: menuButtonClass(selectionMenu.marks.italic),
1793
+ title: L.italic,
1794
+ onClick: () => {
1795
+ actWrap("*", "*", "texto");
1796
+ setSelectionMenu(null);
1797
+ },
1798
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TextItalicIcon, { className: "h-4 w-4" })
1799
+ }
1800
+ ),
1801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1802
+ "button",
1803
+ {
1804
+ type: "button",
1805
+ className: menuButtonClass(selectionMenu.marks.code),
1806
+ title: L.code,
1807
+ onClick: () => {
1808
+ actWrap("`", "`", "c\xF3digo");
1809
+ setSelectionMenu(null);
1810
+ },
1811
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CodeIcon, { className: "h-4 w-4" })
1812
+ }
1813
+ ),
1814
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1815
+ "button",
1816
+ {
1817
+ type: "button",
1818
+ className: menuButtonClass(selectionMenu.marks.link),
1819
+ title: L.link,
1820
+ onClick: () => {
1821
+ actLink();
1822
+ setSelectionMenu(null);
1823
+ },
1824
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.LinkIcon, { className: "h-4 w-4" })
1825
+ }
1826
+ ),
1827
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mx-1 h-6 w-px bg-[var(--border)]" }),
1828
+ COLOR_SWATCHES.map((color) => {
1829
+ var _a;
1830
+ const selected = ((_a = selectionMenu.marks.color) == null ? void 0 : _a.toLowerCase()) === color.toLowerCase();
1831
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1832
+ "button",
1833
+ {
1834
+ type: "button",
1835
+ title: color,
1836
+ className: cx(
1837
+ "relative h-6 w-6 rounded-full border border-[var(--border)] shadow-sm outline-none transition hover:scale-105 focus-visible:ring-2 focus-visible:ring-[var(--ring)]",
1838
+ selected && "z-10 scale-110 ring-2 ring-[var(--ring)] ring-offset-2 ring-offset-[var(--card)] after:absolute after:inset-1.5 after:rounded-full after:border after:border-white/80"
1839
+ ),
1840
+ style: { backgroundColor: color },
1841
+ onClick: () => {
1842
+ actColor(color);
1843
+ setSelectionMenu(null);
1844
+ }
1845
+ },
1846
+ color
1847
+ );
1848
+ })
1849
+ ]
1850
+ }
1851
+ ),
1852
+ portalTarget
1853
+ );
1854
+ };
1855
+ const closeBlockMenu = import_react.default.useCallback(() => {
1856
+ setBlockMenu(null);
1857
+ }, []);
1858
+ const renderBlockMenu = () => {
1859
+ if (!blockMenu || !portalTarget) return null;
1860
+ const position = blockMenu.position;
1861
+ const itemClass = "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left hover:bg-[color-mix(in_oklab,var(--primary)_10%,transparent)]";
1862
+ return (0, import_react_dom.createPortal)(
1863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1864
+ "div",
167
1865
  {
168
- size: "xs",
169
- variant: "outline",
170
- disabled,
171
- onClick: () => {
172
- const url = safeWindowPrompt("URL del enlace", "https://");
173
- if (!url) return;
174
- actWrap("[", `](${url})`, "texto");
175
- },
176
- children: "Enlace"
1866
+ ref: blockMenuRef,
1867
+ className: "fixed z-[2147483647] grid max-h-[min(80vh,520px)] min-w-[240px] gap-1 overflow-y-auto overscroll-contain rounded-xl border border-[var(--border)] bg-[var(--card)] p-1 text-sm text-[var(--foreground)] shadow-2xl",
1868
+ style: { left: Math.min(blockMenu.x, Math.max(8, window.innerWidth - 260)), top: Math.min(blockMenu.y, Math.max(8, window.innerHeight - 360)) },
1869
+ children: [
1870
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-3 py-2 text-xs font-semibold uppercase tracking-wide text-[var(--muted)]", children: "Agregar bloque" }),
1871
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1872
+ "button",
1873
+ {
1874
+ type: "button",
1875
+ className: itemClass,
1876
+ onClick: () => {
1877
+ actInsertAt(position, "\n");
1878
+ closeBlockMenu();
1879
+ },
1880
+ children: [
1881
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.MinusIcon, { className: "h-4 w-4 rotate-90" }),
1882
+ "Salto de l\xEDnea"
1883
+ ]
1884
+ }
1885
+ ),
1886
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1887
+ "button",
1888
+ {
1889
+ type: "button",
1890
+ className: itemClass,
1891
+ onClick: () => {
1892
+ actInsertBlockAt(position, "## Titulo");
1893
+ closeBlockMenu();
1894
+ },
1895
+ children: [
1896
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold", children: "H2" }),
1897
+ "Titulo"
1898
+ ]
1899
+ }
1900
+ ),
1901
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1902
+ "button",
1903
+ {
1904
+ type: "button",
1905
+ className: itemClass,
1906
+ onClick: () => {
1907
+ actInsertBlockAt(position, "Nuevo p\xE1rrafo");
1908
+ closeBlockMenu();
1909
+ },
1910
+ children: [
1911
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TextTIcon, { className: "h-4 w-4" }),
1912
+ "P\xE1rrafo"
1913
+ ]
1914
+ }
1915
+ ),
1916
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1917
+ "button",
1918
+ {
1919
+ type: "button",
1920
+ className: itemClass,
1921
+ onClick: () => {
1922
+ insertImageUrl(position);
1923
+ closeBlockMenu();
1924
+ },
1925
+ children: [
1926
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ImageIcon, { className: "h-4 w-4" }),
1927
+ "Imagen desde URL"
1928
+ ]
1929
+ }
1930
+ ),
1931
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1932
+ "button",
1933
+ {
1934
+ type: "button",
1935
+ className: itemClass,
1936
+ onClick: () => {
1937
+ var _a;
1938
+ uploadInsertPositionRef.current = position;
1939
+ (_a = imageInputRef.current) == null ? void 0 : _a.click();
1940
+ closeBlockMenu();
1941
+ },
1942
+ children: [
1943
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ImageIcon, { className: "h-4 w-4" }),
1944
+ "Subir imagen"
1945
+ ]
1946
+ }
1947
+ ),
1948
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1949
+ "button",
1950
+ {
1951
+ type: "button",
1952
+ className: itemClass,
1953
+ onClick: () => {
1954
+ insertVideoUrl(position);
1955
+ closeBlockMenu();
1956
+ },
1957
+ children: [
1958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.VideoIcon, { className: "h-4 w-4" }),
1959
+ "Video desde URL"
1960
+ ]
1961
+ }
1962
+ ),
1963
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1964
+ "button",
1965
+ {
1966
+ type: "button",
1967
+ className: itemClass,
1968
+ onClick: () => {
1969
+ var _a;
1970
+ uploadInsertPositionRef.current = position;
1971
+ (_a = videoInputRef.current) == null ? void 0 : _a.click();
1972
+ closeBlockMenu();
1973
+ },
1974
+ children: [
1975
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.VideoIcon, { className: "h-4 w-4" }),
1976
+ "Subir video"
1977
+ ]
1978
+ }
1979
+ ),
1980
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1981
+ "button",
1982
+ {
1983
+ type: "button",
1984
+ className: itemClass,
1985
+ onClick: () => {
1986
+ insertTable(position);
1987
+ closeBlockMenu();
1988
+ },
1989
+ children: [
1990
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TableIcon, { className: "h-4 w-4" }),
1991
+ "Tabla"
1992
+ ]
1993
+ }
1994
+ ),
1995
+ enableDiagrams ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1996
+ "button",
1997
+ {
1998
+ type: "button",
1999
+ className: itemClass,
2000
+ onClick: () => {
2001
+ insertUmlClassDiagram(position);
2002
+ closeBlockMenu();
2003
+ },
2004
+ children: [
2005
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CodeIcon, { className: "h-4 w-4" }),
2006
+ "Diagrama UML"
2007
+ ]
2008
+ }
2009
+ ) : null,
2010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2011
+ "button",
2012
+ {
2013
+ type: "button",
2014
+ className: itemClass,
2015
+ onClick: () => {
2016
+ actInsertBlockAt(position, "---");
2017
+ closeBlockMenu();
2018
+ },
2019
+ children: [
2020
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.MinusIcon, { className: "h-4 w-4" }),
2021
+ "Separador"
2022
+ ]
2023
+ }
2024
+ ),
2025
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2026
+ "button",
2027
+ {
2028
+ type: "button",
2029
+ className: "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-[var(--danger)] hover:bg-[color-mix(in_oklab,var(--danger)_10%,transparent)]",
2030
+ onClick: () => {
2031
+ actDeleteBlock(blockMenu.range);
2032
+ closeBlockMenu();
2033
+ },
2034
+ children: [
2035
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TrashIcon, { className: "h-4 w-4" }),
2036
+ "Eliminar bloque"
2037
+ ]
2038
+ }
2039
+ )
2040
+ ]
177
2041
  }
178
2042
  ),
179
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { size: "xs", variant: "outline", disabled, onClick: () => actInsert("\n---\n"), children: "Separador" }),
180
- (templates == null ? void 0 : templates.length) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ml-auto flex flex-wrap items-center gap-1", children: templates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Button.default, { size: "xs", variant: "ghost", disabled, onClick: () => insertTemplate(t), children: [
181
- "+ ",
182
- t.label
183
- ] }, t.label)) }) : null
184
- ] }),
185
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
186
- import_Textarea.default,
2043
+ portalTarget
2044
+ );
2045
+ };
2046
+ const insertImageUrl = import_react.default.useCallback((position) => {
2047
+ var _a;
2048
+ const url = safeWindowPrompt("URL de la imagen", "https://");
2049
+ if (!url) return;
2050
+ if (!safeMediaUrl(url, "image")) {
2051
+ alertMessage("La imagen debe usar una URL http(s) v\xE1lida o un archivo de imagen permitido.");
2052
+ return;
2053
+ }
2054
+ const alt = (_a = safeWindowPrompt("Texto alternativo de la imagen", "Imagen")) != null ? _a : "Imagen";
2055
+ const markdown = `![${alt}](${url})`;
2056
+ if (typeof position === "number") actInsertBlockAt(position, markdown);
2057
+ else actInsertBlock(markdown);
2058
+ }, [actInsertBlock, actInsertBlockAt, alertMessage]);
2059
+ const insertVideoUrl = import_react.default.useCallback((position) => {
2060
+ var _a;
2061
+ const url = safeWindowPrompt("URL del video", "https://");
2062
+ if (!url) return;
2063
+ if (!safeMediaUrl(url, "video")) {
2064
+ alertMessage("El video debe usar una URL http(s) v\xE1lida o un archivo de video permitido.");
2065
+ return;
2066
+ }
2067
+ const title = (_a = safeWindowPrompt("Titulo del video", "Video")) != null ? _a : "Video";
2068
+ const markdown = `@[video:${title}](${url})`;
2069
+ if (typeof position === "number") actInsertBlockAt(position, markdown);
2070
+ else actInsertBlock(markdown);
2071
+ }, [actInsertBlock, actInsertBlockAt, alertMessage]);
2072
+ const insertTable = import_react.default.useCallback((position) => {
2073
+ const colsValue = Number(safeWindowPrompt("Columnas de la tabla", "3"));
2074
+ if (!Number.isFinite(colsValue)) return;
2075
+ const rowsValue = Number(safeWindowPrompt("Filas de la tabla", "3"));
2076
+ if (!Number.isFinite(rowsValue)) return;
2077
+ const cols = Math.max(1, Math.min(8, Math.round(colsValue)));
2078
+ const rows2 = Math.max(1, Math.min(12, Math.round(rowsValue)));
2079
+ const header = `| ${Array.from({ length: cols }, (_, index) => `Columna ${index + 1}`).join(" | ")} |`;
2080
+ const separator = `| ${Array.from({ length: cols }, () => "---").join(" | ")} |`;
2081
+ const body = Array.from({ length: rows2 }, () => `| ${Array.from({ length: cols }, () => " ").join(" | ")} |`).join("\n");
2082
+ const markdown = `${header}
2083
+ ${separator}
2084
+ ${body}`;
2085
+ if (typeof position === "number") actInsertBlockAt(position, markdown);
2086
+ else actInsertBlock(markdown);
2087
+ }, [actInsertBlock, actInsertBlockAt]);
2088
+ const insertUmlClassDiagram = import_react.default.useCallback((position) => {
2089
+ const markdown = [
2090
+ "```mermaid",
2091
+ "classDiagram",
2092
+ " class Usuario {",
2093
+ " +string nombre",
2094
+ " +iniciarSesion()",
2095
+ " }",
2096
+ " class Cuenta {",
2097
+ " +string email",
2098
+ " }",
2099
+ " Usuario --> Cuenta",
2100
+ "```"
2101
+ ].join("\n");
2102
+ if (typeof position === "number") actInsertBlockAt(position, markdown);
2103
+ else actInsertBlock(markdown);
2104
+ }, [actInsertBlock, actInsertBlockAt]);
2105
+ const insertUmlSequenceDiagram = import_react.default.useCallback((position) => {
2106
+ const markdown = [
2107
+ "```mermaid",
2108
+ "sequenceDiagram",
2109
+ " participant Usuario",
2110
+ " participant Sistema",
2111
+ " Usuario->>Sistema: Solicita acceso",
2112
+ " Sistema-->>Usuario: Responde resultado",
2113
+ "```"
2114
+ ].join("\n");
2115
+ if (typeof position === "number") actInsertBlockAt(position, markdown);
2116
+ else actInsertBlock(markdown);
2117
+ }, [actInsertBlock, actInsertBlockAt]);
2118
+ const insertUploadedFile = import_react.default.useCallback(
2119
+ async (file, kind, position) => {
2120
+ if (!file) return;
2121
+ const isImage = kind === "image";
2122
+ const maxBytes = isImage ? maxImageBytes : maxVideoBytes;
2123
+ if (file.size > maxBytes) {
2124
+ const mb = Math.round(maxBytes / 1024 / 1024);
2125
+ alertMessage(`${isImage ? "La imagen" : "El video"} supera el limite de ${mb} MB.`);
2126
+ return;
2127
+ }
2128
+ if (isImage && !file.type.startsWith("image/")) {
2129
+ alertMessage("Selecciona un archivo de imagen v\xE1lido.");
2130
+ return;
2131
+ }
2132
+ if (!isImage && !file.type.startsWith("video/")) {
2133
+ alertMessage("Selecciona un archivo de video v\xE1lido.");
2134
+ return;
2135
+ }
2136
+ try {
2137
+ const dataUrl = await readFileAsDataUrl(file);
2138
+ const markdown = isImage ? `![${file.name || "Imagen"}](${dataUrl})` : `@[video:${file.name || "Video"}](${dataUrl})`;
2139
+ if (typeof position === "number") actInsertBlockAt(position, markdown);
2140
+ else actInsertBlock(markdown);
2141
+ } catch {
2142
+ alertMessage("No se pudo leer el archivo seleccionado.");
2143
+ }
2144
+ },
2145
+ [actInsertBlock, actInsertBlockAt, alertMessage, maxImageBytes, maxVideoBytes]
2146
+ );
2147
+ const Tool = ({
2148
+ content,
2149
+ children
2150
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Tooltip.default, { content: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-0.5", children: content }), placement: "top", delay: 120, className: "max-w-[240px]", children });
2151
+ const ToolSep = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mx-1 h-6 w-px bg-[var(--border)]" });
2152
+ const toggleView = import_react.default.useCallback(
2153
+ (view) => {
2154
+ if (!enablePreview && view !== "markdown") return;
2155
+ setBlockMenu(null);
2156
+ setSelectionMenu(null);
2157
+ setVisibleViews((current) => {
2158
+ const next = { ...current, [view]: !current[view] };
2159
+ if (!enablePreview) return { blocks: false, preview: false, markdown: true };
2160
+ if (!next.blocks && !next.preview && !next.markdown) return current;
2161
+ return next;
2162
+ });
2163
+ },
2164
+ [enablePreview]
2165
+ );
2166
+ const renderEditor = (isFullscreen) => {
2167
+ const effectiveHeight = isFullscreen ? fullscreenHeight : height;
2168
+ const previewStyle = effectiveHeight ? { height: effectiveHeight } : void 0;
2169
+ const viewCount = Number(Boolean(visibleViews.blocks && enablePreview)) + Number(Boolean(visibleViews.preview && enablePreview)) + Number(Boolean(visibleViews.markdown));
2170
+ const gridColumns = viewCount >= 3 ? "lg:grid-cols-2 2xl:grid-cols-3" : viewCount === 2 ? "lg:grid-cols-2" : "grid-cols-1";
2171
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2172
+ "div",
187
2173
  {
188
- ref,
189
- value,
190
- onChange: (e) => onChange(e.currentTarget.value),
191
- placeholder,
192
- rows,
193
- disabled,
194
- className: "resize-none rounded-none border-0 shadow-none focus:ring-0 focus:border-transparent",
195
- style: height ? { height } : void 0
196
- }
197
- )
2174
+ className: cx(
2175
+ "overflow-hidden rounded-2xl border border-[var(--border)] bg-[var(--card)]",
2176
+ isFullscreen && "flex h-full flex-col rounded-none border-0 bg-[var(--background)]",
2177
+ !isFullscreen && className
2178
+ ),
2179
+ children: [
2180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2181
+ "input",
2182
+ {
2183
+ ref: imageInputRef,
2184
+ type: "file",
2185
+ accept: "image/*",
2186
+ className: "hidden",
2187
+ onChange: (event) => {
2188
+ var _a, _b;
2189
+ const input = event.target;
2190
+ void insertUploadedFile((_b = (_a = input.files) == null ? void 0 : _a[0]) != null ? _b : null, "image", uploadInsertPositionRef.current);
2191
+ uploadInsertPositionRef.current = null;
2192
+ input.value = "";
2193
+ }
2194
+ }
2195
+ ),
2196
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2197
+ "input",
2198
+ {
2199
+ ref: videoInputRef,
2200
+ type: "file",
2201
+ accept: "video/mp4,video/webm,video/ogg,video/*",
2202
+ className: "hidden",
2203
+ onChange: (event) => {
2204
+ var _a, _b;
2205
+ const input = event.target;
2206
+ void insertUploadedFile((_b = (_a = input.files) == null ? void 0 : _a[0]) != null ? _b : null, "video", uploadInsertPositionRef.current);
2207
+ uploadInsertPositionRef.current = null;
2208
+ input.value = "";
2209
+ }
2210
+ }
2211
+ ),
2212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5 border-b border-[var(--border)] bg-[color-mix(in_oklab,var(--card)_92%,transparent)] p-2", children: [
2213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: [
2214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.undo, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { size: "sm", title: L.undo, disabled: disabled || undoStackRef.current.length === 0, onClick: doUndo, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ArrowArcLeftIcon, { className: "h-4 w-4" }) }) }),
2215
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.redo, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { size: "sm", title: L.redo, disabled: disabled || redoStackRef.current.length === 0, onClick: doRedo, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ArrowArcRightIcon, { className: "h-4 w-4" }) }) }),
2216
+ ToolSep,
2217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default, { children: [
2218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Trigger, { disabled, className: "rounded-full px-3 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Tooltip.default, { content: L.headings, placement: "top", delay: 120, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-2", children: [
2219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-semibold text-[var(--foreground)]", children: "H" }),
2220
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ChevronDownIcon, { className: "h-4 w-4 text-[var(--muted)]" })
2221
+ ] }) }) }),
2222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default.Content, { children: [
2223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Label, { children: L.headings }),
2224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default.Item, { onSelect: () => actPrefix(""), children: [
2225
+ "P - ",
2226
+ L.paragraph
2227
+ ] }),
2228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default.Item, { onSelect: () => actPrefix("# "), children: [
2229
+ "H1 - ",
2230
+ L.h1
2231
+ ] }),
2232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default.Item, { onSelect: () => actPrefix("## "), children: [
2233
+ "H2 - ",
2234
+ L.h2
2235
+ ] }),
2236
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default.Item, { onSelect: () => actPrefix("### "), children: [
2237
+ "H3 - ",
2238
+ L.h3
2239
+ ] }),
2240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.QuotesIcon, { className: "h-4 w-4" }), onSelect: () => actPrefix("> "), children: L.quote })
2241
+ ] })
2242
+ ] }),
2243
+ ToolSep,
2244
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.bold, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { size: "sm", title: L.bold, disabled, onClick: () => actWrap("**", "**", "texto"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TextBIcon, { className: "h-4 w-4" }) }) }),
2245
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.italic, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { size: "sm", title: L.italic, disabled, onClick: () => actWrap("*", "*", "texto"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TextItalicIcon, { className: "h-4 w-4" }) }) }),
2246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.code, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { size: "sm", title: L.code, disabled, onClick: () => actWrap("`", "`", "c\xF3digo"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CodeIcon, { className: "h-4 w-4" }) }) }),
2247
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.link, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2248
+ import_ActionIconButton.default,
2249
+ {
2250
+ size: "sm",
2251
+ title: L.link,
2252
+ disabled,
2253
+ onClick: actLink,
2254
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.LinkIcon, { className: "h-4 w-4" })
2255
+ }
2256
+ ) }),
2257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default, { children: [
2258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Trigger, { disabled, className: "rounded-full px-3 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Tooltip.default, { content: L.color, placement: "top", delay: 120, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-2", children: [
2259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.PaletteIcon, { className: "h-4 w-4 text-[var(--foreground)]" }),
2260
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ChevronDownIcon, { className: "h-4 w-4 text-[var(--muted)]" })
2261
+ ] }) }) }),
2262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default.Content, { children: [
2263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Label, { children: L.color }),
2264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-5 gap-2 p-2", children: COLOR_SWATCHES.map((color) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2265
+ "button",
2266
+ {
2267
+ type: "button",
2268
+ title: color,
2269
+ className: "h-8 w-8 rounded-full border border-[var(--border)] shadow-sm outline-none transition hover:scale-105 focus-visible:ring-2 focus-visible:ring-[var(--ring)]",
2270
+ style: { backgroundColor: color },
2271
+ onClick: () => actColor(color)
2272
+ },
2273
+ color
2274
+ )) })
2275
+ ] })
2276
+ ] }),
2277
+ ToolSep,
2278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.bullets, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { size: "sm", title: L.bullets, disabled, onClick: () => actPrefix("- "), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ListBulletsIcon, { className: "h-4 w-4" }) }) }),
2279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.numbers, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { size: "sm", title: L.numbers, disabled, onClick: () => actPrefix("1. "), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ListNumbersIcon, { className: "h-4 w-4" }) }) }),
2280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tool, { content: L.quote, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ActionIconButton.default, { size: "sm", title: L.quote, disabled, onClick: () => actPrefix("> "), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.QuotesIcon, { className: "h-4 w-4" }) }) }),
2281
+ ToolSep,
2282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default, { children: [
2283
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Trigger, { disabled, className: "rounded-full px-3 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Tooltip.default, { content: L.insert, placement: "top", delay: 120, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-2", children: [
2284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ImageIcon, { className: "h-4 w-4 text-[var(--foreground)]" }),
2285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ChevronDownIcon, { className: "h-4 w-4 text-[var(--muted)]" })
2286
+ ] }) }) }),
2287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dropdown.default.Content, { children: [
2288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Label, { children: L.insert }),
2289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ImageIcon, { className: "h-4 w-4" }), onSelect: () => insertImageUrl(), children: L.imageUrl }),
2290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2291
+ import_Dropdown.default.Item,
2292
+ {
2293
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.ImageIcon, { className: "h-4 w-4" }),
2294
+ onSelect: () => {
2295
+ var _a;
2296
+ uploadInsertPositionRef.current = null;
2297
+ (_a = imageInputRef.current) == null ? void 0 : _a.click();
2298
+ },
2299
+ children: L.imageUpload
2300
+ }
2301
+ ),
2302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.VideoIcon, { className: "h-4 w-4" }), onSelect: () => insertVideoUrl(), children: L.videoUrl }),
2303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2304
+ import_Dropdown.default.Item,
2305
+ {
2306
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.VideoIcon, { className: "h-4 w-4" }),
2307
+ onSelect: () => {
2308
+ var _a;
2309
+ uploadInsertPositionRef.current = null;
2310
+ (_a = videoInputRef.current) == null ? void 0 : _a.click();
2311
+ },
2312
+ children: L.videoUpload
2313
+ }
2314
+ ),
2315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TableIcon, { className: "h-4 w-4" }), onSelect: () => insertTable(), children: L.table }),
2316
+ enableDiagrams ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2317
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CodeIcon, { className: "h-4 w-4" }), onSelect: () => insertUmlClassDiagram(), children: L.umlClass }),
2318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CodeIcon, { className: "h-4 w-4" }), onSelect: () => insertUmlSequenceDiagram(), children: L.umlSequence })
2319
+ ] }) : null,
2320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Separator, {}),
2321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.MinusIcon, { className: "h-4 w-4 rotate-90" }), onSelect: actLineBreak, children: L.lineBreak }),
2322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.TextTIcon, { className: "h-4 w-4" }), onSelect: actNewParagraph, children: "Nuevo p\xE1rrafo" }),
2323
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dropdown.default.Item, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.MinusIcon, { className: "h-4 w-4" }), onSelect: () => actInsert("\n---\n"), children: L.divider })
2324
+ ] })
2325
+ ] })
2326
+ ] }),
2327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2329
+ import_Button.default,
2330
+ {
2331
+ size: "xs",
2332
+ variant: "secondary",
2333
+ disabled,
2334
+ leftIcon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.PlusIcon, { className: "h-3.5 w-3.5" }),
2335
+ onClick: insertExample,
2336
+ children: L.sampleButton
2337
+ }
2338
+ ),
2339
+ (templates == null ? void 0 : templates.length) ? templates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Button.default, { size: "xs", variant: "ghost", disabled, onClick: () => insertTemplate(t), children: [
2340
+ "+ ",
2341
+ t.label
2342
+ ] }, t.label)) : null
2343
+ ] }),
2344
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ml-auto flex items-center gap-1", children: [
2345
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex overflow-hidden rounded-full border border-[var(--border)] bg-[color-mix(in_oklab,var(--background)_70%,transparent)] p-0.5", children: [
2346
+ enablePreview ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2348
+ "button",
2349
+ {
2350
+ type: "button",
2351
+ className: cx(
2352
+ "rounded-full px-3 py-1.5 text-xs font-semibold transition",
2353
+ visibleViews.blocks ? "bg-[var(--primary)] text-[var(--primary-foreground)] shadow-sm" : "text-[var(--muted)] hover:bg-[color-mix(in_oklab,var(--muted)_10%,transparent)] hover:text-[var(--foreground)]"
2354
+ ),
2355
+ "aria-pressed": visibleViews.blocks,
2356
+ onClick: () => toggleView("blocks"),
2357
+ children: L.blocksView
2358
+ }
2359
+ ),
2360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2361
+ "button",
2362
+ {
2363
+ type: "button",
2364
+ className: cx(
2365
+ "rounded-full px-3 py-1.5 text-xs font-semibold transition",
2366
+ visibleViews.preview ? "bg-[var(--primary)] text-[var(--primary-foreground)] shadow-sm" : "text-[var(--muted)] hover:bg-[color-mix(in_oklab,var(--muted)_10%,transparent)] hover:text-[var(--foreground)]"
2367
+ ),
2368
+ "aria-pressed": visibleViews.preview,
2369
+ onClick: () => toggleView("preview"),
2370
+ children: L.previewView
2371
+ }
2372
+ )
2373
+ ] }) : null,
2374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2375
+ "button",
2376
+ {
2377
+ type: "button",
2378
+ className: cx(
2379
+ "rounded-full px-3 py-1.5 text-xs font-semibold transition",
2380
+ visibleViews.markdown ? "bg-[var(--primary)] text-[var(--primary-foreground)] shadow-sm" : "text-[var(--muted)] hover:bg-[color-mix(in_oklab,var(--muted)_10%,transparent)] hover:text-[var(--foreground)]"
2381
+ ),
2382
+ "aria-pressed": visibleViews.markdown,
2383
+ onClick: () => toggleView("markdown"),
2384
+ children: L.markdownView
2385
+ }
2386
+ )
2387
+ ] }),
2388
+ enableFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2389
+ import_ActionIconButton.default,
2390
+ {
2391
+ size: "sm",
2392
+ title: isFullscreen ? L.exitFullscreen : L.fullscreen,
2393
+ active: isFullscreen,
2394
+ onClick: () => setFullscreen((current) => !current),
2395
+ children: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CornersInIcon, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.CornersOutIcon, { className: "h-4 w-4" })
2396
+ }
2397
+ ) : null
2398
+ ] })
2399
+ ] }),
2400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2401
+ "div",
2402
+ {
2403
+ className: cx(
2404
+ "grid min-h-0 divide-y divide-[var(--border)] lg:divide-y-0 lg:divide-x",
2405
+ gridColumns,
2406
+ isFullscreen && "flex-1"
2407
+ ),
2408
+ children: [
2409
+ visibleViews.blocks && enablePreview ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2410
+ "section",
2411
+ {
2412
+ "aria-label": L.blocksView,
2413
+ className: cx(
2414
+ "min-h-[220px] min-w-0 overflow-auto bg-[color-mix(in_oklab,var(--card)_96%,var(--background))] p-4",
2415
+ isFullscreen && "h-full"
2416
+ ),
2417
+ style: previewStyle,
2418
+ children: value.trim() ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2419
+ MarkdownPreview,
2420
+ {
2421
+ value,
2422
+ className: "text-sm leading-6",
2423
+ emptyLabel: L.emptyPreview,
2424
+ selectable: true,
2425
+ showBlockControls: true,
2426
+ onSelectRange: selectMarkdownRange,
2427
+ onOpenSelectionMenu: openSelectionMenu,
2428
+ onOpenBlockMenu: openBlockMenu
2429
+ }
2430
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex h-full min-h-[180px] flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-[var(--border)] px-4 text-center text-sm text-[var(--muted)]", children: [
2431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: L.emptyPreview }),
2432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2433
+ import_Button.default,
2434
+ {
2435
+ size: "sm",
2436
+ variant: "secondary",
2437
+ leftIcon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_iconos.PlusIcon, { className: "h-4 w-4" }),
2438
+ disabled,
2439
+ onClick: (event) => openBlockMenu(event, { start: 0, end: 0 }),
2440
+ children: "Agregar bloque"
2441
+ }
2442
+ )
2443
+ ] })
2444
+ }
2445
+ ) : null,
2446
+ visibleViews.preview && enablePreview ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2447
+ "section",
2448
+ {
2449
+ "aria-label": L.preview,
2450
+ className: cx(
2451
+ "min-h-[220px] min-w-0 overflow-auto bg-[color-mix(in_oklab,var(--card)_96%,var(--background))] p-5",
2452
+ isFullscreen && "h-full"
2453
+ ),
2454
+ style: previewStyle,
2455
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownPreview, { value, className: "text-sm leading-6", emptyLabel: L.emptyPreview })
2456
+ }
2457
+ ) : null,
2458
+ visibleViews.markdown ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-h-0 min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2459
+ import_Textarea.default,
2460
+ {
2461
+ ref,
2462
+ value,
2463
+ onChange: (e) => {
2464
+ var _a, _b, _c;
2465
+ const target = e.target;
2466
+ selectionRef.current = { start: (_a = target.selectionStart) != null ? _a : 0, end: (_c = (_b = target.selectionEnd) != null ? _b : target.selectionStart) != null ? _c : 0 };
2467
+ handleTextChange(target.value);
2468
+ },
2469
+ onClick: rememberSelection,
2470
+ onFocus: rememberSelection,
2471
+ onKeyDown: handleMarkdownKeyDown,
2472
+ onKeyUp: rememberSelection,
2473
+ onMouseUp: rememberSelection,
2474
+ onSelect: rememberSelection,
2475
+ placeholder,
2476
+ "aria-label": L.editor,
2477
+ rows: isFullscreen ? Math.max(rows, 20) : rows,
2478
+ disabled,
2479
+ className: "resize-none rounded-none border-0 shadow-none focus:border-transparent focus:ring-0",
2480
+ style: effectiveHeight ? { height: effectiveHeight } : void 0
2481
+ }
2482
+ ) }) : null
2483
+ ]
2484
+ }
2485
+ )
2486
+ ]
2487
+ }
2488
+ );
2489
+ };
2490
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2491
+ !fullscreen ? renderEditor(false) : null,
2492
+ fullscreen && portalTarget ? (0, import_react_dom.createPortal)(
2493
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fixed inset-0 z-[2147483647] bg-[var(--background)] p-3 sm:p-5", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mx-auto h-full max-w-[1600px] overflow-hidden rounded-2xl border border-[var(--border)] bg-[var(--card)] shadow-2xl", children: renderEditor(true) }) }),
2494
+ portalTarget
2495
+ ) : null,
2496
+ renderSelectionMenu(),
2497
+ renderBlockMenu()
198
2498
  ] });
199
2499
  }
2500
+ // Annotate the CommonJS export names for ESM import in node:
2501
+ 0 && (module.exports = {
2502
+ MarkdownPreview,
2503
+ renderMarkdown
2504
+ });