@velo-sci/notebook-react 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3022 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AICellGenerate: () => AICellGenerate,
34
+ AIRewrite: () => AIRewrite,
35
+ Cell: () => Cell,
36
+ CellOutputDisplay: () => CellOutputDisplay,
37
+ ChatSidebar: () => ChatSidebar,
38
+ DEFAULT_COMMANDS: () => DEFAULT_COMMANDS,
39
+ EmbedCell: () => EmbedCell,
40
+ FindReplace: () => FindReplace,
41
+ FloatingToolbar: () => FloatingToolbar,
42
+ GhostText: () => GhostText,
43
+ ImageCell: () => ImageCell,
44
+ ImageResize: () => ImageResize,
45
+ InsertHandle: () => InsertHandle,
46
+ LATEX_COMMANDS: () => LATEX_COMMANDS,
47
+ LatexAutocomplete: () => LatexAutocomplete,
48
+ MathEditor: () => MathEditor,
49
+ MermaidPreview: () => MermaidPreview,
50
+ NotebookContext: () => NotebookContext,
51
+ SciNotebook: () => SciNotebook,
52
+ SlashCommand: () => SlashCommand,
53
+ TOCSidebar: () => TOCSidebar,
54
+ TableCell: () => TableCell,
55
+ VirtualRenderer: () => VirtualRenderer,
56
+ initMermaid: () => initMermaid,
57
+ renderEmbedPreview: () => renderEmbedPreview,
58
+ renderImagePreview: () => renderImagePreview,
59
+ renderTablePreview: () => renderTablePreview,
60
+ useCell: () => useCell,
61
+ useFocusedCell: () => useFocusedCell,
62
+ useNotebook: () => useNotebook,
63
+ useNotebookEvent: () => useNotebookEvent,
64
+ useSciNotebook: () => useSciNotebook
65
+ });
66
+ module.exports = __toCommonJS(index_exports);
67
+
68
+ // src/hooks.ts
69
+ var import_react = require("react");
70
+ var NotebookContext = (0, import_react.createContext)(null);
71
+ function useSciNotebook() {
72
+ const engine = (0, import_react.useContext)(NotebookContext);
73
+ if (!engine) {
74
+ throw new Error("useSciNotebook must be used within a NotebookProvider or SciNotebook component");
75
+ }
76
+ return engine;
77
+ }
78
+ function useNotebook() {
79
+ const engine = useSciNotebook();
80
+ const [notebook, setNotebook] = (0, import_react.useState)(engine.getNotebook());
81
+ (0, import_react.useEffect)(() => {
82
+ return engine.on("notebook:updated", (payload) => {
83
+ setNotebook({ ...payload.data.notebook });
84
+ });
85
+ }, [engine]);
86
+ return notebook;
87
+ }
88
+ function useCell(cellId) {
89
+ const engine = useSciNotebook();
90
+ const [, forceUpdate] = (0, import_react.useState)(0);
91
+ (0, import_react.useEffect)(() => {
92
+ const handler = (payload) => {
93
+ if (payload.data.cellId === cellId || payload.type === "notebook:updated") {
94
+ forceUpdate((v) => v + 1);
95
+ }
96
+ };
97
+ const unsubUpdated = engine.on("cell:updated", handler);
98
+ const unsubMode = engine.on("cell:mode-changed", handler);
99
+ const unsubNb = engine.on("notebook:updated", handler);
100
+ return () => {
101
+ unsubUpdated();
102
+ unsubMode();
103
+ unsubNb();
104
+ };
105
+ }, [engine, cellId]);
106
+ return engine.getCell(cellId);
107
+ }
108
+ function useFocusedCell() {
109
+ const engine = useSciNotebook();
110
+ const [focusedId, setFocusedId] = (0, import_react.useState)(null);
111
+ (0, import_react.useEffect)(() => {
112
+ return engine.on("cell:focused", (payload) => {
113
+ setFocusedId(payload.data.cellId);
114
+ });
115
+ }, [engine]);
116
+ return focusedId;
117
+ }
118
+ function useNotebookEvent(type, handler) {
119
+ const engine = useSciNotebook();
120
+ (0, import_react.useEffect)(() => {
121
+ return engine.on(type, handler);
122
+ }, [engine, type, handler]);
123
+ }
124
+
125
+ // src/components/SciNotebook.tsx
126
+ var import_react13 = __toESM(require("react"), 1);
127
+ var import_notebook_core = require("@velo-sci/notebook-core");
128
+ var import_notebook_renderer = require("@velo-sci/notebook-renderer");
129
+
130
+ // src/components/Cell.tsx
131
+ var import_react9 = require("react");
132
+
133
+ // src/components/FloatingToolbar.tsx
134
+ var import_react2 = require("react");
135
+ var import_jsx_runtime = require("react/jsx-runtime");
136
+ var FORMAT_ACTIONS = [
137
+ { label: "B", title: "Bold (Ctrl+B)", wrap: ["**", "**"], style: "font-weight:700" },
138
+ { label: "I", title: "Italic (Ctrl+I)", wrap: ["*", "*"], style: "font-style:italic" },
139
+ { label: "S", title: "Strikethrough", wrap: ["~~", "~~"], style: "text-decoration:line-through" },
140
+ { label: "<>", title: "Inline code", wrap: ["`", "`"], style: "font-family:monospace;font-size:12px" },
141
+ { label: "H1", title: "Heading 1", prefix: "# ", wrap: null, style: "font-weight:700;font-size:12px" },
142
+ { label: "H2", title: "Heading 2", prefix: "## ", wrap: null, style: "font-weight:700;font-size:11px" },
143
+ { label: "\u{1F517}", title: "Link", wrap: ["[", "](url)"], style: "" },
144
+ { label: "\u2022", title: "Bullet list", prefix: "- ", wrap: null, style: "font-size:16px" }
145
+ ];
146
+ var FloatingToolbar = ({ cellId, textareaRef }) => {
147
+ const engine = useSciNotebook();
148
+ const toolbarRef = (0, import_react2.useRef)(null);
149
+ const [pos, setPos] = (0, import_react2.useState)({ top: 0, left: 0, visible: false });
150
+ const updatePosition = (0, import_react2.useCallback)(() => {
151
+ const ta = textareaRef.current;
152
+ if (!ta) return;
153
+ const start = ta.selectionStart;
154
+ const end = ta.selectionEnd;
155
+ if (start === end) {
156
+ setPos((p) => ({ ...p, visible: false }));
157
+ return;
158
+ }
159
+ const taRect = ta.getBoundingClientRect();
160
+ setPos({
161
+ top: taRect.top - 44,
162
+ left: taRect.left + taRect.width / 2,
163
+ visible: true
164
+ });
165
+ }, [textareaRef]);
166
+ (0, import_react2.useEffect)(() => {
167
+ const ta = textareaRef.current;
168
+ if (!ta) return;
169
+ const onSelect = () => {
170
+ requestAnimationFrame(updatePosition);
171
+ };
172
+ ta.addEventListener("select", onSelect);
173
+ ta.addEventListener("mouseup", onSelect);
174
+ ta.addEventListener("keyup", onSelect);
175
+ document.addEventListener("mousedown", (e) => {
176
+ if (toolbarRef.current && !toolbarRef.current.contains(e.target) && e.target !== ta) {
177
+ setPos((p) => ({ ...p, visible: false }));
178
+ }
179
+ });
180
+ return () => {
181
+ ta.removeEventListener("select", onSelect);
182
+ ta.removeEventListener("mouseup", onSelect);
183
+ ta.removeEventListener("keyup", onSelect);
184
+ };
185
+ }, [textareaRef, updatePosition]);
186
+ const applyFormat = (0, import_react2.useCallback)((action) => {
187
+ const ta = textareaRef.current;
188
+ if (!ta) return;
189
+ const start = ta.selectionStart;
190
+ const end = ta.selectionEnd;
191
+ const source = ta.value;
192
+ const selected = source.slice(start, end);
193
+ let newSource;
194
+ let newCursorStart;
195
+ let newCursorEnd;
196
+ if (action.wrap) {
197
+ const [before, after] = action.wrap;
198
+ newSource = source.slice(0, start) + before + selected + after + source.slice(end);
199
+ newCursorStart = start + before.length;
200
+ newCursorEnd = end + before.length;
201
+ } else if ("prefix" in action && action.prefix) {
202
+ const lineStart = source.lastIndexOf("\n", start - 1) + 1;
203
+ const lineEnd = source.indexOf("\n", end);
204
+ const actualEnd = lineEnd === -1 ? source.length : lineEnd;
205
+ const lines = source.slice(lineStart, actualEnd).split("\n");
206
+ const prefixed = lines.map((l) => action.prefix + l).join("\n");
207
+ newSource = source.slice(0, lineStart) + prefixed + source.slice(actualEnd);
208
+ newCursorStart = start + action.prefix.length;
209
+ newCursorEnd = end + action.prefix.length * lines.length;
210
+ } else {
211
+ return;
212
+ }
213
+ engine.updateCellSource(cellId, newSource);
214
+ requestAnimationFrame(() => {
215
+ if (ta) {
216
+ ta.focus();
217
+ ta.setSelectionRange(newCursorStart, newCursorEnd);
218
+ }
219
+ });
220
+ }, [engine, cellId, textareaRef]);
221
+ if (!pos.visible) return null;
222
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
223
+ "div",
224
+ {
225
+ ref: toolbarRef,
226
+ className: "sci-nb-floating-toolbar",
227
+ style: {
228
+ position: "fixed",
229
+ top: `${pos.top}px`,
230
+ left: `${pos.left}px`,
231
+ transform: "translateX(-50%)"
232
+ },
233
+ onMouseDown: (e) => e.preventDefault(),
234
+ children: FORMAT_ACTIONS.map((action, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
235
+ "button",
236
+ {
237
+ className: "sci-nb-ft-btn",
238
+ title: action.title,
239
+ style: action.style ? { ...parseInlineStyle(action.style) } : void 0,
240
+ onClick: () => applyFormat(action),
241
+ children: action.label
242
+ },
243
+ i
244
+ ))
245
+ }
246
+ );
247
+ };
248
+ function parseInlineStyle(css) {
249
+ const style = {};
250
+ css.split(";").forEach((pair) => {
251
+ const [key, val] = pair.split(":");
252
+ if (key && val) {
253
+ const camelKey = key.trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase());
254
+ style[camelKey] = val.trim();
255
+ }
256
+ });
257
+ return style;
258
+ }
259
+
260
+ // src/components/MathEditor.tsx
261
+ var import_react3 = require("react");
262
+ var import_jsx_runtime2 = require("react/jsx-runtime");
263
+ var MATH_CATEGORIES = [
264
+ {
265
+ name: "Estructuras",
266
+ icon: "\u2B1A",
267
+ blocks: [
268
+ { label: "a/b", latex: "\\frac{\u25A2}{\u25A2}", cursor: 6 },
269
+ { label: "\u221A", latex: "\\sqrt{\u25A2}", cursor: 6 },
270
+ { label: "\u207F\u221A", latex: "\\sqrt[\u25A2]{\u25A2}", cursor: 6 },
271
+ { label: "x\u207F", latex: "{\u25A2}^{\u25A2}", cursor: 1 },
272
+ { label: "x\u2099", latex: "{\u25A2}_{\u25A2}", cursor: 1 },
273
+ { label: "x\u0302", latex: "\\hat{\u25A2}", cursor: 5 },
274
+ { label: "x\u0304", latex: "\\bar{\u25A2}", cursor: 5 },
275
+ { label: "x\u20D7", latex: "\\vec{\u25A2}", cursor: 5 },
276
+ { label: "x\u0303", latex: "\\tilde{\u25A2}", cursor: 7 },
277
+ { label: "\u1E8B", latex: "\\dot{\u25A2}", cursor: 5 },
278
+ { label: "\u1E8D", latex: "\\ddot{\u25A2}", cursor: 6 }
279
+ ]
280
+ },
281
+ {
282
+ name: "Integrales",
283
+ icon: "\u222B",
284
+ blocks: [
285
+ { label: "\u222B", latex: "\\int{\u25A2}\\,d{\u25A2}", cursor: 5 },
286
+ { label: "\u222B\u2090\u1D47", latex: "\\int_{\u25A2}^{\u25A2}{\u25A2}\\,d{\u25A2}", cursor: 5 },
287
+ { label: "\u222C", latex: "\\iint{\u25A2}", cursor: 6 },
288
+ { label: "\u222D", latex: "\\iiint{\u25A2}", cursor: 7 },
289
+ { label: "\u222E", latex: "\\oint{\u25A2}", cursor: 6 }
290
+ ]
291
+ },
292
+ {
293
+ name: "Sumatorias",
294
+ icon: "\u2211",
295
+ blocks: [
296
+ { label: "\u2211", latex: "\\sum_{\u25A2}^{\u25A2}{\u25A2}", cursor: 5 },
297
+ { label: "\u220F", latex: "\\prod_{\u25A2}^{\u25A2}{\u25A2}", cursor: 6 },
298
+ { label: "lim", latex: "\\lim_{\u25A2 \\to \u25A2}{\u25A2}", cursor: 5 },
299
+ { label: "\u2211\u2099", latex: "\\sum_{n=\u25A2}^{\u25A2}{\u25A2}", cursor: 7 }
300
+ ]
301
+ },
302
+ {
303
+ name: "Matrices",
304
+ icon: "[ ]",
305
+ blocks: [
306
+ { label: "2\xD72", latex: "\\begin{pmatrix} \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 \\end{pmatrix}", cursor: 16 },
307
+ { label: "3\xD73", latex: "\\begin{pmatrix} \u25A2 & \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 & \u25A2 \\end{pmatrix}", cursor: 16 },
308
+ { label: "[2\xD72]", latex: "\\begin{bmatrix} \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 \\end{bmatrix}", cursor: 16 },
309
+ { label: "|2\xD72|", latex: "\\begin{vmatrix} \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 \\end{vmatrix}", cursor: 16 },
310
+ { label: "cases", latex: "\\begin{cases} \u25A2 & \\text{si } \u25A2 \\\\ \u25A2 & \\text{si } \u25A2 \\end{cases}", cursor: 14 }
311
+ ]
312
+ },
313
+ {
314
+ name: "Griegos",
315
+ icon: "\u03B1",
316
+ blocks: [
317
+ { label: "\u03B1", latex: "\\alpha" },
318
+ { label: "\u03B2", latex: "\\beta" },
319
+ { label: "\u03B3", latex: "\\gamma" },
320
+ { label: "\u03B4", latex: "\\delta" },
321
+ { label: "\u03B5", latex: "\\epsilon" },
322
+ { label: "\u03B6", latex: "\\zeta" },
323
+ { label: "\u03B7", latex: "\\eta" },
324
+ { label: "\u03B8", latex: "\\theta" },
325
+ { label: "\u03BB", latex: "\\lambda" },
326
+ { label: "\u03BC", latex: "\\mu" },
327
+ { label: "\u03C0", latex: "\\pi" },
328
+ { label: "\u03C1", latex: "\\rho" },
329
+ { label: "\u03C3", latex: "\\sigma" },
330
+ { label: "\u03C4", latex: "\\tau" },
331
+ { label: "\u03C6", latex: "\\phi" },
332
+ { label: "\u03C8", latex: "\\psi" },
333
+ { label: "\u03C9", latex: "\\omega" },
334
+ { label: "\u0393", latex: "\\Gamma" },
335
+ { label: "\u0394", latex: "\\Delta" },
336
+ { label: "\u0398", latex: "\\Theta" },
337
+ { label: "\u039B", latex: "\\Lambda" },
338
+ { label: "\u03A3", latex: "\\Sigma" },
339
+ { label: "\u03A6", latex: "\\Phi" },
340
+ { label: "\u03A8", latex: "\\Psi" },
341
+ { label: "\u03A9", latex: "\\Omega" }
342
+ ]
343
+ },
344
+ {
345
+ name: "Operadores",
346
+ icon: "\xB1",
347
+ blocks: [
348
+ { label: "\xB1", latex: "\\pm" },
349
+ { label: "\u2213", latex: "\\mp" },
350
+ { label: "\xD7", latex: "\\times" },
351
+ { label: "\xF7", latex: "\\div" },
352
+ { label: "\xB7", latex: "\\cdot" },
353
+ { label: "\u2218", latex: "\\circ" },
354
+ { label: "\u2297", latex: "\\otimes" },
355
+ { label: "\u2295", latex: "\\oplus" },
356
+ { label: "\u2202", latex: "\\partial" },
357
+ { label: "\u2207", latex: "\\nabla" },
358
+ { label: "\u221E", latex: "\\infty" },
359
+ { label: "\u2248", latex: "\\approx" },
360
+ { label: "\u2260", latex: "\\neq" },
361
+ { label: "\u2264", latex: "\\leq" },
362
+ { label: "\u2265", latex: "\\geq" },
363
+ { label: "\u2261", latex: "\\equiv" },
364
+ { label: "\u221D", latex: "\\propto" },
365
+ { label: "\u2208", latex: "\\in" },
366
+ { label: "\u2209", latex: "\\notin" },
367
+ { label: "\u2282", latex: "\\subset" },
368
+ { label: "\u2283", latex: "\\supset" },
369
+ { label: "\u222A", latex: "\\cup" },
370
+ { label: "\u2229", latex: "\\cap" },
371
+ { label: "\u2205", latex: "\\emptyset" },
372
+ { label: "\u2200", latex: "\\forall" },
373
+ { label: "\u2203", latex: "\\exists" }
374
+ ]
375
+ },
376
+ {
377
+ name: "Flechas",
378
+ icon: "\u2192",
379
+ blocks: [
380
+ { label: "\u2192", latex: "\\rightarrow" },
381
+ { label: "\u2190", latex: "\\leftarrow" },
382
+ { label: "\u2194", latex: "\\leftrightarrow" },
383
+ { label: "\u21D2", latex: "\\Rightarrow" },
384
+ { label: "\u21D0", latex: "\\Leftarrow" },
385
+ { label: "\u21D4", latex: "\\Leftrightarrow" },
386
+ { label: "\u21A6", latex: "\\mapsto" },
387
+ { label: "\u2191", latex: "\\uparrow" },
388
+ { label: "\u2193", latex: "\\downarrow" }
389
+ ]
390
+ },
391
+ {
392
+ name: "Funciones",
393
+ icon: "f(x)",
394
+ blocks: [
395
+ { label: "sin", latex: "\\sin{\u25A2}", cursor: 5 },
396
+ { label: "cos", latex: "\\cos{\u25A2}", cursor: 5 },
397
+ { label: "tan", latex: "\\tan{\u25A2}", cursor: 5 },
398
+ { label: "log", latex: "\\log{\u25A2}", cursor: 5 },
399
+ { label: "ln", latex: "\\ln{\u25A2}", cursor: 4 },
400
+ { label: "exp", latex: "\\exp{\u25A2}", cursor: 5 },
401
+ { label: "lim", latex: "\\lim_{\u25A2}{\u25A2}", cursor: 5 },
402
+ { label: "max", latex: "\\max{\u25A2}", cursor: 5 },
403
+ { label: "min", latex: "\\min{\u25A2}", cursor: 5 },
404
+ { label: "det", latex: "\\det{\u25A2}", cursor: 5 }
405
+ ]
406
+ },
407
+ {
408
+ name: "Delimitadores",
409
+ icon: "()",
410
+ blocks: [
411
+ { label: "(\u2026)", latex: "\\left( \u25A2 \\right)", cursor: 7 },
412
+ { label: "[\u2026]", latex: "\\left[ \u25A2 \\right]", cursor: 7 },
413
+ { label: "{\u2026}", latex: "\\left\\{ \u25A2 \\right\\}", cursor: 8 },
414
+ { label: "|\u2026|", latex: "\\left| \u25A2 \\right|", cursor: 7 },
415
+ { label: "\u2016\u2026\u2016", latex: "\\left\\| \u25A2 \\right\\|", cursor: 8 },
416
+ { label: "\u230A\u2026\u230B", latex: "\\lfloor \u25A2 \\rfloor", cursor: 8 },
417
+ { label: "\u2308\u2026\u2309", latex: "\\lceil \u25A2 \\rceil", cursor: 7 }
418
+ ]
419
+ }
420
+ ];
421
+ function renderLatexPreview(latex) {
422
+ const clean = latex.replace(/^\$\$\s*/, "").replace(/\s*\$\$$/, "").trim();
423
+ if (!clean) return '<span class="sci-nb-math-preview-empty">Formula vacia</span>';
424
+ if (typeof globalThis !== "undefined" && globalThis.katex) {
425
+ try {
426
+ return globalThis.katex.renderToString(clean, {
427
+ displayMode: true,
428
+ throwOnError: false
429
+ });
430
+ } catch {
431
+ }
432
+ }
433
+ return `<code class="sci-nb-math-preview-code">${escapeHtml(clean)}</code>`;
434
+ }
435
+ function escapeHtml(s) {
436
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
437
+ }
438
+ var MathEditor = ({ cellId, source, onExit }) => {
439
+ const engine = useSciNotebook();
440
+ const textareaRef = (0, import_react3.useRef)(null);
441
+ const [activeCategory, setActiveCategory] = (0, import_react3.useState)(0);
442
+ const [showRaw, setShowRaw] = (0, import_react3.useState)(false);
443
+ const innerLatex = source.replace(/^\$\$\s*/, "").replace(/\s*\$\$$/, "").trim();
444
+ const updateSource = (0, import_react3.useCallback)((newInner) => {
445
+ engine.updateCellSource(cellId, `$$
446
+ ${newInner}
447
+ $$`);
448
+ }, [engine, cellId]);
449
+ const handleRawChange = (0, import_react3.useCallback)((e) => {
450
+ updateSource(e.target.value);
451
+ }, [updateSource]);
452
+ const insertBlock = (0, import_react3.useCallback)((block) => {
453
+ const ta = textareaRef.current;
454
+ if (!ta) {
455
+ updateSource(innerLatex + (innerLatex ? " " : "") + block.latex.replace(/▢/g, ""));
456
+ return;
457
+ }
458
+ const start = ta.selectionStart;
459
+ const end = ta.selectionEnd;
460
+ const val = ta.value;
461
+ const selected = val.slice(start, end);
462
+ let inserted = block.latex;
463
+ if (selected) {
464
+ inserted = inserted.replace("\u25A2", selected);
465
+ }
466
+ inserted = inserted.replace(/▢/g, "");
467
+ const newVal = val.slice(0, start) + inserted + val.slice(end);
468
+ updateSource(newVal);
469
+ requestAnimationFrame(() => {
470
+ if (textareaRef.current) {
471
+ const cursorPos = block.cursor != null ? start + block.cursor : start + inserted.length;
472
+ textareaRef.current.focus();
473
+ textareaRef.current.setSelectionRange(cursorPos, cursorPos);
474
+ }
475
+ });
476
+ }, [innerLatex, updateSource]);
477
+ const handleKeyDown = (0, import_react3.useCallback)((e) => {
478
+ if (e.key === "Escape") {
479
+ e.preventDefault();
480
+ onExit();
481
+ } else if (e.key === "Enter" && e.shiftKey) {
482
+ e.preventDefault();
483
+ onExit();
484
+ }
485
+ }, [onExit]);
486
+ (0, import_react3.useEffect)(() => {
487
+ if (showRaw && textareaRef.current) {
488
+ const ta = textareaRef.current;
489
+ ta.style.height = "auto";
490
+ ta.style.height = `${Math.max(60, ta.scrollHeight)}px`;
491
+ }
492
+ }, [innerLatex, showRaw]);
493
+ const category = MATH_CATEGORIES[activeCategory];
494
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "sci-nb-math-editor", onKeyDown: handleKeyDown, children: [
495
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sci-nb-math-tabs", children: MATH_CATEGORIES.map((cat, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
496
+ "button",
497
+ {
498
+ className: `sci-nb-math-tab ${i === activeCategory ? "sci-nb-math-tab--active" : ""}`,
499
+ onClick: () => setActiveCategory(i),
500
+ title: cat.name,
501
+ children: [
502
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "sci-nb-math-tab-icon", children: cat.icon }),
503
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "sci-nb-math-tab-label", children: cat.name })
504
+ ]
505
+ },
506
+ cat.name
507
+ )) }),
508
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sci-nb-math-palette", children: category.blocks.map((block, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
509
+ "button",
510
+ {
511
+ className: "sci-nb-math-block",
512
+ onClick: () => insertBlock(block),
513
+ title: block.latex,
514
+ children: block.label
515
+ },
516
+ i
517
+ )) }),
518
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "sci-nb-math-editor-area", children: [
519
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "sci-nb-math-mode-toggle", children: [
520
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
521
+ "button",
522
+ {
523
+ className: `sci-nb-math-mode-btn ${!showRaw ? "sci-nb-math-mode-btn--active" : ""}`,
524
+ onClick: () => setShowRaw(false),
525
+ children: "Preview"
526
+ }
527
+ ),
528
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
529
+ "button",
530
+ {
531
+ className: `sci-nb-math-mode-btn ${showRaw ? "sci-nb-math-mode-btn--active" : ""}`,
532
+ onClick: () => setShowRaw(true),
533
+ children: "LaTeX"
534
+ }
535
+ )
536
+ ] }),
537
+ showRaw ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
538
+ "textarea",
539
+ {
540
+ ref: textareaRef,
541
+ className: "sci-nb-math-raw",
542
+ value: innerLatex,
543
+ onChange: handleRawChange,
544
+ placeholder: "Escribe LaTeX aqui...",
545
+ spellCheck: false,
546
+ autoFocus: true
547
+ }
548
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "sci-nb-math-visual", children: [
549
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
550
+ "div",
551
+ {
552
+ className: "sci-nb-math-preview",
553
+ dangerouslySetInnerHTML: { __html: renderLatexPreview(source) }
554
+ }
555
+ ),
556
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "sci-nb-math-visual-hint", children: [
557
+ "Haz click en los bloques de arriba para construir tu formula. Cambia a modo ",
558
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "LaTeX" }),
559
+ " para editar directamente."
560
+ ] })
561
+ ] })
562
+ ] }),
563
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "sci-nb-cell-hint", children: [
564
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("kbd", { children: "Esc" }),
565
+ " salir \xB7 ",
566
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("kbd", { children: "Shift+Enter" }),
567
+ " siguiente"
568
+ ] })
569
+ ] });
570
+ };
571
+
572
+ // src/components/ImageCell.tsx
573
+ var import_react4 = require("react");
574
+ var import_jsx_runtime3 = require("react/jsx-runtime");
575
+ function parseImageSource(source, metadata) {
576
+ return {
577
+ src: source || "",
578
+ alt: metadata.alt || "",
579
+ caption: metadata.caption || "",
580
+ width: metadata.width || "100%",
581
+ align: metadata.align || "center"
582
+ };
583
+ }
584
+ var ImageCell = ({ cellId, source, metadata, onExit }) => {
585
+ const engine = useSciNotebook();
586
+ const fileInputRef = (0, import_react4.useRef)(null);
587
+ const [data, setData] = (0, import_react4.useState)(() => parseImageSource(source, metadata));
588
+ const [dragOver, setDragOver] = (0, import_react4.useState)(false);
589
+ const save = (0, import_react4.useCallback)((updates) => {
590
+ const next = { ...data, ...updates };
591
+ setData(next);
592
+ engine.updateCellSource(cellId, next.src);
593
+ engine.updateCellMetadata(cellId, {
594
+ alt: next.alt,
595
+ caption: next.caption,
596
+ width: next.width,
597
+ align: next.align
598
+ });
599
+ }, [engine, cellId, data]);
600
+ const handleFileSelect = (0, import_react4.useCallback)((file) => {
601
+ if (!file.type.startsWith("image/")) return;
602
+ const reader = new FileReader();
603
+ reader.onload = (e) => {
604
+ const dataUrl = e.target?.result;
605
+ save({ src: dataUrl });
606
+ };
607
+ reader.readAsDataURL(file);
608
+ }, [save]);
609
+ const handleDrop = (0, import_react4.useCallback)((e) => {
610
+ e.preventDefault();
611
+ setDragOver(false);
612
+ const file = e.dataTransfer.files[0];
613
+ if (file) handleFileSelect(file);
614
+ }, [handleFileSelect]);
615
+ const handleDragOver = (0, import_react4.useCallback)((e) => {
616
+ e.preventDefault();
617
+ setDragOver(true);
618
+ }, []);
619
+ const handleKeyDown = (0, import_react4.useCallback)((e) => {
620
+ if (e.key === "Escape") {
621
+ e.preventDefault();
622
+ onExit();
623
+ }
624
+ }, [onExit]);
625
+ (0, import_react4.useEffect)(() => {
626
+ const handlePaste = (e) => {
627
+ const items = e.clipboardData?.items;
628
+ if (!items) return;
629
+ for (const item of Array.from(items)) {
630
+ if (item.type.startsWith("image/")) {
631
+ e.preventDefault();
632
+ const file = item.getAsFile();
633
+ if (file) handleFileSelect(file);
634
+ return;
635
+ }
636
+ }
637
+ };
638
+ document.addEventListener("paste", handlePaste);
639
+ return () => document.removeEventListener("paste", handlePaste);
640
+ }, [handleFileSelect]);
641
+ const hasSrc = !!data.src.trim();
642
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-editor", onKeyDown: handleKeyDown, children: [
643
+ hasSrc ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-preview", style: { textAlign: data.align }, children: [
644
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
645
+ "img",
646
+ {
647
+ src: data.src,
648
+ alt: data.alt,
649
+ style: { maxWidth: data.width, width: "auto", maxHeight: "400px" }
650
+ }
651
+ ),
652
+ data.caption && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "sci-nb-image-caption", children: data.caption })
653
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
654
+ "div",
655
+ {
656
+ className: `sci-nb-image-dropzone ${dragOver ? "sci-nb-image-dropzone--active" : ""}`,
657
+ onDrop: handleDrop,
658
+ onDragOver: handleDragOver,
659
+ onDragLeave: () => setDragOver(false),
660
+ onClick: () => fileInputRef.current?.click(),
661
+ children: [
662
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "32", height: "32", viewBox: "0 0 32 32", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
663
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "4", y: "4", width: "24", height: "24", rx: "3" }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "2.5" }),
665
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 22l6-6 4 4 4-4 10 10", strokeLinejoin: "round" })
666
+ ] }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "Arrastra, pega (Ctrl+V) o haz click para seleccionar" }),
668
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
669
+ "input",
670
+ {
671
+ ref: fileInputRef,
672
+ type: "file",
673
+ accept: "image/*",
674
+ style: { display: "none" },
675
+ onChange: (e) => {
676
+ const file = e.target.files?.[0];
677
+ if (file) handleFileSelect(file);
678
+ }
679
+ }
680
+ )
681
+ ]
682
+ }
683
+ ),
684
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-controls", children: [
685
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-field", children: [
686
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "URL" }),
687
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
688
+ "input",
689
+ {
690
+ type: "text",
691
+ value: data.src.startsWith("data:") ? "(archivo local)" : data.src,
692
+ onChange: (e) => save({ src: e.target.value }),
693
+ placeholder: "https://example.com/image.png",
694
+ disabled: data.src.startsWith("data:")
695
+ }
696
+ )
697
+ ] }),
698
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-field", children: [
699
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "Alt text" }),
700
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
701
+ "input",
702
+ {
703
+ type: "text",
704
+ value: data.alt,
705
+ onChange: (e) => save({ alt: e.target.value }),
706
+ placeholder: "Descripcion de la imagen"
707
+ }
708
+ )
709
+ ] }),
710
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-field", children: [
711
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "Caption" }),
712
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
713
+ "input",
714
+ {
715
+ type: "text",
716
+ value: data.caption,
717
+ onChange: (e) => save({ caption: e.target.value }),
718
+ placeholder: "Pie de imagen (opcional)"
719
+ }
720
+ )
721
+ ] }),
722
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-row", children: [
723
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-field sci-nb-image-field--small", children: [
724
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "Ancho" }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { value: data.width, onChange: (e) => save({ width: e.target.value }), children: [
726
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "25%", children: "25%" }),
727
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "50%", children: "50%" }),
728
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "75%", children: "75%" }),
729
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "100%", children: "100%" }),
730
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "auto", children: "Auto" })
731
+ ] })
732
+ ] }),
733
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-image-field sci-nb-image-field--small", children: [
734
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "Alinear" }),
735
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { value: data.align, onChange: (e) => save({ align: e.target.value }), children: [
736
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "left", children: "Izquierda" }),
737
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "center", children: "Centro" }),
738
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "right", children: "Derecha" })
739
+ ] })
740
+ ] }),
741
+ hasSrc && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "sci-nb-image-clear", onClick: () => save({ src: "" }), children: "Quitar imagen" })
742
+ ] })
743
+ ] }),
744
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "sci-nb-cell-hint", children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("kbd", { children: "Esc" }),
746
+ " salir"
747
+ ] })
748
+ ] });
749
+ };
750
+ function renderImagePreview(source, metadata) {
751
+ const data = parseImageSource(source, metadata);
752
+ if (!data.src) {
753
+ return '<div class="sci-nb-image-empty"><span class="sci-nb-placeholder">Click para agregar imagen</span></div>';
754
+ }
755
+ const alignStyle = `text-align:${data.align}`;
756
+ const widthStyle = `max-width:${data.width};width:auto;max-height:400px`;
757
+ let html = `<div class="sci-nb-image-view" style="${alignStyle}">`;
758
+ html += `<img src="${escapeAttr(data.src)}" alt="${escapeAttr(data.alt)}" style="${widthStyle}" />`;
759
+ if (data.caption) {
760
+ html += `<p class="sci-nb-image-caption">${escapeHtml2(data.caption)}</p>`;
761
+ }
762
+ html += `</div>`;
763
+ return html;
764
+ }
765
+ function escapeHtml2(s) {
766
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
767
+ }
768
+ function escapeAttr(s) {
769
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
770
+ }
771
+
772
+ // src/components/EmbedCell.tsx
773
+ var import_react5 = require("react");
774
+ var import_jsx_runtime4 = require("react/jsx-runtime");
775
+ var EMBED_PRESETS = [
776
+ { label: "YouTube", pattern: "https://www.youtube.com/embed/", icon: "\u25B6" },
777
+ { label: "CodePen", pattern: "https://codepen.io/", icon: "\u2328" },
778
+ { label: "Observable", pattern: "https://observablehq.com/embed/", icon: "\u25C9" },
779
+ { label: "Desmos", pattern: "https://www.desmos.com/calculator/", icon: "\u{1F4C8}" },
780
+ { label: "GeoGebra", pattern: "https://www.geogebra.org/material/iframe/id/", icon: "\u{1F4D0}" },
781
+ { label: "Custom URL", pattern: "", icon: "\u{1F517}" }
782
+ ];
783
+ function parseEmbedSource(source, metadata) {
784
+ return {
785
+ url: source || "",
786
+ height: metadata.height || "400px",
787
+ sandbox: metadata.sandbox || "allow-scripts allow-same-origin allow-popups",
788
+ title: metadata.title || ""
789
+ };
790
+ }
791
+ var EmbedCell = ({ cellId, source, metadata, onExit }) => {
792
+ const engine = useSciNotebook();
793
+ const [data, setData] = (0, import_react5.useState)(() => parseEmbedSource(source, metadata));
794
+ const [showPreview, setShowPreview] = (0, import_react5.useState)(!!data.url);
795
+ const save = (0, import_react5.useCallback)((updates) => {
796
+ const next = { ...data, ...updates };
797
+ setData(next);
798
+ engine.updateCellSource(cellId, next.url);
799
+ engine.updateCellMetadata(cellId, {
800
+ height: next.height,
801
+ sandbox: next.sandbox,
802
+ title: next.title
803
+ });
804
+ }, [engine, cellId, data]);
805
+ const handleKeyDown = (0, import_react5.useCallback)((e) => {
806
+ if (e.key === "Escape") {
807
+ e.preventDefault();
808
+ onExit();
809
+ }
810
+ }, [onExit]);
811
+ const hasUrl = !!data.url.trim();
812
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sci-nb-embed-editor", onKeyDown: handleKeyDown, children: [
813
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "sci-nb-embed-presets", children: EMBED_PRESETS.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
814
+ "button",
815
+ {
816
+ className: "sci-nb-embed-preset",
817
+ onClick: () => {
818
+ if (preset.pattern) {
819
+ save({ url: preset.pattern });
820
+ }
821
+ },
822
+ title: preset.label,
823
+ children: [
824
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: preset.icon }),
825
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: preset.label })
826
+ ]
827
+ },
828
+ preset.label
829
+ )) }),
830
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sci-nb-embed-url-row", children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
832
+ "input",
833
+ {
834
+ type: "text",
835
+ className: "sci-nb-embed-url",
836
+ value: data.url,
837
+ onChange: (e) => save({ url: e.target.value }),
838
+ placeholder: "https://www.youtube.com/embed/dQw4w9WgXcQ",
839
+ autoFocus: true
840
+ }
841
+ ),
842
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
843
+ "button",
844
+ {
845
+ className: `sci-nb-embed-preview-btn ${showPreview ? "sci-nb-embed-preview-btn--active" : ""}`,
846
+ onClick: () => setShowPreview(!showPreview),
847
+ disabled: !hasUrl,
848
+ children: showPreview ? "Ocultar" : "Preview"
849
+ }
850
+ )
851
+ ] }),
852
+ showPreview && hasUrl && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "sci-nb-embed-frame-wrap", style: { height: data.height }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
853
+ "iframe",
854
+ {
855
+ src: data.url,
856
+ title: data.title || "Embedded content",
857
+ sandbox: data.sandbox,
858
+ style: { width: "100%", height: "100%", border: "none", borderRadius: "6px" },
859
+ loading: "lazy",
860
+ allowFullScreen: true
861
+ }
862
+ ) }),
863
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sci-nb-embed-settings", children: [
864
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sci-nb-embed-field", children: [
865
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Titulo" }),
866
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
867
+ "input",
868
+ {
869
+ type: "text",
870
+ value: data.title,
871
+ onChange: (e) => save({ title: e.target.value }),
872
+ placeholder: "Titulo del embed (accesibilidad)"
873
+ }
874
+ )
875
+ ] }),
876
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sci-nb-embed-row", children: [
877
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sci-nb-embed-field sci-nb-embed-field--small", children: [
878
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Altura" }),
879
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { value: data.height, onChange: (e) => save({ height: e.target.value }), children: [
880
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "200px", children: "200px" }),
881
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "300px", children: "300px" }),
882
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "400px", children: "400px" }),
883
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "500px", children: "500px" }),
884
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "600px", children: "600px" })
885
+ ] })
886
+ ] }),
887
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sci-nb-embed-field sci-nb-embed-field--small", children: [
888
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Sandbox" }),
889
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { value: data.sandbox, onChange: (e) => save({ sandbox: e.target.value }), children: [
890
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "allow-scripts allow-same-origin allow-popups", children: "Standard" }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "allow-scripts", children: "Solo scripts" }),
892
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "", children: "Restringido" })
893
+ ] })
894
+ ] })
895
+ ] })
896
+ ] }),
897
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "sci-nb-cell-hint", children: [
898
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("kbd", { children: "Esc" }),
899
+ " salir"
900
+ ] })
901
+ ] });
902
+ };
903
+ function renderEmbedPreview(source, metadata) {
904
+ const data = parseEmbedSource(source, metadata);
905
+ if (!data.url) {
906
+ return '<div class="sci-nb-embed-empty"><span class="sci-nb-placeholder">Click para agregar contenido embebido</span></div>';
907
+ }
908
+ const titleAttr = data.title ? ` title="${escapeAttr2(data.title)}"` : "";
909
+ return `<div class="sci-nb-embed-view" style="height:${data.height}">
910
+ <iframe src="${escapeAttr2(data.url)}"${titleAttr} sandbox="${escapeAttr2(data.sandbox)}" style="width:100%;height:100%;border:none;border-radius:6px" loading="lazy" allowfullscreen></iframe>
911
+ </div>`;
912
+ }
913
+ function escapeAttr2(s) {
914
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
915
+ }
916
+
917
+ // src/components/SlashCommand.tsx
918
+ var import_react6 = require("react");
919
+ var import_jsx_runtime5 = require("react/jsx-runtime");
920
+ var DEFAULT_COMMANDS = [
921
+ { type: "markdown", label: "Texto", description: "Bloque de texto con formato Markdown", icon: "M", keywords: ["text", "markdown", "paragraph", "texto"] },
922
+ { type: "code", label: "C\xF3digo", description: "Bloque de c\xF3digo con syntax highlighting", icon: "</>", keywords: ["code", "codigo", "script", "program"] },
923
+ { type: "latex", label: "F\xF3rmula", description: "Editor visual de f\xF3rmulas LaTeX", icon: "\u2211", keywords: ["latex", "math", "formula", "ecuacion", "equation"] },
924
+ { type: "image", label: "Imagen", description: "Imagen con drag & drop, URL, caption", icon: "\u{1F5BC}", keywords: ["image", "imagen", "foto", "picture", "img"] },
925
+ { type: "embed", label: "Embed", description: "YouTube, CodePen, Desmos, iframe", icon: "\u29C9", keywords: ["embed", "iframe", "youtube", "video", "codepen"] },
926
+ { type: "table", label: "Tabla", description: "Tabla editable con filas y columnas", icon: "\u25A6", keywords: ["table", "tabla", "grid", "spreadsheet"] },
927
+ { type: "mermaid", label: "Diagrama", description: "Diagrama Mermaid (flowchart, sequence, etc.)", icon: "\u25C7", keywords: ["mermaid", "diagram", "diagrama", "flowchart", "chart"] },
928
+ { type: "raw", label: "Raw", description: "Texto sin formato", icon: "T", keywords: ["raw", "plain", "text", "crudo"] }
929
+ ];
930
+ var SlashCommand = ({
931
+ position,
932
+ query,
933
+ onSelect,
934
+ onClose,
935
+ extraCommands
936
+ }) => {
937
+ const allCommands = extraCommands ? [...DEFAULT_COMMANDS, ...extraCommands] : DEFAULT_COMMANDS;
938
+ const filtered = query ? allCommands.filter((cmd) => {
939
+ const q = query.toLowerCase();
940
+ return cmd.label.toLowerCase().includes(q) || cmd.type.toLowerCase().includes(q) || cmd.keywords.some((k) => k.includes(q));
941
+ }) : allCommands;
942
+ const [selectedIndex, setSelectedIndex] = (0, import_react6.useState)(0);
943
+ const menuRef = (0, import_react6.useRef)(null);
944
+ (0, import_react6.useEffect)(() => {
945
+ setSelectedIndex(0);
946
+ }, [query]);
947
+ (0, import_react6.useEffect)(() => {
948
+ const handleKey = (e) => {
949
+ if (e.key === "ArrowDown") {
950
+ e.preventDefault();
951
+ setSelectedIndex((i) => (i + 1) % Math.max(filtered.length, 1));
952
+ } else if (e.key === "ArrowUp") {
953
+ e.preventDefault();
954
+ setSelectedIndex((i) => (i - 1 + filtered.length) % Math.max(filtered.length, 1));
955
+ } else if (e.key === "Enter" && filtered.length > 0) {
956
+ e.preventDefault();
957
+ onSelect(filtered[selectedIndex]?.type || "markdown");
958
+ } else if (e.key === "Escape") {
959
+ e.preventDefault();
960
+ onClose();
961
+ }
962
+ };
963
+ document.addEventListener("keydown", handleKey, true);
964
+ return () => document.removeEventListener("keydown", handleKey, true);
965
+ }, [filtered, selectedIndex, onSelect, onClose]);
966
+ (0, import_react6.useEffect)(() => {
967
+ const handleClick = (e) => {
968
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
969
+ onClose();
970
+ }
971
+ };
972
+ document.addEventListener("mousedown", handleClick);
973
+ return () => document.removeEventListener("mousedown", handleClick);
974
+ }, [onClose]);
975
+ if (filtered.length === 0) {
976
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
977
+ "div",
978
+ {
979
+ ref: menuRef,
980
+ className: "sci-nb-slash-menu",
981
+ style: { top: position.top, left: position.left },
982
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "sci-nb-slash-empty", children: [
983
+ 'Sin resultados para "/',
984
+ query,
985
+ '"'
986
+ ] })
987
+ }
988
+ );
989
+ }
990
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
991
+ "div",
992
+ {
993
+ ref: menuRef,
994
+ className: "sci-nb-slash-menu",
995
+ style: { top: position.top, left: position.left },
996
+ children: [
997
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "sci-nb-slash-header", children: "Insertar bloque" }),
998
+ filtered.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
999
+ "button",
1000
+ {
1001
+ className: `sci-nb-slash-item ${i === selectedIndex ? "sci-nb-slash-item--active" : ""}`,
1002
+ onMouseEnter: () => setSelectedIndex(i),
1003
+ onClick: () => onSelect(cmd.type),
1004
+ children: [
1005
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "sci-nb-slash-icon", children: cmd.icon }),
1006
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "sci-nb-slash-text", children: [
1007
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "sci-nb-slash-label", children: cmd.label }),
1008
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "sci-nb-slash-desc", children: cmd.description })
1009
+ ] })
1010
+ ]
1011
+ },
1012
+ cmd.type + cmd.label
1013
+ ))
1014
+ ]
1015
+ }
1016
+ );
1017
+ };
1018
+
1019
+ // src/components/TableCell.tsx
1020
+ var import_react7 = require("react");
1021
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1022
+ function parseMarkdownTable(source) {
1023
+ const lines = source.trim().split("\n").filter((l) => l.trim());
1024
+ if (lines.length < 2) {
1025
+ return { headers: ["Col 1", "Col 2", "Col 3"], rows: [["", "", ""], ["", "", ""]] };
1026
+ }
1027
+ const parseLine = (line) => line.split("|").map((c) => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length);
1028
+ const headers = parseLine(lines[0]);
1029
+ const rows = lines.slice(2).map(parseLine);
1030
+ const colCount = headers.length;
1031
+ const normalizedRows = rows.map((row) => {
1032
+ while (row.length < colCount) row.push("");
1033
+ return row.slice(0, colCount);
1034
+ });
1035
+ if (normalizedRows.length === 0) {
1036
+ normalizedRows.push(new Array(colCount).fill(""));
1037
+ }
1038
+ return { headers, rows: normalizedRows };
1039
+ }
1040
+ function toMarkdownTable(data) {
1041
+ const { headers, rows } = data;
1042
+ const headerLine = `| ${headers.join(" | ")} |`;
1043
+ const sepLine = `| ${headers.map(() => "---").join(" | ")} |`;
1044
+ const rowLines = rows.map((row) => `| ${row.join(" | ")} |`);
1045
+ return [headerLine, sepLine, ...rowLines].join("\n");
1046
+ }
1047
+ var TableCell = ({ cellId, source, onExit }) => {
1048
+ const engine = useSciNotebook();
1049
+ const [data, setData] = (0, import_react7.useState)(() => parseMarkdownTable(source));
1050
+ const syncToEngine = (0, import_react7.useCallback)((newData) => {
1051
+ setData(newData);
1052
+ engine.updateCellSource(cellId, toMarkdownTable(newData));
1053
+ }, [engine, cellId]);
1054
+ const updateHeader = (0, import_react7.useCallback)((colIdx, value) => {
1055
+ const newHeaders = [...data.headers];
1056
+ newHeaders[colIdx] = value;
1057
+ syncToEngine({ ...data, headers: newHeaders });
1058
+ }, [data, syncToEngine]);
1059
+ const updateCell = (0, import_react7.useCallback)((rowIdx, colIdx, value) => {
1060
+ const newRows = data.rows.map((r) => [...r]);
1061
+ newRows[rowIdx][colIdx] = value;
1062
+ syncToEngine({ ...data, rows: newRows });
1063
+ }, [data, syncToEngine]);
1064
+ const addRow = (0, import_react7.useCallback)(() => {
1065
+ const newRow = new Array(data.headers.length).fill("");
1066
+ syncToEngine({ ...data, rows: [...data.rows, newRow] });
1067
+ }, [data, syncToEngine]);
1068
+ const addColumn = (0, import_react7.useCallback)(() => {
1069
+ const newHeaders = [...data.headers, `Col ${data.headers.length + 1}`];
1070
+ const newRows = data.rows.map((row) => [...row, ""]);
1071
+ syncToEngine({ headers: newHeaders, rows: newRows });
1072
+ }, [data, syncToEngine]);
1073
+ const removeRow = (0, import_react7.useCallback)((rowIdx) => {
1074
+ if (data.rows.length <= 1) return;
1075
+ const newRows = data.rows.filter((_, i) => i !== rowIdx);
1076
+ syncToEngine({ ...data, rows: newRows });
1077
+ }, [data, syncToEngine]);
1078
+ const removeColumn = (0, import_react7.useCallback)((colIdx) => {
1079
+ if (data.headers.length <= 1) return;
1080
+ const newHeaders = data.headers.filter((_, i) => i !== colIdx);
1081
+ const newRows = data.rows.map((row) => row.filter((_, i) => i !== colIdx));
1082
+ syncToEngine({ headers: newHeaders, rows: newRows });
1083
+ }, [data, syncToEngine]);
1084
+ const handleKeyDown = (0, import_react7.useCallback)((e) => {
1085
+ if (e.key === "Escape") {
1086
+ e.preventDefault();
1087
+ onExit();
1088
+ } else if (e.key === "Tab" && !e.shiftKey) {
1089
+ }
1090
+ }, [onExit]);
1091
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "sci-nb-table-editor", onKeyDown: handleKeyDown, children: [
1092
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "sci-nb-table-toolbar", children: [
1093
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: addRow, children: "+ Fila" }),
1094
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: addColumn, children: "+ Columna" })
1095
+ ] }),
1096
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { children: [
1097
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { children: [
1098
+ data.headers.map((h, ci) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1099
+ "input",
1100
+ {
1101
+ value: h,
1102
+ onChange: (e) => updateHeader(ci, e.target.value),
1103
+ placeholder: `Col ${ci + 1}`
1104
+ }
1105
+ ) }, ci)),
1106
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: { width: 30, padding: 0 } })
1107
+ ] }) }),
1108
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: data.rows.map((row, ri) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { children: [
1109
+ row.map((cell, ci) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1110
+ "input",
1111
+ {
1112
+ value: cell,
1113
+ onChange: (e) => updateCell(ri, ci, e.target.value),
1114
+ placeholder: "..."
1115
+ }
1116
+ ) }, ci)),
1117
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: { width: 30, padding: 0, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1118
+ "button",
1119
+ {
1120
+ className: "sci-nb-btn sci-nb-btn--danger",
1121
+ onClick: () => removeRow(ri),
1122
+ title: "Eliminar fila",
1123
+ style: { padding: "2px 4px", fontSize: 10, border: "none", background: "transparent" },
1124
+ children: "\u2715"
1125
+ }
1126
+ ) })
1127
+ ] }, ri)) })
1128
+ ] }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "sci-nb-cell-hint", children: [
1130
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("kbd", { children: "Tab" }),
1131
+ " siguiente celda \xB7 ",
1132
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("kbd", { children: "Esc" }),
1133
+ " salir"
1134
+ ] })
1135
+ ] });
1136
+ };
1137
+ function renderTablePreview(source) {
1138
+ const data = parseMarkdownTable(source);
1139
+ if (data.headers.length === 0) return "<p>Tabla vac\xEDa</p>";
1140
+ const ths = data.headers.map((h) => `<th>${escapeHtml3(h)}</th>`).join("");
1141
+ const trs = data.rows.map(
1142
+ (row) => `<tr>${row.map((c) => `<td>${escapeHtml3(c)}</td>`).join("")}</tr>`
1143
+ ).join("");
1144
+ return `<table class="sci-nb-rendered-table"><thead><tr>${ths}</tr></thead><tbody>${trs}</tbody></table>`;
1145
+ }
1146
+ function escapeHtml3(s) {
1147
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1148
+ }
1149
+
1150
+ // src/components/CellOutput.tsx
1151
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1152
+ var CellOutputDisplay = ({ outputs }) => {
1153
+ if (!outputs || outputs.length === 0) return null;
1154
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "sci-nb-cell-outputs", children: outputs.map((output, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `sci-nb-output sci-nb-output--${output.outputType}`, children: renderOutput(output) }, i)) });
1155
+ };
1156
+ function renderOutput(output) {
1157
+ switch (output.outputType) {
1158
+ case "stream":
1159
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { className: `sci-nb-output-stream sci-nb-output-stream--${output.name}`, children: output.text });
1160
+ case "display": {
1161
+ if (output.data["text/html"]) {
1162
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1163
+ "div",
1164
+ {
1165
+ className: "sci-nb-output-html",
1166
+ dangerouslySetInnerHTML: { __html: output.data["text/html"] }
1167
+ }
1168
+ );
1169
+ }
1170
+ if (output.data["image/svg+xml"]) {
1171
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1172
+ "div",
1173
+ {
1174
+ className: "sci-nb-output-svg",
1175
+ dangerouslySetInnerHTML: { __html: output.data["image/svg+xml"] }
1176
+ }
1177
+ );
1178
+ }
1179
+ if (output.data["image/png"]) {
1180
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1181
+ "img",
1182
+ {
1183
+ className: "sci-nb-output-image",
1184
+ src: `data:image/png;base64,${output.data["image/png"]}`,
1185
+ alt: "Output"
1186
+ }
1187
+ );
1188
+ }
1189
+ if (output.data["image/jpeg"]) {
1190
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1191
+ "img",
1192
+ {
1193
+ className: "sci-nb-output-image",
1194
+ src: `data:image/jpeg;base64,${output.data["image/jpeg"]}`,
1195
+ alt: "Output"
1196
+ }
1197
+ );
1198
+ }
1199
+ if (output.data["application/json"]) {
1200
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { className: "sci-nb-output-json", children: JSON.stringify(JSON.parse(output.data["application/json"]), null, 2) });
1201
+ }
1202
+ if (output.data["text/plain"]) {
1203
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { className: "sci-nb-output-text", children: output.data["text/plain"] });
1204
+ }
1205
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { className: "sci-nb-output-text", children: "[Display output]" });
1206
+ }
1207
+ case "error":
1208
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "sci-nb-output-error", children: [
1209
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("strong", { className: "sci-nb-output-error-name", children: [
1210
+ output.name,
1211
+ ": "
1212
+ ] }),
1213
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "sci-nb-output-error-msg", children: output.message }),
1214
+ output.traceback && output.traceback.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pre", { className: "sci-nb-output-traceback", children: output.traceback.join("\n") })
1215
+ ] });
1216
+ default:
1217
+ return null;
1218
+ }
1219
+ }
1220
+
1221
+ // src/components/MermaidCell.tsx
1222
+ var import_react8 = require("react");
1223
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1224
+ var mermaidIdCounter = 0;
1225
+ var MermaidPreview = ({ source, onClick }) => {
1226
+ const [svg, setSvg] = (0, import_react8.useState)(null);
1227
+ const [error, setError] = (0, import_react8.useState)(null);
1228
+ const containerRef = (0, import_react8.useRef)(null);
1229
+ (0, import_react8.useEffect)(() => {
1230
+ const trimmed = source.trim();
1231
+ if (!trimmed) {
1232
+ setSvg(null);
1233
+ setError(null);
1234
+ return;
1235
+ }
1236
+ const mermaid2 = globalThis.mermaid;
1237
+ if (!mermaid2) {
1238
+ setError(null);
1239
+ setSvg(null);
1240
+ return;
1241
+ }
1242
+ let cancelled = false;
1243
+ const id = `sci-mermaid-${++mermaidIdCounter}`;
1244
+ (async () => {
1245
+ try {
1246
+ const result = await mermaid2.render(id, trimmed);
1247
+ if (!cancelled) {
1248
+ setSvg(result.svg);
1249
+ setError(null);
1250
+ }
1251
+ } catch (e) {
1252
+ if (!cancelled) {
1253
+ setError(e.message || String(e));
1254
+ setSvg(null);
1255
+ }
1256
+ const errEl = document.getElementById(`d${id}`);
1257
+ if (errEl) errEl.remove();
1258
+ }
1259
+ })();
1260
+ return () => {
1261
+ cancelled = true;
1262
+ };
1263
+ }, [source]);
1264
+ const mermaid = globalThis.mermaid;
1265
+ if (!source.trim()) {
1266
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "sci-nb-mermaid-preview", onClick, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "sci-nb-placeholder", children: "Diagrama vacio \u2014 escribe sintaxis Mermaid" }) });
1267
+ }
1268
+ if (!mermaid) {
1269
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "sci-nb-mermaid-preview", onClick, children: [
1270
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("pre", { className: "sci-nb-code", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { className: "language-mermaid", children: source }) }),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { fontSize: 11, color: "#94a3b8", textAlign: "center", padding: 4 }, children: [
1272
+ "Mermaid no disponible. Importa ",
1273
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: "mermaid" }),
1274
+ " y exponlo como ",
1275
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: "globalThis.mermaid" }),
1276
+ "."
1277
+ ] })
1278
+ ] });
1279
+ }
1280
+ if (error) {
1281
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "sci-nb-mermaid-error", onClick, children: [
1282
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: "Mermaid error:" }),
1283
+ " ",
1284
+ error
1285
+ ] });
1286
+ }
1287
+ if (svg) {
1288
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1289
+ "div",
1290
+ {
1291
+ ref: containerRef,
1292
+ className: "sci-nb-mermaid-preview",
1293
+ onClick,
1294
+ dangerouslySetInnerHTML: { __html: svg }
1295
+ }
1296
+ );
1297
+ }
1298
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "sci-nb-mermaid-preview", onClick, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "sci-nb-placeholder", children: "Renderizando diagrama..." }) });
1299
+ };
1300
+ function initMermaid(mermaidLib, config) {
1301
+ globalThis.mermaid = mermaidLib;
1302
+ mermaidLib.initialize({
1303
+ startOnLoad: false,
1304
+ theme: "default",
1305
+ securityLevel: "loose",
1306
+ ...config
1307
+ });
1308
+ }
1309
+
1310
+ // src/components/Cell.tsx
1311
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1312
+ var CELL_TYPES = [
1313
+ { value: "markdown", label: "Markdown", icon: "M" },
1314
+ { value: "code", label: "Code", icon: "</>" },
1315
+ { value: "raw", label: "Raw", icon: "T" },
1316
+ { value: "latex", label: "LaTeX", icon: "\u2211" },
1317
+ { value: "image", label: "Imagen", icon: "\u{1F5BC}" },
1318
+ { value: "embed", label: "Embed", icon: "\u29C9" }
1319
+ ];
1320
+ var PLACEHOLDERS = {
1321
+ markdown: "Escribe markdown aqui... (click para editar)",
1322
+ code: "Escribe codigo aqui...",
1323
+ raw: "Texto raw...",
1324
+ latex: "Escribe LaTeX aqui... e.g. \\int_0^1 x^2 dx",
1325
+ image: "Click para agregar imagen",
1326
+ embed: "Click para agregar contenido embebido"
1327
+ };
1328
+ var Cell = ({ cellId, pipeline, index, totalCells }) => {
1329
+ const cell = useCell(cellId);
1330
+ const engine = useSciNotebook();
1331
+ const textareaRef = (0, import_react9.useRef)(null);
1332
+ const cellRef = (0, import_react9.useRef)(null);
1333
+ const [showTypeMenu, setShowTypeMenu] = (0, import_react9.useState)(false);
1334
+ const [hovered, setHovered] = (0, import_react9.useState)(false);
1335
+ const [slashState, setSlashState] = (0, import_react9.useState)(null);
1336
+ const [dragOver, setDragOver] = (0, import_react9.useState)(null);
1337
+ const [isDragging, setIsDragging] = (0, import_react9.useState)(false);
1338
+ const rendered = (0, import_react9.useMemo)(() => {
1339
+ if (!cell) return { html: "", cellId: "", renderTime: 0, cached: false };
1340
+ return pipeline.render(cell);
1341
+ }, [cell?.source, cell?.type, cell?.metadata, pipeline]);
1342
+ (0, import_react9.useEffect)(() => {
1343
+ if (cell?.editing && textareaRef.current) {
1344
+ const ta = textareaRef.current;
1345
+ ta.style.height = "auto";
1346
+ ta.style.height = `${Math.max(40, ta.scrollHeight)}px`;
1347
+ }
1348
+ }, [cell?.source, cell?.editing]);
1349
+ (0, import_react9.useEffect)(() => {
1350
+ if (cell?.editing && textareaRef.current) {
1351
+ textareaRef.current.focus();
1352
+ }
1353
+ }, [cell?.editing]);
1354
+ (0, import_react9.useEffect)(() => {
1355
+ if (!showTypeMenu) return;
1356
+ const handler = (e) => {
1357
+ if (cellRef.current && !cellRef.current.contains(e.target)) {
1358
+ setShowTypeMenu(false);
1359
+ }
1360
+ };
1361
+ document.addEventListener("mousedown", handler);
1362
+ return () => document.removeEventListener("mousedown", handler);
1363
+ }, [showTypeMenu]);
1364
+ const handleSourceChange = (0, import_react9.useCallback)((e) => {
1365
+ const val = e.target.value;
1366
+ engine.updateCellSource(cellId, val);
1367
+ const ta = e.target;
1368
+ const cursor = ta.selectionStart;
1369
+ const textBefore = val.slice(0, cursor);
1370
+ const lastNewline = textBefore.lastIndexOf("\n");
1371
+ const lineStart = lastNewline + 1;
1372
+ const currentLine = textBefore.slice(lineStart);
1373
+ if (currentLine.startsWith("/")) {
1374
+ const query = currentLine.slice(1);
1375
+ const rect = ta.getBoundingClientRect();
1376
+ const lineHeight = 22;
1377
+ const lines = textBefore.split("\n").length;
1378
+ setSlashState({
1379
+ query,
1380
+ pos: { top: rect.top + lines * lineHeight + 4 - ta.scrollTop, left: rect.left + 8 }
1381
+ });
1382
+ } else {
1383
+ setSlashState(null);
1384
+ }
1385
+ }, [engine, cellId]);
1386
+ const enterEdit = (0, import_react9.useCallback)(() => {
1387
+ engine.setEditMode(cellId);
1388
+ engine.focusCell(cellId);
1389
+ }, [engine, cellId]);
1390
+ const exitEdit = (0, import_react9.useCallback)(() => {
1391
+ engine.setViewMode(cellId);
1392
+ }, [engine, cellId]);
1393
+ const handleSlashSelect = (0, import_react9.useCallback)((type) => {
1394
+ const val = cell?.source || "";
1395
+ const ta = textareaRef.current;
1396
+ if (ta) {
1397
+ const cursor = ta.selectionStart;
1398
+ const textBefore = val.slice(0, cursor);
1399
+ const lastNewline = textBefore.lastIndexOf("\n");
1400
+ const lineStart = lastNewline + 1;
1401
+ const cleaned = val.slice(0, lineStart) + val.slice(cursor);
1402
+ engine.updateCellSource(cellId, cleaned.trim());
1403
+ }
1404
+ engine.setCellType(cellId, type);
1405
+ setSlashState(null);
1406
+ }, [engine, cellId, cell?.source]);
1407
+ const handleKeyDown = (0, import_react9.useCallback)((e) => {
1408
+ if (slashState) {
1409
+ if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) return;
1410
+ if (e.key === "Escape") {
1411
+ e.preventDefault();
1412
+ setSlashState(null);
1413
+ return;
1414
+ }
1415
+ }
1416
+ if (e.key === "Escape") {
1417
+ e.preventDefault();
1418
+ exitEdit();
1419
+ } else if (e.key === "Enter" && e.shiftKey) {
1420
+ e.preventDefault();
1421
+ exitEdit();
1422
+ const cells = engine.getCells();
1423
+ const idx = cells.findIndex((c) => c.id === cellId);
1424
+ if (idx < cells.length - 1) {
1425
+ engine.focusCell(cells[idx + 1].id);
1426
+ engine.setEditMode(cells[idx + 1].id);
1427
+ }
1428
+ } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
1429
+ e.preventDefault();
1430
+ exitEdit();
1431
+ } else if (e.key === "Tab" && !e.shiftKey) {
1432
+ e.preventDefault();
1433
+ const ta = e.currentTarget;
1434
+ const start = ta.selectionStart;
1435
+ const end = ta.selectionEnd;
1436
+ const val = ta.value;
1437
+ const newVal = val.substring(0, start) + " " + val.substring(end);
1438
+ engine.updateCellSource(cellId, newVal);
1439
+ requestAnimationFrame(() => {
1440
+ if (textareaRef.current) {
1441
+ textareaRef.current.selectionStart = start + 2;
1442
+ textareaRef.current.selectionEnd = start + 2;
1443
+ }
1444
+ });
1445
+ } else if (e.key === "Tab" && e.shiftKey) {
1446
+ e.preventDefault();
1447
+ const ta = e.currentTarget;
1448
+ const start = ta.selectionStart;
1449
+ const val = ta.value;
1450
+ const before = val.substring(0, start);
1451
+ const trimmed = before.replace(/ $/, "");
1452
+ if (trimmed !== before) {
1453
+ engine.updateCellSource(cellId, trimmed + val.substring(start));
1454
+ const diff = before.length - trimmed.length;
1455
+ requestAnimationFrame(() => {
1456
+ if (textareaRef.current) {
1457
+ textareaRef.current.selectionStart = start - diff;
1458
+ textareaRef.current.selectionEnd = start - diff;
1459
+ }
1460
+ });
1461
+ }
1462
+ } else if (e.key === "b" && (e.ctrlKey || e.metaKey)) {
1463
+ e.preventDefault();
1464
+ wrapSelection("**", "**");
1465
+ } else if (e.key === "i" && (e.ctrlKey || e.metaKey)) {
1466
+ e.preventDefault();
1467
+ wrapSelection("*", "*");
1468
+ }
1469
+ }, [engine, cellId, exitEdit]);
1470
+ const wrapSelection = (0, import_react9.useCallback)((before, after) => {
1471
+ const ta = textareaRef.current;
1472
+ if (!ta) return;
1473
+ const start = ta.selectionStart;
1474
+ const end = ta.selectionEnd;
1475
+ const val = ta.value;
1476
+ const newVal = val.substring(0, start) + before + val.substring(start, end) + after + val.substring(end);
1477
+ engine.updateCellSource(cellId, newVal);
1478
+ requestAnimationFrame(() => {
1479
+ if (textareaRef.current) {
1480
+ textareaRef.current.selectionStart = start + before.length;
1481
+ textareaRef.current.selectionEnd = end + before.length;
1482
+ }
1483
+ });
1484
+ }, [engine, cellId]);
1485
+ if (!cell) return null;
1486
+ const isEditing = !!cell.editing;
1487
+ const isEmpty = !cell.source.trim();
1488
+ const placeholder = PLACEHOLDERS[cell.type] || "Click to edit...";
1489
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1490
+ "div",
1491
+ {
1492
+ ref: cellRef,
1493
+ className: [
1494
+ "sci-nb-cell",
1495
+ `sci-nb-cell--${cell.type}`,
1496
+ isEditing ? "sci-nb-cell--edit" : "sci-nb-cell--view",
1497
+ hovered ? "sci-nb-cell--hover" : "",
1498
+ isDragging ? "sci-nb-cell--dragging" : "",
1499
+ dragOver === "top" ? "sci-nb-cell--drag-over-top" : "",
1500
+ dragOver === "bottom" ? "sci-nb-cell--drag-over-bottom" : ""
1501
+ ].filter(Boolean).join(" "),
1502
+ draggable: !isEditing,
1503
+ onDragStart: (e) => {
1504
+ e.dataTransfer.setData("text/plain", cellId);
1505
+ e.dataTransfer.effectAllowed = "move";
1506
+ setIsDragging(true);
1507
+ },
1508
+ onDragEnd: () => {
1509
+ setIsDragging(false);
1510
+ setDragOver(null);
1511
+ },
1512
+ onDragOver: (e) => {
1513
+ e.preventDefault();
1514
+ e.dataTransfer.dropEffect = "move";
1515
+ const rect = e.currentTarget.getBoundingClientRect();
1516
+ const midY = rect.top + rect.height / 2;
1517
+ setDragOver(e.clientY < midY ? "top" : "bottom");
1518
+ },
1519
+ onDragLeave: () => setDragOver(null),
1520
+ onDrop: (e) => {
1521
+ e.preventDefault();
1522
+ const draggedId = e.dataTransfer.getData("text/plain");
1523
+ setDragOver(null);
1524
+ if (draggedId && draggedId !== cellId) {
1525
+ const targetIdx = dragOver === "top" ? index : index + 1;
1526
+ engine.moveCell(draggedId, targetIdx);
1527
+ }
1528
+ },
1529
+ "data-testid": `cell-${cell.id}`,
1530
+ "data-editing": String(isEditing),
1531
+ "data-cell-type": cell.type,
1532
+ role: "region",
1533
+ "aria-label": `${cell.type} cell ${index + 1} of ${totalCells}${isEditing ? ", editing" : ""}`,
1534
+ "aria-selected": isEditing,
1535
+ tabIndex: 0,
1536
+ onMouseEnter: () => setHovered(true),
1537
+ onMouseLeave: () => setHovered(false),
1538
+ onClick: () => engine.focusCell(cellId),
1539
+ children: [
1540
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "sci-nb-cell-gutter", children: [
1541
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "sci-nb-cell-handle", title: "Drag to reorder", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: "12", height: "20", viewBox: "0 0 12 20", fill: "currentColor", children: [
1542
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "3", cy: "4", r: "1.5" }),
1543
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "9", cy: "4", r: "1.5" }),
1544
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "3", cy: "10", r: "1.5" }),
1545
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "9", cy: "10", r: "1.5" }),
1546
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "3", cy: "16", r: "1.5" }),
1547
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "9", cy: "16", r: "1.5" })
1548
+ ] }) }),
1549
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "sci-nb-cell-index", children: [
1550
+ "[",
1551
+ index + 1,
1552
+ "]"
1553
+ ] })
1554
+ ] }),
1555
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "sci-nb-cell-badge-wrap", children: [
1556
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1557
+ "button",
1558
+ {
1559
+ className: "sci-nb-cell-badge",
1560
+ onClick: (e) => {
1561
+ e.stopPropagation();
1562
+ setShowTypeMenu(!showTypeMenu);
1563
+ },
1564
+ title: "Change cell type",
1565
+ children: CELL_TYPES.find((ct) => ct.value === cell.type)?.icon || cell.type.slice(0, 2).toUpperCase()
1566
+ }
1567
+ ),
1568
+ showTypeMenu && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "sci-nb-type-menu", children: CELL_TYPES.map((ct) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1569
+ "button",
1570
+ {
1571
+ className: `sci-nb-type-option ${cell.type === ct.value ? "sci-nb-type-option--active" : ""}`,
1572
+ onClick: (e) => {
1573
+ e.stopPropagation();
1574
+ engine.setCellType(cellId, ct.value);
1575
+ setShowTypeMenu(false);
1576
+ },
1577
+ children: [
1578
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "sci-nb-type-option-icon", children: ct.icon }),
1579
+ ct.label
1580
+ ]
1581
+ },
1582
+ ct.value
1583
+ )) })
1584
+ ] }),
1585
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "sci-nb-cell-content", children: [
1586
+ isEditing ? renderEditMode(cell, cellId, textareaRef, handleSourceChange, handleKeyDown, placeholder, exitEdit, slashState, handleSlashSelect, () => setSlashState(null)) : renderViewMode(cell, rendered.html, isEmpty, placeholder, enterEdit),
1587
+ cell.outputs && cell.outputs.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CellOutputDisplay, { outputs: cell.outputs })
1588
+ ] }),
1589
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "sci-nb-cell-actions", children: [
1590
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1591
+ "button",
1592
+ {
1593
+ className: "sci-nb-btn",
1594
+ onClick: (e) => {
1595
+ e.stopPropagation();
1596
+ engine.moveCell(cellId, index - 1);
1597
+ },
1598
+ disabled: index === 0,
1599
+ title: "Move up",
1600
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M7 11V3M7 3L3 7M7 3l4 4", strokeLinecap: "round", strokeLinejoin: "round" }) })
1601
+ }
1602
+ ),
1603
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1604
+ "button",
1605
+ {
1606
+ className: "sci-nb-btn",
1607
+ onClick: (e) => {
1608
+ e.stopPropagation();
1609
+ engine.moveCell(cellId, index + 1);
1610
+ },
1611
+ disabled: index >= totalCells - 1,
1612
+ title: "Move down",
1613
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M7 3v8M7 11l-4-4M7 11l4-4", strokeLinecap: "round", strokeLinejoin: "round" }) })
1614
+ }
1615
+ ),
1616
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1617
+ "button",
1618
+ {
1619
+ className: "sci-nb-btn",
1620
+ onClick: (e) => {
1621
+ e.stopPropagation();
1622
+ engine.duplicateCell(cellId);
1623
+ },
1624
+ title: "Duplicate cell",
1625
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
1626
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5" }),
1627
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M10 2H3.5A1.5 1.5 0 002 3.5V10" })
1628
+ ] })
1629
+ }
1630
+ ),
1631
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1632
+ "button",
1633
+ {
1634
+ className: "sci-nb-btn sci-nb-btn--danger",
1635
+ onClick: (e) => {
1636
+ e.stopPropagation();
1637
+ engine.deleteCell(cellId);
1638
+ },
1639
+ title: "Delete cell",
1640
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M3 4h8M5.5 4V3a1 1 0 011-1h1a1 1 0 011 1v1M6 6.5v3M8 6.5v3M4 4l.5 7a1.5 1.5 0 001.5 1.5h2A1.5 1.5 0 0010 11l.5-7", strokeLinecap: "round", strokeLinejoin: "round" }) })
1641
+ }
1642
+ )
1643
+ ] })
1644
+ ]
1645
+ }
1646
+ );
1647
+ };
1648
+ function renderEditMode(cell, cellId, textareaRef, handleSourceChange, handleKeyDown, placeholder, exitEdit, slashState, onSlashSelect, onSlashClose) {
1649
+ if (cell.type === "latex") {
1650
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MathEditor, { cellId, source: cell.source, onExit: exitEdit });
1651
+ }
1652
+ if (cell.type === "image") {
1653
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ImageCell, { cellId, source: cell.source, metadata: cell.metadata, onExit: exitEdit });
1654
+ }
1655
+ if (cell.type === "embed") {
1656
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EmbedCell, { cellId, source: cell.source, metadata: cell.metadata, onExit: exitEdit });
1657
+ }
1658
+ if (cell.type === "table") {
1659
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TableCell, { cellId, source: cell.source, metadata: cell.metadata, onExit: exitEdit });
1660
+ }
1661
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1662
+ cell.type === "markdown" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FloatingToolbar, { cellId, textareaRef }),
1663
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1664
+ "textarea",
1665
+ {
1666
+ ref: textareaRef,
1667
+ className: "sci-nb-editor",
1668
+ value: cell.source,
1669
+ onChange: handleSourceChange,
1670
+ onKeyDown: handleKeyDown,
1671
+ placeholder,
1672
+ spellCheck: cell.type === "markdown",
1673
+ rows: 1
1674
+ }
1675
+ ),
1676
+ slashState && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1677
+ SlashCommand,
1678
+ {
1679
+ position: slashState.pos,
1680
+ query: slashState.query,
1681
+ onSelect: onSlashSelect,
1682
+ onClose: onSlashClose
1683
+ }
1684
+ ),
1685
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "sci-nb-cell-hint", children: [
1686
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("kbd", { children: "/" }),
1687
+ " comandos \xB7 ",
1688
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("kbd", { children: "Shift+Enter" }),
1689
+ " siguiente \xB7 ",
1690
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("kbd", { children: "Esc" }),
1691
+ " salir"
1692
+ ] })
1693
+ ] });
1694
+ }
1695
+ function renderViewMode(cell, renderedHtml, isEmpty, placeholder, enterEdit) {
1696
+ if (cell.type === "image") {
1697
+ const html = renderImagePreview(cell.source, cell.metadata);
1698
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1699
+ "div",
1700
+ {
1701
+ className: "sci-nb-preview",
1702
+ onClick: enterEdit,
1703
+ dangerouslySetInnerHTML: { __html: html }
1704
+ }
1705
+ );
1706
+ }
1707
+ if (cell.type === "embed") {
1708
+ const html = renderEmbedPreview(cell.source, cell.metadata);
1709
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1710
+ "div",
1711
+ {
1712
+ className: "sci-nb-preview sci-nb-preview--embed",
1713
+ onClick: enterEdit,
1714
+ dangerouslySetInnerHTML: { __html: html }
1715
+ }
1716
+ );
1717
+ }
1718
+ if (cell.type === "table") {
1719
+ const html = renderTablePreview(cell.source);
1720
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1721
+ "div",
1722
+ {
1723
+ className: "sci-nb-preview",
1724
+ onClick: enterEdit,
1725
+ dangerouslySetInnerHTML: { __html: html }
1726
+ }
1727
+ );
1728
+ }
1729
+ if (cell.type === "mermaid") {
1730
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MermaidPreview, { source: cell.source, onClick: enterEdit });
1731
+ }
1732
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1733
+ "div",
1734
+ {
1735
+ className: `sci-nb-preview ${isEmpty ? "sci-nb-preview--empty" : ""}`,
1736
+ onClick: enterEdit,
1737
+ dangerouslySetInnerHTML: isEmpty ? { __html: `<span class="sci-nb-placeholder">${placeholder}</span>` } : { __html: renderedHtml }
1738
+ }
1739
+ );
1740
+ }
1741
+
1742
+ // src/components/InsertHandle.tsx
1743
+ var import_react10 = require("react");
1744
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1745
+ var INSERT_TYPES = [
1746
+ { type: "markdown", label: "Markdown", icon: "M" },
1747
+ { type: "code", label: "Code", icon: "</>" },
1748
+ { type: "latex", label: "LaTeX", icon: "\u2211" },
1749
+ { type: "image", label: "Imagen", icon: "\u{1F5BC}" },
1750
+ { type: "embed", label: "Embed", icon: "\u29C9" },
1751
+ { type: "raw", label: "Raw", icon: "T" }
1752
+ ];
1753
+ var InsertHandle = ({ index }) => {
1754
+ const engine = useSciNotebook();
1755
+ const [open, setOpen] = (0, import_react10.useState)(false);
1756
+ const menuRef = (0, import_react10.useRef)(null);
1757
+ (0, import_react10.useEffect)(() => {
1758
+ if (!open) return;
1759
+ const onClickOutside = (e) => {
1760
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
1761
+ setOpen(false);
1762
+ }
1763
+ };
1764
+ document.addEventListener("mousedown", onClickOutside);
1765
+ return () => document.removeEventListener("mousedown", onClickOutside);
1766
+ }, [open]);
1767
+ const handleInsert = (type) => {
1768
+ engine.insertCell(index, type);
1769
+ setOpen(false);
1770
+ requestAnimationFrame(() => {
1771
+ const cells = engine.getCells();
1772
+ if (cells[index]) {
1773
+ engine.setEditMode(cells[index].id);
1774
+ engine.focusCell(cells[index].id);
1775
+ }
1776
+ });
1777
+ };
1778
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "sci-nb-insert-handle", ref: menuRef, children: [
1779
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "sci-nb-insert-line", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1780
+ "button",
1781
+ {
1782
+ className: "sci-nb-insert-btn",
1783
+ onClick: () => setOpen(!open),
1784
+ title: "Insert cell",
1785
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
1786
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("line", { x1: "8", y1: "3", x2: "8", y2: "13", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
1787
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("line", { x1: "3", y1: "8", x2: "13", y2: "8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
1788
+ ] })
1789
+ }
1790
+ ) }),
1791
+ open && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "sci-nb-insert-menu", children: INSERT_TYPES.map((ct) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1792
+ "button",
1793
+ {
1794
+ className: "sci-nb-insert-option",
1795
+ onClick: () => handleInsert(ct.type),
1796
+ children: [
1797
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "sci-nb-insert-option-icon", children: ct.icon }),
1798
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: ct.label })
1799
+ ]
1800
+ },
1801
+ ct.type
1802
+ )) })
1803
+ ] });
1804
+ };
1805
+
1806
+ // src/components/TOCSidebar.tsx
1807
+ var import_react11 = require("react");
1808
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1809
+ var TOCSidebar = ({ focusedCellId }) => {
1810
+ const notebook = useNotebook();
1811
+ const engine = useSciNotebook();
1812
+ const items = (0, import_react11.useMemo)(() => {
1813
+ if (!notebook) return [];
1814
+ const result = [];
1815
+ for (const cell of notebook.cells) {
1816
+ if (cell.type !== "markdown") continue;
1817
+ const lines = cell.source.split("\n");
1818
+ for (const line of lines) {
1819
+ const match = line.match(/^(#{1,3})\s+(.+)/);
1820
+ if (match) {
1821
+ result.push({
1822
+ cellId: cell.id,
1823
+ level: match[1].length,
1824
+ text: match[2].replace(/[*_`~#]/g, "").trim()
1825
+ });
1826
+ }
1827
+ }
1828
+ }
1829
+ return result;
1830
+ }, [notebook]);
1831
+ const handleClick = (0, import_react11.useCallback)((cellId) => {
1832
+ engine.focusCell(cellId);
1833
+ engine.setEditMode(cellId);
1834
+ const el = document.querySelector(`[data-testid="cell-${cellId}"]`);
1835
+ if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
1836
+ }, [engine]);
1837
+ if (items.length === 0) return null;
1838
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("nav", { className: "sci-nb-toc", children: [
1839
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "sci-nb-toc-title", children: "Contenido" }),
1840
+ items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1841
+ "button",
1842
+ {
1843
+ className: [
1844
+ "sci-nb-toc-item",
1845
+ `sci-nb-toc-item--h${item.level}`,
1846
+ item.cellId === focusedCellId ? "sci-nb-toc-item--active" : ""
1847
+ ].join(" "),
1848
+ onClick: () => handleClick(item.cellId),
1849
+ title: item.text,
1850
+ children: item.text
1851
+ },
1852
+ `${item.cellId}-${i}`
1853
+ ))
1854
+ ] });
1855
+ };
1856
+
1857
+ // src/components/FindReplace.tsx
1858
+ var import_react12 = require("react");
1859
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1860
+ var FindReplace = ({ onClose }) => {
1861
+ const notebook = useNotebook();
1862
+ const engine = useSciNotebook();
1863
+ const [query, setQuery] = (0, import_react12.useState)("");
1864
+ const [replacement, setReplacement] = (0, import_react12.useState)("");
1865
+ const [showReplace, setShowReplace] = (0, import_react12.useState)(false);
1866
+ const [caseSensitive, setCaseSensitive] = (0, import_react12.useState)(false);
1867
+ const [currentIdx, setCurrentIdx] = (0, import_react12.useState)(0);
1868
+ const inputRef = (0, import_react12.useRef)(null);
1869
+ (0, import_react12.useEffect)(() => {
1870
+ inputRef.current?.focus();
1871
+ }, []);
1872
+ const matches = (0, import_react12.useMemo)(() => {
1873
+ if (!query || !notebook) return [];
1874
+ const result = [];
1875
+ const q = caseSensitive ? query : query.toLowerCase();
1876
+ for (const cell of notebook.cells) {
1877
+ const src = caseSensitive ? cell.source : cell.source.toLowerCase();
1878
+ let pos = 0;
1879
+ while (true) {
1880
+ const idx = src.indexOf(q, pos);
1881
+ if (idx === -1) break;
1882
+ result.push({ cellId: cell.id, index: idx, length: query.length });
1883
+ pos = idx + 1;
1884
+ }
1885
+ }
1886
+ return result;
1887
+ }, [query, notebook, caseSensitive]);
1888
+ (0, import_react12.useEffect)(() => {
1889
+ setCurrentIdx(0);
1890
+ }, [query, caseSensitive]);
1891
+ const navigateToMatch = (0, import_react12.useCallback)((match) => {
1892
+ engine.focusCell(match.cellId);
1893
+ const el = document.querySelector(`[data-testid="cell-${match.cellId}"]`);
1894
+ if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
1895
+ }, [engine]);
1896
+ const goNext = (0, import_react12.useCallback)(() => {
1897
+ if (matches.length === 0) return;
1898
+ const next = (currentIdx + 1) % matches.length;
1899
+ setCurrentIdx(next);
1900
+ navigateToMatch(matches[next]);
1901
+ }, [matches, currentIdx, navigateToMatch]);
1902
+ const goPrev = (0, import_react12.useCallback)(() => {
1903
+ if (matches.length === 0) return;
1904
+ const prev = (currentIdx - 1 + matches.length) % matches.length;
1905
+ setCurrentIdx(prev);
1906
+ navigateToMatch(matches[prev]);
1907
+ }, [matches, currentIdx, navigateToMatch]);
1908
+ const replaceCurrent = (0, import_react12.useCallback)(() => {
1909
+ if (matches.length === 0) return;
1910
+ const match = matches[currentIdx];
1911
+ if (!match) return;
1912
+ const cell = notebook?.cells.find((c) => c.id === match.cellId);
1913
+ if (!cell) return;
1914
+ const newSource = cell.source.slice(0, match.index) + replacement + cell.source.slice(match.index + match.length);
1915
+ engine.updateCellSource(match.cellId, newSource);
1916
+ }, [matches, currentIdx, replacement, notebook, engine]);
1917
+ const replaceAll = (0, import_react12.useCallback)(() => {
1918
+ if (matches.length === 0 || !notebook) return;
1919
+ const byCellId = /* @__PURE__ */ new Map();
1920
+ for (const m of matches) {
1921
+ const arr = byCellId.get(m.cellId) || [];
1922
+ arr.push(m);
1923
+ byCellId.set(m.cellId, arr);
1924
+ }
1925
+ for (const [cellId, cellMatches] of byCellId) {
1926
+ const cell = notebook.cells.find((c) => c.id === cellId);
1927
+ if (!cell) continue;
1928
+ let src = cell.source;
1929
+ for (let i = cellMatches.length - 1; i >= 0; i--) {
1930
+ const m = cellMatches[i];
1931
+ src = src.slice(0, m.index) + replacement + src.slice(m.index + m.length);
1932
+ }
1933
+ engine.updateCellSource(cellId, src);
1934
+ }
1935
+ }, [matches, replacement, notebook, engine]);
1936
+ const handleKeyDown = (0, import_react12.useCallback)((e) => {
1937
+ if (e.key === "Escape") {
1938
+ e.preventDefault();
1939
+ onClose();
1940
+ } else if (e.key === "Enter") {
1941
+ e.preventDefault();
1942
+ if (e.shiftKey) goPrev();
1943
+ else goNext();
1944
+ } else if (e.key === "h" && (e.ctrlKey || e.metaKey)) {
1945
+ e.preventDefault();
1946
+ setShowReplace((v) => !v);
1947
+ }
1948
+ }, [onClose, goNext, goPrev]);
1949
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "sci-nb-find-bar", onKeyDown: handleKeyDown, children: [
1950
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1951
+ "input",
1952
+ {
1953
+ ref: inputRef,
1954
+ type: "text",
1955
+ value: query,
1956
+ onChange: (e) => setQuery(e.target.value),
1957
+ placeholder: "Buscar..."
1958
+ }
1959
+ ),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "sci-nb-find-count", children: matches.length > 0 ? `${currentIdx + 1}/${matches.length}` : query ? "0" : "" }),
1961
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { onClick: goPrev, title: "Anterior (Shift+Enter)", children: "\u25B2" }),
1962
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { onClick: goNext, title: "Siguiente (Enter)", children: "\u25BC" }),
1963
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1964
+ "button",
1965
+ {
1966
+ onClick: () => setCaseSensitive((v) => !v),
1967
+ title: "Aa: Case sensitive",
1968
+ style: { fontWeight: caseSensitive ? 700 : 400 },
1969
+ children: "Aa"
1970
+ }
1971
+ ),
1972
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { onClick: () => setShowReplace((v) => !v), title: "Reemplazar (Ctrl+H)", children: [
1973
+ showReplace ? "\u25BE" : "\u25B8",
1974
+ " Reemplazar"
1975
+ ] }),
1976
+ showReplace && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1977
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1978
+ "input",
1979
+ {
1980
+ type: "text",
1981
+ value: replacement,
1982
+ onChange: (e) => setReplacement(e.target.value),
1983
+ placeholder: "Reemplazar con..."
1984
+ }
1985
+ ),
1986
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { onClick: replaceCurrent, title: "Reemplazar actual", children: "1" }),
1987
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { onClick: replaceAll, title: "Reemplazar todos", children: "\u2200" })
1988
+ ] }),
1989
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { onClick: onClose, title: "Cerrar (Esc)", children: "\u2715" })
1990
+ ] });
1991
+ };
1992
+
1993
+ // src/components/SciNotebook.tsx
1994
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1995
+ var SciNotebook = ({
1996
+ notebook: initialNotebook,
1997
+ engine: providedEngine,
1998
+ plugins,
1999
+ initialContent,
2000
+ theme = "light",
2001
+ onChange,
2002
+ onCellFocus,
2003
+ readOnly = false,
2004
+ showToolbar = true,
2005
+ className,
2006
+ style,
2007
+ engineRef,
2008
+ showTOC: showTOCProp = false
2009
+ }) => {
2010
+ const engine = (0, import_react13.useMemo)(() => {
2011
+ if (providedEngine) return providedEngine;
2012
+ if (initialContent && !initialNotebook) {
2013
+ const cells2 = parseInitialContent(initialContent);
2014
+ return (0, import_notebook_core.createNotebook)({
2015
+ notebook: {
2016
+ id: "",
2017
+ title: "Untitled",
2018
+ cells: cells2,
2019
+ metadata: {},
2020
+ version: 1,
2021
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2022
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2023
+ },
2024
+ config: { plugins }
2025
+ });
2026
+ }
2027
+ return (0, import_notebook_core.createNotebook)({ notebook: initialNotebook, config: { plugins } });
2028
+ }, [providedEngine]);
2029
+ (0, import_react13.useEffect)(() => {
2030
+ if (engineRef) engineRef.current = engine;
2031
+ return () => {
2032
+ if (engineRef) engineRef.current = null;
2033
+ };
2034
+ }, [engine, engineRef]);
2035
+ const [notebook, setNotebook] = (0, import_react13.useState)(initialNotebook || engine.getNotebook());
2036
+ (0, import_react13.useEffect)(() => {
2037
+ const unsub = engine.on("notebook:updated", (payload) => {
2038
+ setNotebook(payload.data.notebook);
2039
+ if (onChange) onChange(payload.data.notebook);
2040
+ });
2041
+ return unsub;
2042
+ }, [engine, onChange]);
2043
+ (0, import_react13.useEffect)(() => {
2044
+ if (!onCellFocus) return;
2045
+ return engine.on("cell:focused", (payload) => {
2046
+ onCellFocus(payload.data.cellId);
2047
+ });
2048
+ }, [engine, onCellFocus]);
2049
+ const pipeline = (0, import_react13.useMemo)(() => new import_notebook_renderer.RenderPipeline(), []);
2050
+ const cells = notebook.cells;
2051
+ const [showFind, setShowFind] = (0, import_react13.useState)(false);
2052
+ const [showTOC, setShowTOC] = (0, import_react13.useState)(showTOCProp);
2053
+ const [focusedCellId, setFocusedCellId] = (0, import_react13.useState)(null);
2054
+ (0, import_react13.useEffect)(() => {
2055
+ return engine.on("cell:focused", (payload) => {
2056
+ setFocusedCellId(payload.data.cellId);
2057
+ });
2058
+ }, [engine]);
2059
+ const handleGlobalKeyDown = (0, import_react13.useCallback)((e) => {
2060
+ if (readOnly) return;
2061
+ if ((e.ctrlKey || e.metaKey) && e.key === "f") {
2062
+ e.preventDefault();
2063
+ setShowFind((v) => !v);
2064
+ return;
2065
+ }
2066
+ engine.handleKeyDown(e.nativeEvent);
2067
+ }, [engine, readOnly]);
2068
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NotebookContext.Provider, { value: engine, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2069
+ "div",
2070
+ {
2071
+ className: `sci-nb ${className || ""}`,
2072
+ style,
2073
+ "data-theme": theme,
2074
+ onKeyDown: handleGlobalKeyDown,
2075
+ tabIndex: 0,
2076
+ children: [
2077
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "sci-nb-toolbar", children: [
2078
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "sci-nb-toolbar-group", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "sci-nb-toolbar-title", children: notebook.title }) }),
2079
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "sci-nb-toolbar-group", children: [
2080
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2081
+ "button",
2082
+ {
2083
+ className: "sci-nb-toolbar-btn",
2084
+ onClick: () => engine.undo(),
2085
+ disabled: !engine.canUndo(),
2086
+ title: "Undo (Ctrl+Z)",
2087
+ children: [
2088
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M3 7h6a3 3 0 010 6H7M3 7l3-3M3 7l3 3", strokeLinecap: "round", strokeLinejoin: "round" }) }),
2089
+ "Undo"
2090
+ ]
2091
+ }
2092
+ ),
2093
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2094
+ "button",
2095
+ {
2096
+ className: "sci-nb-toolbar-btn",
2097
+ onClick: () => engine.redo(),
2098
+ disabled: !engine.canRedo(),
2099
+ title: "Redo (Ctrl+Shift+Z)",
2100
+ children: [
2101
+ "Redo",
2102
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M11 7H5a3 3 0 000 6h2M11 7l-3-3M11 7l-3 3", strokeLinecap: "round", strokeLinejoin: "round" }) })
2103
+ ]
2104
+ }
2105
+ ),
2106
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "sci-nb-toolbar-sep" }),
2107
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2108
+ "button",
2109
+ {
2110
+ className: "sci-nb-toolbar-btn",
2111
+ onClick: () => engine.setAllEditMode(),
2112
+ title: "Edit all cells",
2113
+ children: "Edit All"
2114
+ }
2115
+ ),
2116
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2117
+ "button",
2118
+ {
2119
+ className: "sci-nb-toolbar-btn",
2120
+ onClick: () => engine.setAllViewMode(),
2121
+ title: "Preview all cells",
2122
+ children: "View All"
2123
+ }
2124
+ ),
2125
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "sci-nb-toolbar-sep" }),
2126
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2127
+ "button",
2128
+ {
2129
+ className: "sci-nb-toolbar-btn",
2130
+ onClick: () => setShowFind((v) => !v),
2131
+ title: "Find & Replace (Ctrl+F)",
2132
+ children: "Buscar"
2133
+ }
2134
+ ),
2135
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2136
+ "button",
2137
+ {
2138
+ className: `sci-nb-toolbar-btn ${showTOC ? "sci-nb-toolbar-btn--active" : ""}`,
2139
+ onClick: () => setShowTOC((v) => !v),
2140
+ title: "Table of Contents",
2141
+ children: "TOC"
2142
+ }
2143
+ )
2144
+ ] })
2145
+ ] }),
2146
+ showFind && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FindReplace, { onClose: () => setShowFind(false) }),
2147
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "sci-nb-layout", style: { display: "flex", gap: 16 }, children: [
2148
+ showTOC && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TOCSidebar, { focusedCellId }),
2149
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "sci-nb-cells", style: { flex: 1 }, children: [
2150
+ cells.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "sci-nb-empty", children: [
2151
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "sci-nb-empty-icon", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("svg", { width: "48", height: "48", viewBox: "0 0 48 48", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
2152
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("rect", { x: "8", y: "6", width: "32", height: "36", rx: "4" }),
2153
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("line", { x1: "14", y1: "14", x2: "34", y2: "14" }),
2154
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("line", { x1: "14", y1: "22", x2: "28", y2: "22" }),
2155
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("line", { x1: "14", y1: "30", x2: "22", y2: "30" })
2156
+ ] }) }),
2157
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: "Notebook vacio. Agrega una celda para comenzar." }),
2158
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InsertHandle, { index: 0 })
2159
+ ] }),
2160
+ cells.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InsertHandle, { index: 0 }),
2161
+ cells.map((cell, idx) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react13.default.Fragment, { children: [
2162
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2163
+ Cell,
2164
+ {
2165
+ cellId: cell.id,
2166
+ pipeline,
2167
+ index: idx,
2168
+ totalCells: cells.length
2169
+ }
2170
+ ),
2171
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InsertHandle, { index: idx + 1 })
2172
+ ] }, cell.id))
2173
+ ] })
2174
+ ] })
2175
+ ]
2176
+ }
2177
+ ) });
2178
+ };
2179
+ function parseInitialContent(content) {
2180
+ const blocks = content.split(/\n---\n/).filter((b) => b.trim());
2181
+ if (blocks.length === 0) {
2182
+ return [{ id: `cell_${Date.now()}`, type: "markdown", source: content, metadata: {} }];
2183
+ }
2184
+ return blocks.map((block, i) => ({
2185
+ id: `cell_${Date.now()}_${i}`,
2186
+ type: "markdown",
2187
+ source: block.trim(),
2188
+ metadata: {}
2189
+ }));
2190
+ }
2191
+
2192
+ // src/components/LatexAutocomplete.tsx
2193
+ var import_react14 = require("react");
2194
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2195
+ var LATEX_COMMANDS = [
2196
+ // Structures
2197
+ { cmd: "\\frac{}{}", desc: "Fracci\xF3n", category: "struct" },
2198
+ { cmd: "\\sqrt{}", desc: "Ra\xEDz cuadrada", category: "struct" },
2199
+ { cmd: "\\sqrt[]{}", desc: "Ra\xEDz n-\xE9sima", category: "struct" },
2200
+ { cmd: "^{}", desc: "Super\xEDndice", category: "struct" },
2201
+ { cmd: "_{}", desc: "Sub\xEDndice", category: "struct" },
2202
+ { cmd: "\\hat{}", desc: "Hat", category: "struct" },
2203
+ { cmd: "\\bar{}", desc: "Bar", category: "struct" },
2204
+ { cmd: "\\vec{}", desc: "Vector", category: "struct" },
2205
+ { cmd: "\\tilde{}", desc: "Tilde", category: "struct" },
2206
+ { cmd: "\\dot{}", desc: "Dot", category: "struct" },
2207
+ { cmd: "\\ddot{}", desc: "Double dot", category: "struct" },
2208
+ { cmd: "\\overline{}", desc: "Overline", category: "struct" },
2209
+ { cmd: "\\underline{}", desc: "Underline", category: "struct" },
2210
+ { cmd: "\\overbrace{}", desc: "Overbrace", category: "struct" },
2211
+ { cmd: "\\underbrace{}", desc: "Underbrace", category: "struct" },
2212
+ // Integrals
2213
+ { cmd: "\\int", desc: "Integral", category: "calc" },
2214
+ { cmd: "\\int_{}^{}", desc: "Integral definida", category: "calc" },
2215
+ { cmd: "\\iint", desc: "Integral doble", category: "calc" },
2216
+ { cmd: "\\iiint", desc: "Integral triple", category: "calc" },
2217
+ { cmd: "\\oint", desc: "Integral de contorno", category: "calc" },
2218
+ { cmd: "\\sum_{}^{}", desc: "Sumatoria", category: "calc" },
2219
+ { cmd: "\\prod_{}^{}", desc: "Productoria", category: "calc" },
2220
+ { cmd: "\\lim_{\\to}", desc: "L\xEDmite", category: "calc" },
2221
+ { cmd: "\\partial", desc: "Derivada parcial", category: "calc" },
2222
+ { cmd: "\\nabla", desc: "Nabla", category: "calc" },
2223
+ { cmd: "\\infty", desc: "Infinito", category: "calc" },
2224
+ // Greek
2225
+ { cmd: "\\alpha", desc: "\u03B1", category: "greek" },
2226
+ { cmd: "\\beta", desc: "\u03B2", category: "greek" },
2227
+ { cmd: "\\gamma", desc: "\u03B3", category: "greek" },
2228
+ { cmd: "\\delta", desc: "\u03B4", category: "greek" },
2229
+ { cmd: "\\epsilon", desc: "\u03B5", category: "greek" },
2230
+ { cmd: "\\zeta", desc: "\u03B6", category: "greek" },
2231
+ { cmd: "\\eta", desc: "\u03B7", category: "greek" },
2232
+ { cmd: "\\theta", desc: "\u03B8", category: "greek" },
2233
+ { cmd: "\\iota", desc: "\u03B9", category: "greek" },
2234
+ { cmd: "\\kappa", desc: "\u03BA", category: "greek" },
2235
+ { cmd: "\\lambda", desc: "\u03BB", category: "greek" },
2236
+ { cmd: "\\mu", desc: "\u03BC", category: "greek" },
2237
+ { cmd: "\\nu", desc: "\u03BD", category: "greek" },
2238
+ { cmd: "\\xi", desc: "\u03BE", category: "greek" },
2239
+ { cmd: "\\pi", desc: "\u03C0", category: "greek" },
2240
+ { cmd: "\\rho", desc: "\u03C1", category: "greek" },
2241
+ { cmd: "\\sigma", desc: "\u03C3", category: "greek" },
2242
+ { cmd: "\\tau", desc: "\u03C4", category: "greek" },
2243
+ { cmd: "\\upsilon", desc: "\u03C5", category: "greek" },
2244
+ { cmd: "\\phi", desc: "\u03C6", category: "greek" },
2245
+ { cmd: "\\chi", desc: "\u03C7", category: "greek" },
2246
+ { cmd: "\\psi", desc: "\u03C8", category: "greek" },
2247
+ { cmd: "\\omega", desc: "\u03C9", category: "greek" },
2248
+ { cmd: "\\Gamma", desc: "\u0393", category: "greek" },
2249
+ { cmd: "\\Delta", desc: "\u0394", category: "greek" },
2250
+ { cmd: "\\Theta", desc: "\u0398", category: "greek" },
2251
+ { cmd: "\\Lambda", desc: "\u039B", category: "greek" },
2252
+ { cmd: "\\Sigma", desc: "\u03A3", category: "greek" },
2253
+ { cmd: "\\Phi", desc: "\u03A6", category: "greek" },
2254
+ { cmd: "\\Psi", desc: "\u03A8", category: "greek" },
2255
+ { cmd: "\\Omega", desc: "\u03A9", category: "greek" },
2256
+ // Operators
2257
+ { cmd: "\\pm", desc: "\xB1", category: "op" },
2258
+ { cmd: "\\mp", desc: "\u2213", category: "op" },
2259
+ { cmd: "\\times", desc: "\xD7", category: "op" },
2260
+ { cmd: "\\div", desc: "\xF7", category: "op" },
2261
+ { cmd: "\\cdot", desc: "\xB7", category: "op" },
2262
+ { cmd: "\\leq", desc: "\u2264", category: "op" },
2263
+ { cmd: "\\geq", desc: "\u2265", category: "op" },
2264
+ { cmd: "\\neq", desc: "\u2260", category: "op" },
2265
+ { cmd: "\\approx", desc: "\u2248", category: "op" },
2266
+ { cmd: "\\equiv", desc: "\u2261", category: "op" },
2267
+ { cmd: "\\in", desc: "\u2208", category: "op" },
2268
+ { cmd: "\\notin", desc: "\u2209", category: "op" },
2269
+ { cmd: "\\subset", desc: "\u2282", category: "op" },
2270
+ { cmd: "\\supset", desc: "\u2283", category: "op" },
2271
+ { cmd: "\\cup", desc: "\u222A", category: "op" },
2272
+ { cmd: "\\cap", desc: "\u2229", category: "op" },
2273
+ { cmd: "\\forall", desc: "\u2200", category: "op" },
2274
+ { cmd: "\\exists", desc: "\u2203", category: "op" },
2275
+ // Arrows
2276
+ { cmd: "\\rightarrow", desc: "\u2192", category: "arrow" },
2277
+ { cmd: "\\leftarrow", desc: "\u2190", category: "arrow" },
2278
+ { cmd: "\\leftrightarrow", desc: "\u2194", category: "arrow" },
2279
+ { cmd: "\\Rightarrow", desc: "\u21D2", category: "arrow" },
2280
+ { cmd: "\\Leftarrow", desc: "\u21D0", category: "arrow" },
2281
+ { cmd: "\\Leftrightarrow", desc: "\u21D4", category: "arrow" },
2282
+ { cmd: "\\mapsto", desc: "\u21A6", category: "arrow" },
2283
+ // Functions
2284
+ { cmd: "\\sin", desc: "sin", category: "func" },
2285
+ { cmd: "\\cos", desc: "cos", category: "func" },
2286
+ { cmd: "\\tan", desc: "tan", category: "func" },
2287
+ { cmd: "\\log", desc: "log", category: "func" },
2288
+ { cmd: "\\ln", desc: "ln", category: "func" },
2289
+ { cmd: "\\exp", desc: "exp", category: "func" },
2290
+ { cmd: "\\max", desc: "max", category: "func" },
2291
+ { cmd: "\\min", desc: "min", category: "func" },
2292
+ { cmd: "\\det", desc: "det", category: "func" },
2293
+ // Environments
2294
+ { cmd: "\\begin{pmatrix}\\end{pmatrix}", desc: "Matriz ()", category: "env" },
2295
+ { cmd: "\\begin{bmatrix}\\end{bmatrix}", desc: "Matriz []", category: "env" },
2296
+ { cmd: "\\begin{vmatrix}\\end{vmatrix}", desc: "Determinante ||", category: "env" },
2297
+ { cmd: "\\begin{cases}\\end{cases}", desc: "Cases {", category: "env" },
2298
+ { cmd: "\\begin{aligned}\\end{aligned}", desc: "Aligned", category: "env" },
2299
+ // Delimiters
2300
+ { cmd: "\\left(\\right)", desc: "Par\xE9ntesis auto", category: "delim" },
2301
+ { cmd: "\\left[\\right]", desc: "Corchetes auto", category: "delim" },
2302
+ { cmd: "\\left\\{\\right\\}", desc: "Llaves auto", category: "delim" },
2303
+ { cmd: "\\left|\\right|", desc: "Valor absoluto", category: "delim" },
2304
+ { cmd: "\\lfloor\\rfloor", desc: "Piso", category: "delim" },
2305
+ { cmd: "\\lceil\\rceil", desc: "Techo", category: "delim" },
2306
+ // Misc
2307
+ { cmd: "\\text{}", desc: "Texto", category: "misc" },
2308
+ { cmd: "\\mathbb{}", desc: "Blackboard bold", category: "misc" },
2309
+ { cmd: "\\mathcal{}", desc: "Caligr\xE1fico", category: "misc" },
2310
+ { cmd: "\\mathrm{}", desc: "Roman", category: "misc" },
2311
+ { cmd: "\\mathbf{}", desc: "Bold", category: "misc" },
2312
+ { cmd: "\\quad", desc: "Espacio grande", category: "misc" },
2313
+ { cmd: "\\qquad", desc: "Espacio doble", category: "misc" },
2314
+ { cmd: "\\,", desc: "Espacio fino", category: "misc" },
2315
+ { cmd: "\\ldots", desc: "Puntos ...", category: "misc" },
2316
+ { cmd: "\\cdots", desc: "Puntos centrados", category: "misc" },
2317
+ { cmd: "\\vdots", desc: "Puntos verticales", category: "misc" },
2318
+ { cmd: "\\ddots", desc: "Puntos diagonales", category: "misc" }
2319
+ ];
2320
+ var LatexAutocomplete = ({
2321
+ query,
2322
+ position,
2323
+ onSelect,
2324
+ onClose
2325
+ }) => {
2326
+ const [selectedIndex, setSelectedIndex] = (0, import_react14.useState)(0);
2327
+ const menuRef = (0, import_react14.useRef)(null);
2328
+ const filtered = (0, import_react14.useMemo)(() => {
2329
+ if (!query) return LATEX_COMMANDS.slice(0, 12);
2330
+ const q = query.toLowerCase();
2331
+ return LATEX_COMMANDS.filter(
2332
+ (c) => c.cmd.toLowerCase().includes(q) || c.desc.toLowerCase().includes(q)
2333
+ ).slice(0, 10);
2334
+ }, [query]);
2335
+ (0, import_react14.useEffect)(() => {
2336
+ setSelectedIndex(0);
2337
+ }, [query]);
2338
+ (0, import_react14.useEffect)(() => {
2339
+ const handleKey = (e) => {
2340
+ if (e.key === "ArrowDown") {
2341
+ e.preventDefault();
2342
+ e.stopPropagation();
2343
+ setSelectedIndex((i) => (i + 1) % Math.max(filtered.length, 1));
2344
+ } else if (e.key === "ArrowUp") {
2345
+ e.preventDefault();
2346
+ e.stopPropagation();
2347
+ setSelectedIndex((i) => (i - 1 + filtered.length) % Math.max(filtered.length, 1));
2348
+ } else if (e.key === "Enter" || e.key === "Tab") {
2349
+ if (filtered.length > 0) {
2350
+ e.preventDefault();
2351
+ e.stopPropagation();
2352
+ onSelect(filtered[selectedIndex]?.cmd || "");
2353
+ }
2354
+ } else if (e.key === "Escape") {
2355
+ e.preventDefault();
2356
+ onClose();
2357
+ }
2358
+ };
2359
+ document.addEventListener("keydown", handleKey, true);
2360
+ return () => document.removeEventListener("keydown", handleKey, true);
2361
+ }, [filtered, selectedIndex, onSelect, onClose]);
2362
+ if (filtered.length === 0) return null;
2363
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2364
+ "div",
2365
+ {
2366
+ ref: menuRef,
2367
+ className: "sci-nb-slash-menu",
2368
+ style: { top: position.top, left: position.left, minWidth: 220 },
2369
+ children: filtered.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2370
+ "button",
2371
+ {
2372
+ className: `sci-nb-slash-item ${i === selectedIndex ? "sci-nb-slash-item--active" : ""}`,
2373
+ onMouseEnter: () => setSelectedIndex(i),
2374
+ onClick: () => onSelect(item.cmd),
2375
+ children: [
2376
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "sci-nb-slash-icon", style: { fontFamily: "monospace", fontSize: 11 }, children: item.desc.length <= 2 ? item.desc : item.cmd.slice(0, 4) }),
2377
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "sci-nb-slash-text", children: [
2378
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "sci-nb-slash-label", style: { fontFamily: "monospace", fontSize: 12 }, children: item.cmd }),
2379
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "sci-nb-slash-desc", children: item.desc })
2380
+ ] })
2381
+ ]
2382
+ },
2383
+ item.cmd
2384
+ ))
2385
+ }
2386
+ );
2387
+ };
2388
+
2389
+ // src/components/GhostText.tsx
2390
+ var import_react15 = require("react");
2391
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2392
+ var GhostText = ({
2393
+ text,
2394
+ textareaRef,
2395
+ onAccept,
2396
+ onDismiss
2397
+ }) => {
2398
+ const handleKeyDown = (0, import_react15.useCallback)((e) => {
2399
+ if (e.key === "Tab" && !e.shiftKey) {
2400
+ e.preventDefault();
2401
+ e.stopPropagation();
2402
+ onAccept();
2403
+ } else if (e.key === "Escape") {
2404
+ e.preventDefault();
2405
+ onDismiss();
2406
+ }
2407
+ }, [onAccept, onDismiss]);
2408
+ (0, import_react15.useEffect)(() => {
2409
+ const ta2 = textareaRef.current;
2410
+ if (!ta2) return;
2411
+ ta2.addEventListener("keydown", handleKeyDown, true);
2412
+ return () => ta2.removeEventListener("keydown", handleKeyDown, true);
2413
+ }, [textareaRef, handleKeyDown]);
2414
+ if (!text) return null;
2415
+ const ta = textareaRef.current;
2416
+ if (!ta) return null;
2417
+ const cursorPos = ta.selectionStart;
2418
+ const before = ta.value.slice(0, cursorPos);
2419
+ const lines = before.split("\n");
2420
+ const lineHeight = 22;
2421
+ const charWidth = 7.8;
2422
+ const top = (lines.length - 1) * lineHeight;
2423
+ const left = lines[lines.length - 1].length * charWidth;
2424
+ const firstLine = text.split("\n")[0];
2425
+ const hasMore = text.includes("\n");
2426
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2427
+ "div",
2428
+ {
2429
+ className: "sci-nb-ghost-text",
2430
+ style: {
2431
+ position: "absolute",
2432
+ top: `${top + 10}px`,
2433
+ left: `${left + 12}px`,
2434
+ pointerEvents: "none",
2435
+ whiteSpace: "pre",
2436
+ fontFamily: "inherit",
2437
+ fontSize: "inherit",
2438
+ lineHeight: `${lineHeight}px`,
2439
+ zIndex: 5
2440
+ },
2441
+ children: [
2442
+ firstLine,
2443
+ hasMore ? "..." : "",
2444
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: { fontSize: 10, opacity: 0.5, marginLeft: 8 }, children: "Tab \u21B9" })
2445
+ ]
2446
+ }
2447
+ );
2448
+ };
2449
+
2450
+ // src/components/ChatSidebar.tsx
2451
+ var import_react16 = require("react");
2452
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2453
+ var ChatSidebar = ({
2454
+ onSend,
2455
+ systemPrompt,
2456
+ onApply,
2457
+ onClose
2458
+ }) => {
2459
+ const engine = useSciNotebook();
2460
+ const [messages, setMessages] = (0, import_react16.useState)(() => {
2461
+ if (systemPrompt) {
2462
+ return [{ role: "system", content: systemPrompt, timestamp: Date.now() }];
2463
+ }
2464
+ return [];
2465
+ });
2466
+ const [input, setInput] = (0, import_react16.useState)("");
2467
+ const [loading, setLoading] = (0, import_react16.useState)(false);
2468
+ const messagesEndRef = (0, import_react16.useRef)(null);
2469
+ const inputRef = (0, import_react16.useRef)(null);
2470
+ (0, import_react16.useEffect)(() => {
2471
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2472
+ }, [messages]);
2473
+ (0, import_react16.useEffect)(() => {
2474
+ inputRef.current?.focus();
2475
+ }, []);
2476
+ const handleSend = (0, import_react16.useCallback)(async () => {
2477
+ const text = input.trim();
2478
+ if (!text || loading) return;
2479
+ const userMsg = { role: "user", content: text, timestamp: Date.now() };
2480
+ const newMessages = [...messages, userMsg];
2481
+ setMessages(newMessages);
2482
+ setInput("");
2483
+ setLoading(true);
2484
+ try {
2485
+ if (onSend) {
2486
+ const response = await onSend(text, newMessages);
2487
+ setMessages((prev) => [
2488
+ ...prev,
2489
+ { role: "assistant", content: response, timestamp: Date.now() }
2490
+ ]);
2491
+ } else {
2492
+ setMessages((prev) => [
2493
+ ...prev,
2494
+ {
2495
+ role: "assistant",
2496
+ content: "No AI provider configured. Pass an `onSend` prop to enable AI chat.",
2497
+ timestamp: Date.now()
2498
+ }
2499
+ ]);
2500
+ }
2501
+ } catch (e) {
2502
+ setMessages((prev) => [
2503
+ ...prev,
2504
+ {
2505
+ role: "assistant",
2506
+ content: `Error: ${e.message || "Failed to get response"}`,
2507
+ timestamp: Date.now()
2508
+ }
2509
+ ]);
2510
+ } finally {
2511
+ setLoading(false);
2512
+ }
2513
+ }, [input, messages, loading, onSend]);
2514
+ const handleKeyDown = (0, import_react16.useCallback)((e) => {
2515
+ if (e.key === "Enter" && !e.shiftKey) {
2516
+ e.preventDefault();
2517
+ handleSend();
2518
+ }
2519
+ }, [handleSend]);
2520
+ const visibleMessages = messages.filter((m) => m.role !== "system");
2521
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "sci-nb-chat-sidebar", children: [
2522
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "sci-nb-chat-header", children: [
2523
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "AI Assistant" }),
2524
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2525
+ "button",
2526
+ {
2527
+ onClick: onClose,
2528
+ style: { border: "none", background: "transparent", cursor: "pointer", fontSize: 16 },
2529
+ children: "\u2715"
2530
+ }
2531
+ )
2532
+ ] }),
2533
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "sci-nb-chat-messages", children: [
2534
+ visibleMessages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { textAlign: "center", color: "#94a3b8", padding: 24 }, children: "Ask me anything about your notebook..." }),
2535
+ visibleMessages.map((msg, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `sci-nb-chat-msg sci-nb-chat-msg--${msg.role}`, children: [
2536
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { children: msg.content }),
2537
+ msg.role === "assistant" && onApply && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2538
+ "button",
2539
+ {
2540
+ onClick: () => onApply(msg.content),
2541
+ style: {
2542
+ marginTop: 4,
2543
+ fontSize: 11,
2544
+ padding: "2px 6px",
2545
+ border: "1px solid #e2e8f0",
2546
+ borderRadius: 4,
2547
+ background: "transparent",
2548
+ cursor: "pointer"
2549
+ },
2550
+ children: "Apply to cell"
2551
+ }
2552
+ )
2553
+ ] }, i)),
2554
+ loading && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "sci-nb-chat-msg sci-nb-chat-msg--assistant", style: { opacity: 0.6 }, children: "Thinking..." }),
2555
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: messagesEndRef })
2556
+ ] }),
2557
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "sci-nb-chat-input", children: [
2558
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2559
+ "input",
2560
+ {
2561
+ ref: inputRef,
2562
+ type: "text",
2563
+ value: input,
2564
+ onChange: (e) => setInput(e.target.value),
2565
+ onKeyDown: handleKeyDown,
2566
+ placeholder: "Ask something...",
2567
+ disabled: loading
2568
+ }
2569
+ ),
2570
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { onClick: handleSend, disabled: loading || !input.trim(), children: "Send" })
2571
+ ] })
2572
+ ] });
2573
+ };
2574
+
2575
+ // src/components/ImageResize.tsx
2576
+ var import_react17 = require("react");
2577
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2578
+ var ImageResize = ({
2579
+ src,
2580
+ alt = "",
2581
+ initialWidth,
2582
+ maxWidth = "100%",
2583
+ onResize,
2584
+ children
2585
+ }) => {
2586
+ const containerRef = (0, import_react17.useRef)(null);
2587
+ const imgRef = (0, import_react17.useRef)(null);
2588
+ const [dragging, setDragging] = (0, import_react17.useState)(false);
2589
+ const [width, setWidth] = (0, import_react17.useState)(null);
2590
+ const startRef = (0, import_react17.useRef)({ x: 0, w: 0 });
2591
+ const handleMouseDown = (0, import_react17.useCallback)((e) => {
2592
+ e.preventDefault();
2593
+ e.stopPropagation();
2594
+ const img = imgRef.current;
2595
+ if (!img) return;
2596
+ startRef.current = { x: e.clientX, w: img.offsetWidth };
2597
+ setDragging(true);
2598
+ }, []);
2599
+ (0, import_react17.useEffect)(() => {
2600
+ if (!dragging) return;
2601
+ const handleMouseMove = (e) => {
2602
+ const dx = e.clientX - startRef.current.x;
2603
+ const newW = Math.max(50, startRef.current.w + dx);
2604
+ setWidth(newW);
2605
+ };
2606
+ const handleMouseUp = () => {
2607
+ setDragging(false);
2608
+ if (width !== null && containerRef.current) {
2609
+ const parentW = containerRef.current.parentElement?.offsetWidth || 1;
2610
+ const pct = Math.round(width / parentW * 100);
2611
+ onResize(`${Math.min(pct, 100)}%`);
2612
+ }
2613
+ };
2614
+ document.addEventListener("mousemove", handleMouseMove);
2615
+ document.addEventListener("mouseup", handleMouseUp);
2616
+ return () => {
2617
+ document.removeEventListener("mousemove", handleMouseMove);
2618
+ document.removeEventListener("mouseup", handleMouseUp);
2619
+ };
2620
+ }, [dragging, width, onResize]);
2621
+ const style = {
2622
+ maxWidth,
2623
+ width: width !== null ? `${width}px` : initialWidth,
2624
+ position: "relative",
2625
+ display: "inline-block"
2626
+ };
2627
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: containerRef, className: "sci-nb-image-resizable", style, children: [
2628
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2629
+ "img",
2630
+ {
2631
+ ref: imgRef,
2632
+ src,
2633
+ alt,
2634
+ style: { width: "100%", height: "auto", display: "block" },
2635
+ draggable: false
2636
+ }
2637
+ ),
2638
+ children,
2639
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2640
+ "div",
2641
+ {
2642
+ className: "sci-nb-image-resize-handle sci-nb-image-resize-handle--se",
2643
+ onMouseDown: handleMouseDown
2644
+ }
2645
+ )
2646
+ ] });
2647
+ };
2648
+
2649
+ // src/components/VirtualRenderer.tsx
2650
+ var import_react18 = require("react");
2651
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2652
+ var VirtualRenderer = ({
2653
+ cells,
2654
+ pipeline,
2655
+ estimatedHeight = 120,
2656
+ overscan = 5
2657
+ }) => {
2658
+ const containerRef = (0, import_react18.useRef)(null);
2659
+ const [visibleRange, setVisibleRange] = (0, import_react18.useState)({ start: 0, end: 20 });
2660
+ const cellHeights = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
2661
+ const getHeight = (0, import_react18.useCallback)((index) => {
2662
+ return cellHeights.current.get(index) ?? estimatedHeight;
2663
+ }, [estimatedHeight]);
2664
+ const totalHeight = (0, import_react18.useMemo)(() => {
2665
+ let h = 0;
2666
+ for (let i = 0; i < cells.length; i++) {
2667
+ h += getHeight(i) + 32;
2668
+ }
2669
+ return h;
2670
+ }, [cells.length, getHeight]);
2671
+ (0, import_react18.useEffect)(() => {
2672
+ const container = containerRef.current;
2673
+ if (!container) return;
2674
+ const handleScroll = () => {
2675
+ const scrollTop = container.scrollTop;
2676
+ const viewportHeight = container.clientHeight;
2677
+ let accum = 0;
2678
+ let start = 0;
2679
+ let end = cells.length;
2680
+ for (let i = 0; i < cells.length; i++) {
2681
+ const h = getHeight(i) + 32;
2682
+ if (accum + h >= scrollTop && start === 0) {
2683
+ start = Math.max(0, i - overscan);
2684
+ }
2685
+ if (accum > scrollTop + viewportHeight) {
2686
+ end = Math.min(cells.length, i + overscan);
2687
+ break;
2688
+ }
2689
+ accum += h;
2690
+ }
2691
+ setVisibleRange((prev) => {
2692
+ if (prev.start === start && prev.end === end) return prev;
2693
+ return { start, end };
2694
+ });
2695
+ };
2696
+ handleScroll();
2697
+ container.addEventListener("scroll", handleScroll, { passive: true });
2698
+ return () => container.removeEventListener("scroll", handleScroll);
2699
+ }, [cells.length, getHeight, overscan]);
2700
+ const measureRef = (0, import_react18.useCallback)((index) => {
2701
+ return (el) => {
2702
+ if (!el) return;
2703
+ const observer = new ResizeObserver((entries) => {
2704
+ for (const entry of entries) {
2705
+ const h = entry.contentRect.height;
2706
+ if (h > 0 && cellHeights.current.get(index) !== h) {
2707
+ cellHeights.current.set(index, h);
2708
+ }
2709
+ }
2710
+ });
2711
+ observer.observe(el);
2712
+ };
2713
+ }, []);
2714
+ const topOffset = (0, import_react18.useMemo)(() => {
2715
+ let h = 0;
2716
+ for (let i = 0; i < visibleRange.start; i++) {
2717
+ h += getHeight(i) + 32;
2718
+ }
2719
+ return h;
2720
+ }, [visibleRange.start, getHeight]);
2721
+ const visibleCells = cells.slice(visibleRange.start, visibleRange.end);
2722
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2723
+ "div",
2724
+ {
2725
+ ref: containerRef,
2726
+ className: "sci-nb-virtual-container",
2727
+ style: { height: "100%", overflow: "auto", position: "relative" },
2728
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { height: totalHeight, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { position: "absolute", top: topOffset, left: 0, right: 0 }, children: [
2729
+ visibleRange.start === 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(InsertHandle, { index: 0 }),
2730
+ visibleCells.map((cell, i) => {
2731
+ const realIndex = visibleRange.start + i;
2732
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { ref: measureRef(realIndex), children: [
2733
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2734
+ Cell,
2735
+ {
2736
+ cellId: cell.id,
2737
+ pipeline,
2738
+ index: realIndex,
2739
+ totalCells: cells.length
2740
+ }
2741
+ ),
2742
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(InsertHandle, { index: realIndex + 1 })
2743
+ ] }, cell.id);
2744
+ })
2745
+ ] }) })
2746
+ }
2747
+ );
2748
+ };
2749
+
2750
+ // src/components/AIRewrite.tsx
2751
+ var import_react19 = require("react");
2752
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2753
+ var AIRewrite = ({
2754
+ selectedText,
2755
+ position,
2756
+ onRewrite,
2757
+ onAccept,
2758
+ onReject
2759
+ }) => {
2760
+ const [state, setState] = (0, import_react19.useState)("prompt");
2761
+ const [instruction, setInstruction] = (0, import_react19.useState)("");
2762
+ const [result, setResult] = (0, import_react19.useState)("");
2763
+ const [error, setError] = (0, import_react19.useState)(null);
2764
+ const inputRef = (0, import_react19.useRef)(null);
2765
+ (0, import_react19.useEffect)(() => {
2766
+ inputRef.current?.focus();
2767
+ }, []);
2768
+ const handleSubmit = (0, import_react19.useCallback)(async () => {
2769
+ if (!instruction.trim()) return;
2770
+ setState("loading");
2771
+ setError(null);
2772
+ try {
2773
+ const rewritten = await onRewrite(instruction, selectedText);
2774
+ setResult(rewritten);
2775
+ setState("preview");
2776
+ } catch (e) {
2777
+ setError(e.message || "Rewrite failed");
2778
+ setState("prompt");
2779
+ }
2780
+ }, [instruction, selectedText, onRewrite]);
2781
+ const handleKeyDown = (0, import_react19.useCallback)((e) => {
2782
+ if (e.key === "Enter" && !e.shiftKey) {
2783
+ e.preventDefault();
2784
+ handleSubmit();
2785
+ } else if (e.key === "Escape") {
2786
+ e.preventDefault();
2787
+ onReject();
2788
+ }
2789
+ }, [handleSubmit, onReject]);
2790
+ const handleAccept = (0, import_react19.useCallback)(() => {
2791
+ onAccept(result);
2792
+ }, [result, onAccept]);
2793
+ const handleRetry = (0, import_react19.useCallback)(() => {
2794
+ setState("prompt");
2795
+ setResult("");
2796
+ inputRef.current?.focus();
2797
+ }, []);
2798
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
2799
+ "div",
2800
+ {
2801
+ className: "sci-nb-ai-rewrite",
2802
+ style: {
2803
+ position: "absolute",
2804
+ top: position.top,
2805
+ left: position.left,
2806
+ zIndex: 100
2807
+ },
2808
+ children: [
2809
+ state === "prompt" && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "sci-nb-ai-rewrite-prompt", children: [
2810
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "sci-nb-ai-rewrite-selected", children: [
2811
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "sci-nb-ai-rewrite-label", children: "Selected:" }),
2812
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "sci-nb-ai-rewrite-text", children: selectedText.length > 80 ? selectedText.slice(0, 80) + "..." : selectedText })
2813
+ ] }),
2814
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "sci-nb-ai-rewrite-input-row", children: [
2815
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2816
+ "input",
2817
+ {
2818
+ ref: inputRef,
2819
+ type: "text",
2820
+ value: instruction,
2821
+ onChange: (e) => setInstruction(e.target.value),
2822
+ onKeyDown: handleKeyDown,
2823
+ placeholder: "How should I rewrite this?",
2824
+ className: "sci-nb-ai-rewrite-input"
2825
+ }
2826
+ ),
2827
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2828
+ "button",
2829
+ {
2830
+ onClick: handleSubmit,
2831
+ disabled: !instruction.trim(),
2832
+ className: "sci-nb-ai-rewrite-btn sci-nb-ai-rewrite-btn--primary",
2833
+ children: "Rewrite"
2834
+ }
2835
+ ),
2836
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { onClick: onReject, className: "sci-nb-ai-rewrite-btn", children: "Cancel" })
2837
+ ] }),
2838
+ error && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "sci-nb-ai-rewrite-error", children: error })
2839
+ ] }),
2840
+ state === "loading" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "sci-nb-ai-rewrite-loading", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Rewriting..." }) }),
2841
+ state === "preview" && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "sci-nb-ai-rewrite-preview", children: [
2842
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "sci-nb-ai-rewrite-diff", children: [
2843
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "sci-nb-ai-rewrite-diff-old", children: [
2844
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "sci-nb-ai-rewrite-diff-label", children: "Original:" }),
2845
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("pre", { children: selectedText })
2846
+ ] }),
2847
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "sci-nb-ai-rewrite-diff-new", children: [
2848
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "sci-nb-ai-rewrite-diff-label", children: "Rewritten:" }),
2849
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("pre", { children: result })
2850
+ ] })
2851
+ ] }),
2852
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "sci-nb-ai-rewrite-actions", children: [
2853
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2854
+ "button",
2855
+ {
2856
+ onClick: handleAccept,
2857
+ className: "sci-nb-ai-rewrite-btn sci-nb-ai-rewrite-btn--primary",
2858
+ children: "Accept"
2859
+ }
2860
+ ),
2861
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { onClick: handleRetry, className: "sci-nb-ai-rewrite-btn", children: "Retry" }),
2862
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { onClick: onReject, className: "sci-nb-ai-rewrite-btn", children: "Reject" })
2863
+ ] })
2864
+ ] })
2865
+ ]
2866
+ }
2867
+ );
2868
+ };
2869
+
2870
+ // src/components/AICellGenerate.tsx
2871
+ var import_react20 = require("react");
2872
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2873
+ var AICellGenerate = ({
2874
+ onGenerate,
2875
+ onAccept,
2876
+ onCancel
2877
+ }) => {
2878
+ const [state, setState] = (0, import_react20.useState)("prompt");
2879
+ const [prompt, setPrompt] = (0, import_react20.useState)("");
2880
+ const [cells, setCells] = (0, import_react20.useState)([]);
2881
+ const [error, setError] = (0, import_react20.useState)(null);
2882
+ const inputRef = (0, import_react20.useRef)(null);
2883
+ (0, import_react20.useEffect)(() => {
2884
+ inputRef.current?.focus();
2885
+ }, []);
2886
+ const handleGenerate = (0, import_react20.useCallback)(async () => {
2887
+ if (!prompt.trim()) return;
2888
+ setState("loading");
2889
+ setError(null);
2890
+ try {
2891
+ const generated = await onGenerate(prompt);
2892
+ setCells(generated);
2893
+ setState("preview");
2894
+ } catch (e) {
2895
+ setError(e.message || "Generation failed");
2896
+ setState("prompt");
2897
+ }
2898
+ }, [prompt, onGenerate]);
2899
+ const handleKeyDown = (0, import_react20.useCallback)((e) => {
2900
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
2901
+ e.preventDefault();
2902
+ handleGenerate();
2903
+ } else if (e.key === "Escape") {
2904
+ e.preventDefault();
2905
+ onCancel();
2906
+ }
2907
+ }, [handleGenerate, onCancel]);
2908
+ const handleAccept = (0, import_react20.useCallback)(() => {
2909
+ onAccept(cells);
2910
+ }, [cells, onAccept]);
2911
+ const handleRegenerate = (0, import_react20.useCallback)(() => {
2912
+ setState("prompt");
2913
+ setCells([]);
2914
+ inputRef.current?.focus();
2915
+ }, []);
2916
+ const CELL_TYPE_LABELS = {
2917
+ markdown: "Markdown",
2918
+ code: "Code",
2919
+ latex: "LaTeX",
2920
+ table: "Table",
2921
+ mermaid: "Mermaid",
2922
+ raw: "Raw"
2923
+ };
2924
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "sci-nb-ai-generate", children: [
2925
+ state === "prompt" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "sci-nb-ai-generate-prompt", children: [
2926
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "sci-nb-ai-generate-header", children: [
2927
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M8 1v14M1 8h14", strokeLinecap: "round" }) }),
2928
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: "Generate cells with AI" })
2929
+ ] }),
2930
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2931
+ "textarea",
2932
+ {
2933
+ ref: inputRef,
2934
+ value: prompt,
2935
+ onChange: (e) => setPrompt(e.target.value),
2936
+ onKeyDown: handleKeyDown,
2937
+ placeholder: "Describe what you want to generate...\ne.g. 'Create a markdown cell explaining Newton's second law with a LaTeX formula'",
2938
+ className: "sci-nb-ai-generate-textarea",
2939
+ rows: 3
2940
+ }
2941
+ ),
2942
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "sci-nb-ai-generate-actions", children: [
2943
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2944
+ "button",
2945
+ {
2946
+ onClick: handleGenerate,
2947
+ disabled: !prompt.trim(),
2948
+ className: "sci-nb-ai-rewrite-btn sci-nb-ai-rewrite-btn--primary",
2949
+ children: "Generate (Ctrl+Enter)"
2950
+ }
2951
+ ),
2952
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: onCancel, className: "sci-nb-ai-rewrite-btn", children: "Cancel" })
2953
+ ] }),
2954
+ error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "sci-nb-ai-rewrite-error", children: error })
2955
+ ] }),
2956
+ state === "loading" && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "sci-nb-ai-generate-loading", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: "Generating cells..." }) }),
2957
+ state === "preview" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "sci-nb-ai-generate-preview", children: [
2958
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "sci-nb-ai-generate-header", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { children: [
2959
+ "Generated ",
2960
+ cells.length,
2961
+ " cell",
2962
+ cells.length !== 1 ? "s" : ""
2963
+ ] }) }),
2964
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "sci-nb-ai-generate-cells", children: cells.map((cell, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "sci-nb-ai-generate-cell", children: [
2965
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "sci-nb-ai-generate-cell-badge", children: CELL_TYPE_LABELS[cell.type] || cell.type }),
2966
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("pre", { className: "sci-nb-ai-generate-cell-source", children: cell.source.length > 200 ? cell.source.slice(0, 200) + "..." : cell.source })
2967
+ ] }, i)) }),
2968
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "sci-nb-ai-generate-actions", children: [
2969
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2970
+ "button",
2971
+ {
2972
+ onClick: handleAccept,
2973
+ className: "sci-nb-ai-rewrite-btn sci-nb-ai-rewrite-btn--primary",
2974
+ children: [
2975
+ "Insert ",
2976
+ cells.length,
2977
+ " cell",
2978
+ cells.length !== 1 ? "s" : ""
2979
+ ]
2980
+ }
2981
+ ),
2982
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: handleRegenerate, className: "sci-nb-ai-rewrite-btn", children: "Regenerate" }),
2983
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: onCancel, className: "sci-nb-ai-rewrite-btn", children: "Cancel" })
2984
+ ] })
2985
+ ] })
2986
+ ] });
2987
+ };
2988
+ // Annotate the CommonJS export names for ESM import in node:
2989
+ 0 && (module.exports = {
2990
+ AICellGenerate,
2991
+ AIRewrite,
2992
+ Cell,
2993
+ CellOutputDisplay,
2994
+ ChatSidebar,
2995
+ DEFAULT_COMMANDS,
2996
+ EmbedCell,
2997
+ FindReplace,
2998
+ FloatingToolbar,
2999
+ GhostText,
3000
+ ImageCell,
3001
+ ImageResize,
3002
+ InsertHandle,
3003
+ LATEX_COMMANDS,
3004
+ LatexAutocomplete,
3005
+ MathEditor,
3006
+ MermaidPreview,
3007
+ NotebookContext,
3008
+ SciNotebook,
3009
+ SlashCommand,
3010
+ TOCSidebar,
3011
+ TableCell,
3012
+ VirtualRenderer,
3013
+ initMermaid,
3014
+ renderEmbedPreview,
3015
+ renderImagePreview,
3016
+ renderTablePreview,
3017
+ useCell,
3018
+ useFocusedCell,
3019
+ useNotebook,
3020
+ useNotebookEvent,
3021
+ useSciNotebook
3022
+ });