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