@flexiui/svelte-rich-text 0.0.24 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/AudioPlayerSimple.svelte +7 -0
  2. package/dist/AudioPlayerSimple.svelte.d.ts +25 -0
  3. package/dist/RichText.svelte +110 -5
  4. package/dist/extensions/Audio.d.ts +29 -0
  5. package/dist/extensions/Audio.js +123 -0
  6. package/dist/extensions/AudioPlayer.svelte +622 -0
  7. package/dist/extensions/AudioPlayer.svelte.d.ts +20 -0
  8. package/dist/extensions/AudioPlayerWrapper.svelte +123 -0
  9. package/dist/extensions/AudioPlayerWrapper.svelte.d.ts +21 -0
  10. package/dist/extensions/MediaGrid/MediaGrid.d.ts +2 -0
  11. package/dist/extensions/MediaGrid/MediaGrid.js +90 -0
  12. package/dist/extensions/MediaGrid/MediaGrid.svelte +265 -0
  13. package/dist/extensions/MediaGrid/MediaGrid.svelte.d.ts +14 -0
  14. package/dist/extensions/MediaGrid/MediaGridItem.d.ts +2 -0
  15. package/dist/extensions/MediaGrid/MediaGridItem.js +24 -0
  16. package/dist/extensions/MediaGrid/MediaGridItem.svelte +484 -0
  17. package/dist/extensions/MediaGrid/MediaGridItem.svelte.d.ts +14 -0
  18. package/dist/extensions/MediaGrid/auth-service.d.ts +1 -0
  19. package/dist/extensions/MediaGrid/auth-service.js +11 -0
  20. package/dist/extensions/Table/CustomTableCell.d.ts +19 -0
  21. package/dist/extensions/Table/CustomTableCell.js +86 -0
  22. package/dist/extensions/Table/CustomTableHeader.d.ts +1 -0
  23. package/dist/extensions/Table/CustomTableHeader.js +33 -0
  24. package/dist/extensions/Table/TableCellControls.d.ts +0 -0
  25. package/dist/extensions/Table/TableCellControls.js +0 -0
  26. package/dist/extensions/Table/TableCellNodeView.svelte +576 -0
  27. package/dist/extensions/Table/TableCellNodeView.svelte.d.ts +14 -0
  28. package/dist/extensions/Table/TableCellSelection.d.ts +12 -0
  29. package/dist/extensions/Table/TableCellSelection.js +35 -0
  30. package/dist/extensions/audioStore.d.ts +1 -0
  31. package/dist/extensions/audioStore.js +2 -0
  32. package/dist/extensions/extensions.d.ts +7 -0
  33. package/dist/extensions/extensions.js +86 -0
  34. package/dist/styles.css +182 -0
  35. package/package.json +2 -1
@@ -0,0 +1,484 @@
1
+ <script lang="ts">
2
+ import {
3
+ computePosition,
4
+ offset,
5
+ autoUpdate,
6
+ size,
7
+ autoPlacement,
8
+ } from "@floating-ui/dom";
9
+ import type { NodeViewProps } from "@tiptap/core";
10
+ import { onMount, onDestroy } from "svelte";
11
+ import cx from "clsx";
12
+ import { NodeViewContent, NodeViewWrapper } from "svelte-tiptap";
13
+ import { refreshUserToken } from "./auth-service";
14
+
15
+ const { node, editor, getPos, selected }: NodeViewProps = $props();
16
+
17
+ const UPLOAD_API_URL = "http://localhost:3500/api/media";
18
+ const UPLOADS_URL =
19
+ "https://pub-503cb197f1134814ac02f257b9cde1c1.r2.dev/uploads/";
20
+ let token =
21
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2Njk5NzY3ZTBhNmM3MzI3OTMwZWMxMGQiLCJuYW1lIjpudWxsLCJ1c2VybmFtZSI6ImFsZXhncDg5NSIsImVtYWlsIjoiYWxleGdwODk1QGdtYWlsLmNvbSIsInJvbGUiOiJzdXBlcl9hZG1pbiIsInJvbGVzIjpbInN1cGVyX2FkbWluIl0sImFkZHJlc3MiOiJDYXJyZXIgUHVpZ21hcsOtLCA3MTYiLCJsYXN0TG9nZ2VkSW5UZW5hbnQiOm51bGwsImlhdCI6MTc2MjQ3MDk2NiwiZXhwIjoxNzYyNDcxODY2fQ.BTkbEyT6vsim7EvHoxSyyCTErnh0smsqy-HsnHqCeQw";
22
+ const refreshToken =
23
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2Njk5NzY3ZTBhNmM3MzI3OTMwZWMxMGQiLCJuYW1lIjpudWxsLCJ1c2VybmFtZSI6ImFsZXhncDg5NSIsImVtYWlsIjoiYWxleGdwODk1QGdtYWlsLmNvbSIsInJvbGUiOiJzdXBlcl9hZG1pbiIsInJvbGVzIjpbInN1cGVyX2FkbWluIl0sImFkZHJlc3MiOiJDYXJyZXIgUHVpZ21hcsOtLCA3MTYiLCJ0ZW5hbnRzIjpbXSwibGFzdExvZ2dlZEluVGVuYW50IjpudWxsLCJpYXQiOjE3NjI0NjU3NjMsImV4cCI6MTc2NTA1Nzc2M30.V9IHMl8JwEOeEOMBeZRJ0BTAeEIoxzzWA87E1aAYBuU";
24
+ let showIndicator = $state(false);
25
+ let indicatorType = $state("numeric");
26
+ let index = $state(0);
27
+ let hasImage = $state(false);
28
+ let dragging = $state(false);
29
+ let uploading = $state(false);
30
+ let uploadedFiles = [];
31
+ let tooltipVisible = $state(false);
32
+ let tooltipX = $state(0);
33
+ let tooltipY = $state(0);
34
+ let tooltip: HTMLDivElement = $state(null) as HTMLDivElement;
35
+ let cleanup: () => void;
36
+ let addGridMediaIconEl: HTMLElement = $state(null) as HTMLElement;
37
+ let fileInput: HTMLInputElement = $state(null) as HTMLInputElement;
38
+
39
+ function recompute() {
40
+ const pos = typeof getPos === "function" ? getPos() : getPos;
41
+ if (typeof pos !== "number") return;
42
+ const resolvedPos = editor.state.doc.resolve(pos);
43
+ const parent = resolvedPos.parent;
44
+
45
+ if (parent?.type?.name === "MediaGridComponent") {
46
+ showIndicator = !!parent.attrs.showIndicator;
47
+ indicatorType = parent.attrs.indicatorType ?? "numeric";
48
+ index = resolvedPos.index(resolvedPos.depth); // 👈 aquí el cambio
49
+ } else {
50
+ showIndicator = false;
51
+ }
52
+
53
+ if (node.childCount === 0) {
54
+ hasImage = false;
55
+ } else {
56
+ hasImage = true;
57
+ }
58
+ }
59
+
60
+ // Ejecuta al montar / cuando node cambie (útil para la primera vez)
61
+ $effect(() => {
62
+ recompute();
63
+ });
64
+
65
+ // Suscribe a los eventos del editor para detectar cambios en el documento
66
+ let _onUpdate: () => void;
67
+ let _onTransaction: ({ transaction }: any) => void;
68
+
69
+ onMount(() => {
70
+ _onUpdate = () => recompute();
71
+ _onTransaction = ({ transaction }: any) => {
72
+ // solo recalcular si el doc ha cambiado (evita work innecesario)
73
+ if (transaction.docChanged) recompute();
74
+ };
75
+
76
+ editor.on("update", _onUpdate);
77
+ editor.on("transaction", _onTransaction);
78
+ });
79
+
80
+ onDestroy(() => {
81
+ if (_onUpdate) editor.off("update", _onUpdate);
82
+ if (_onTransaction) editor.off("transaction", _onTransaction);
83
+ });
84
+
85
+ // function handleAddMedia() {
86
+ // insertImage();
87
+ // }
88
+
89
+ function insertImage(url: string | ArrayBuffer | null = null) {
90
+ const pos = getPos();
91
+
92
+ if (!url) {
93
+ url = window.prompt("Enter the URL of the image:");
94
+ }
95
+
96
+ if (url) {
97
+ // editor
98
+ // .chain()
99
+ // .focus()
100
+ // .setTextSelection(pos + 1) // 👈 entra dentro del content
101
+ // .setImage({ src: url })
102
+ // .run();
103
+
104
+ editor
105
+ .chain()
106
+ .insertContentAt(pos + 1, [
107
+ {
108
+ type: "image",
109
+ attrs: {
110
+ src: url,
111
+ },
112
+ },
113
+ ])
114
+ .focus()
115
+ .run();
116
+ hasImage = true;
117
+ hideTooltip();
118
+ }
119
+ }
120
+
121
+ async function handleDragOver(e: any) {
122
+ console.log(e);
123
+ dragging = true;
124
+ e.preventDefault();
125
+ console.log("Drag over");
126
+ }
127
+
128
+ async function handleDrop(e: DragEvent) {
129
+ const types = e.dataTransfer?.types || [];
130
+
131
+ // Verifica si es un archivo real (desde ordenador)
132
+ const isFileDrop = Array.from(types).includes("Files");
133
+
134
+ if (isFileDrop) {
135
+ e.preventDefault(); // solo prevenimos el comportamiento por defecto si viene del ordenador
136
+ dragging = false;
137
+
138
+ const files = Array.from(e.dataTransfer!.files).filter((file) =>
139
+ file.type.startsWith("image/")
140
+ );
141
+
142
+ if (files.length > 0) {
143
+ console.log("Dropped local files:", files);
144
+ await uploadFiles(files);
145
+ }
146
+ } else {
147
+ dragging = false;
148
+ console.log("Dropped external content:", types);
149
+
150
+ // puede ser desde navegador (ejemplo: arrastrar imagen desde otra pestaña)
151
+ if (types.includes("text/uri-list")) {
152
+ const url = e.dataTransfer?.getData("text/uri-list");
153
+ if (url) {
154
+ console.log("Dropped image URL:", url);
155
+ insertImage(url);
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ function handleDragEnter(e: any) {
162
+ console.log("Drag enter");
163
+ dragging = true;
164
+ }
165
+
166
+ function handleDragLeave(e: any) {
167
+ console.log("Drag leave");
168
+ dragging = false;
169
+ }
170
+
171
+ function handleFileChange(e: any) {
172
+ console.log("File change");
173
+ const files = [...e.target.files];
174
+ console.log(files);
175
+
176
+ uploadFiles(files);
177
+ }
178
+
179
+ async function uploadFiles(files: any) {
180
+ uploading = true;
181
+
182
+ // console.log(newSelectedFiles)
183
+ let formData = new FormData();
184
+
185
+ files.forEach(async (file) => {
186
+ formData.append("file", file);
187
+ });
188
+
189
+ const response = await fetch(UPLOAD_API_URL, {
190
+ method: "POST",
191
+ headers: {
192
+ Authorization: "Bearer " + token,
193
+ },
194
+ body: formData,
195
+ });
196
+
197
+ const json = await response?.json();
198
+
199
+ if (json.error) {
200
+ // console.log({ errorMessage: docs.message });
201
+
202
+ if (json.error === "Token expired") {
203
+ console.log("Token expired: MediaModal.svelte");
204
+ // return Astro.redirect('/admin/logout');
205
+ const refreshTokenData = await refreshUserToken(refreshToken);
206
+
207
+ if (refreshTokenData?.ok) {
208
+ token = refreshTokenData.token;
209
+
210
+ uploadFiles(files);
211
+
212
+ return;
213
+
214
+ // return Astro.redirect('/admin/changes/history')
215
+ } else {
216
+ const { error, message } = refreshTokenData;
217
+ console.error({ error, message });
218
+ }
219
+ } else if (json.error === "Token missing or invalid") {
220
+ console.log("Token missing or invalid");
221
+ }
222
+ }
223
+
224
+ uploadedFiles = json.data;
225
+ let newImageNodes = [];
226
+
227
+ if (uploadedFiles.length > 0) {
228
+ uploading = false;
229
+ newImageNodes = uploadedFiles.map((file) => {
230
+ return {
231
+ type: "image",
232
+ attrs: {
233
+ src: UPLOADS_URL + file?.file,
234
+ },
235
+ };
236
+ });
237
+
238
+ setTimeout(() => {
239
+ editor
240
+ .chain()
241
+ .focus()
242
+ .insertContentAt(getPos() + 1, newImageNodes)
243
+ .run();
244
+ }, 1000);
245
+ }
246
+ }
247
+
248
+ function dragAreaClickHandler(e) {
249
+ console.log("Clicked drag area");
250
+ if (addGridMediaIconEl) {
251
+ showTooltip(addGridMediaIconEl);
252
+ }
253
+ }
254
+
255
+ let currentTriggerEl: HTMLElement | null = null;
256
+
257
+ function showTooltip(el: HTMLElement) {
258
+ // Si ya hay uno abierto en OTRO elemento → cerramos primero
259
+ if (tooltipVisible && currentTriggerEl && currentTriggerEl !== el) {
260
+ hideTooltip();
261
+ }
262
+
263
+ // Si ya está abierto en el mismo elemento → no hacemos nada
264
+ if (tooltipVisible && currentTriggerEl === el) {
265
+ return;
266
+ }
267
+
268
+ hideTooltip(); // limpiar antes de abrir
269
+ currentTriggerEl = el;
270
+ tooltipVisible = true;
271
+ document.body.append(tooltip);
272
+
273
+ document.addEventListener("mousedown", handleClickOutside);
274
+ cleanup = autoUpdate(el, tooltip, () => updatePosition(el));
275
+ }
276
+
277
+ function hideTooltip() {
278
+ tooltipVisible = false;
279
+ tooltip?.remove();
280
+ document.removeEventListener("mousedown", handleClickOutside);
281
+ cleanup && cleanup();
282
+ currentTriggerEl = null;
283
+ }
284
+
285
+ function handleClickOutside(e: MouseEvent) {
286
+ if (!tooltip) return;
287
+ const target = e.target as Node;
288
+
289
+ // Excepciones: tooltip, trigger actual, y el drag-area contenedor
290
+ if (
291
+ tooltip.contains(target) ||
292
+ currentTriggerEl?.contains(target) ||
293
+ currentTriggerEl?.closest(".fl-image-upload-drag-area")?.contains(target)
294
+ ) {
295
+ return; // no cerrar
296
+ }
297
+
298
+ hideTooltip();
299
+ }
300
+
301
+ function updatePosition(el: HTMLElement) {
302
+ computePosition(el, tooltip, {
303
+ placement: "bottom",
304
+ middleware: [
305
+ offset(4),
306
+ size({
307
+ apply({ rects, elements }) {
308
+ // Object.assign(elements.floating.style, {
309
+ // height: `${rects.reference.height}px`,
310
+ // // minHeight: `64px`,
311
+ // });
312
+ },
313
+ }),
314
+ // autoPlacement({
315
+ // allowedPlacements: ["left-start", "bottom-end",],
316
+ // }),
317
+ ],
318
+ }).then(({ x, y }) => {
319
+ tooltipX = x;
320
+ tooltipY = y;
321
+ });
322
+ }
323
+ </script>
324
+
325
+ <NodeViewWrapper
326
+ data-selected={selected}
327
+ class={cx(`fl-media-grid-item ${!hasImage ? "is-empty" : ""}`)}
328
+ data-drag-handle=""
329
+ ondragover={handleDragOver}
330
+ ondrop={handleDrop}
331
+ ondragenter={handleDragEnter}
332
+ ondragleave={handleDragLeave}
333
+ >
334
+ {#if !hasImage}
335
+ <!-- <label class="fl-upload-file-panel">
336
+ <input type="file" accept="image/*" multiple onchange={handleFileChange} />
337
+ </label> -->
338
+ <div
339
+ class="fl-image-upload-drag-area"
340
+ class:dragging
341
+ role="button"
342
+ tabindex="0"
343
+ onclick={dragAreaClickHandler}
344
+ onkeydown={(e) =>
345
+ e.key === "Enter" || e.key === " " ? dragAreaClickHandler(e) : null}
346
+ contenteditable="false"
347
+ >
348
+ {#if !dragging}
349
+ <span class="fl-add-grid-media-icon" bind:this={addGridMediaIconEl}>
350
+ <svg
351
+ xmlns="http://www.w3.org/2000/svg"
352
+ width="40"
353
+ height="40"
354
+ viewBox="0 0 24 24"
355
+ fill="currentColor"
356
+ class="icon icon-tabler icons-tabler-filled icon-tabler-circle-plus"
357
+ ><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path
358
+ d="M4.929 4.929a10 10 0 1 1 14.141 14.141a10 10 0 0 1 -14.14 -14.14zm8.071 4.071a1 1 0 1 0 -2 0v2h-2a1 1 0 1 0 0 2h2v2a1 1 0 1 0 2 0v-2h2a1 1 0 1 0 0 -2h-2v-2z"
359
+ ></path></svg
360
+ >
361
+ </span>
362
+ <input bind:this={fileInput} type="file" accept="image/*" multiple onchange={handleFileChange} style="display: none;"/>
363
+ {/if}
364
+ </div>
365
+ {/if}
366
+
367
+ {#if showIndicator}
368
+ <span class="fl-grid-indicator" contenteditable="false">
369
+ {indicatorType === "numeric"
370
+ ? index + 1
371
+ : String.fromCharCode(65 + index)}
372
+ </span>
373
+ {/if}
374
+
375
+ <NodeViewContent
376
+ class="media-grid-item-view-content"
377
+ contenteditable={false}
378
+ />
379
+
380
+ {#if !hasImage}
381
+ <div
382
+ bind:this={tooltip}
383
+ id="tooltip"
384
+ class="tooltip fl-dropdown-panel"
385
+ style="display: {tooltipVisible
386
+ ? 'flex'
387
+ : 'none'}; left: {tooltipX}px; top: {tooltipY}px;"
388
+ >
389
+ <button onclick={() => fileInput.click()}>Subir archivo/s</button>
390
+ <button onclick={() => insertImage()}>Añadir desde url</button>
391
+ </div>
392
+ {/if}
393
+ </NodeViewWrapper>
394
+
395
+ <!-- <Dropdown
396
+ bind:refreshDropdown
397
+ bind:calculatePosition={recalculateDropdownPosition}
398
+ bind:toggleByKey
399
+ position={activePosition}
400
+ yOffset={0}
401
+ xOffset={0}
402
+ margin={3}
403
+ id="dropdown"
404
+ on:close={handleCloseDropdown}
405
+ on:open={handleOpenDropdown}
406
+ >
407
+
408
+ </Dropdown> -->
409
+
410
+ <style>
411
+ .tooltip {
412
+
413
+ }
414
+ .fl-dropdown-panel {
415
+ background: #0d0d0da8;
416
+ border: 1px solid #ffffff12;
417
+ backdrop-filter: blur(42px);
418
+ border-radius: 14px;
419
+ padding: 8px;
420
+ position: absolute;
421
+ flex-direction: column;
422
+ gap: 6px;
423
+
424
+ button {
425
+ border: none;
426
+ background-color: #6b6b6b75;
427
+ padding: 6px 12px;
428
+ border-radius: 8px;
429
+ cursor: pointer;
430
+ transition: background-color 0.2s ease-in-out;
431
+ &:hover {
432
+ background-color: #6b6b6b96;
433
+ }
434
+ }
435
+ }
436
+ .fl-image-upload-drag-area {
437
+ border: 1px dashed #eeeeee3d;
438
+ width: 100%;
439
+ aspect-ratio: 16 / 9;
440
+ display: flex;
441
+ align-items: center;
442
+ justify-content: center;
443
+ border-radius: 16px;
444
+ position: absolute;
445
+ cursor: pointer;
446
+ transition: all 0.2s ease-in-out;
447
+ &:hover {
448
+ border-color: #eeeeee6f;
449
+
450
+ .fl-add-grid-media-icon {
451
+ color: #fff;
452
+ }
453
+ }
454
+
455
+ &:active {
456
+ border-color: #8086ff;
457
+ }
458
+
459
+ &.dragging {
460
+ background: #535bf214;
461
+ border-color: #535bf2;
462
+ }
463
+ }
464
+
465
+ .fl-add-grid-media-icon {
466
+ background: transparent;
467
+ border-radius: 18px;
468
+ border: none;
469
+ padding: 0;
470
+ cursor: pointer;
471
+ transition: all 0.2s ease-in-out;
472
+ color: rgba(255, 255, 255, 0.7);
473
+ display: flex;
474
+ }
475
+
476
+ .fl-upload-file-panel {
477
+ position: absolute;
478
+ height: 100%;
479
+ display: flex;
480
+ align-items: center;
481
+ justify-content: center;
482
+ width: 100%;
483
+ }
484
+ </style>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type MediaGridItemProps = typeof __propDef.props;
10
+ export type MediaGridItemEvents = typeof __propDef.events;
11
+ export type MediaGridItemSlots = typeof __propDef.slots;
12
+ export default class MediaGridItem extends SvelteComponentTyped<MediaGridItemProps, MediaGridItemEvents, MediaGridItemSlots> {
13
+ }
14
+ export {};
@@ -0,0 +1 @@
1
+ export declare function refreshUserToken(refreshToken: any): Promise<any>;
@@ -0,0 +1,11 @@
1
+ const API_URL = "http://localhost:3500/api/";
2
+ export async function refreshUserToken(refreshToken) {
3
+ const response = await fetch(API_URL + "users/refresh-token", {
4
+ method: "POST",
5
+ headers: {
6
+ "Content-Type": "application/json",
7
+ "refresh": refreshToken,
8
+ },
9
+ });
10
+ return await response?.json();
11
+ }
@@ -0,0 +1,19 @@
1
+ export interface CustomTableCellStorage {
2
+ customTableSelection: string | null;
3
+ gripSelectionIsInFirstRow: boolean;
4
+ prevGripSelectionIsInFirstRow: boolean;
5
+ }
6
+ declare module '@tiptap/core' {
7
+ interface Storage {
8
+ tableCell: CustomTableCellStorage;
9
+ }
10
+ }
11
+ declare module '@tiptap/core' {
12
+ interface Commands<ReturnType> {
13
+ tableCell: {
14
+ selectRow: (cell: number) => ReturnType;
15
+ selectColumn: (cell: number) => ReturnType;
16
+ };
17
+ }
18
+ }
19
+ export declare const CustomTableCell: any;
@@ -0,0 +1,86 @@
1
+ import { TableCell } from '@tiptap/extension-table/cell';
2
+ import { CellSelection, deleteRow, deleteColumn } from 'prosemirror-tables';
3
+ import { SvelteNodeViewRenderer } from 'svelte-tiptap';
4
+ import TableCellNodeView from './TableCellNodeView.svelte';
5
+ import { Plugin } from '@tiptap/pm/state';
6
+ /* ---- Extensión propiamente dicha ---- */
7
+ export const CustomTableCell = TableCell.extend({
8
+ name: 'tableCell',
9
+ addStorage() {
10
+ return {
11
+ customTableSelection: null,
12
+ gripSelectionIsInFirstRow: false,
13
+ prevGripSelectionIsInFirstRow: false,
14
+ };
15
+ },
16
+ addNodeView() {
17
+ const editor = this.editor; // ✅ aquí lo tienes disponible
18
+ console.log('Editor:', editor);
19
+ return SvelteNodeViewRenderer(TableCellNodeView, {
20
+ as: 'td',
21
+ stopEvent: () => false,
22
+ });
23
+ },
24
+ addCommands() {
25
+ return {
26
+ selectRow: (cell) => ({ tr, dispatch, state }) => {
27
+ const pos = tr.doc.resolve(cell);
28
+ const rowSel = CellSelection.rowSelection(pos);
29
+ if (dispatch) {
30
+ // Busca si es la primera fila
31
+ const isFirst = isFirstRow(pos);
32
+ this.storage.customTableSelection = 'row';
33
+ this.storage.gripSelectionIsInFirstRow = isFirst; // ✅ nuevo flag
34
+ this.storage.prevGripSelectionIsInFirstRow = isFirst; // ✅ nuevo flag
35
+ tr.setSelection(rowSel);
36
+ dispatch(tr);
37
+ console.log('Row selected. Is first row?', isFirst);
38
+ }
39
+ return true;
40
+ },
41
+ selectColumn: (cell) => ({ tr, dispatch, state }) => {
42
+ const pos = tr.doc.resolve(cell);
43
+ const colSel = CellSelection.colSelection(pos);
44
+ if (dispatch) {
45
+ // Busca si es la primera fila
46
+ const isFirst = isFirstRow(pos);
47
+ this.storage.customTableSelection = 'column';
48
+ this.storage.gripSelectionIsInFirstRow = isFirst; // ✅ nuevo flag
49
+ this.storage.prevGripSelectionIsInFirstRow = isFirst; // ✅ nuevo flag
50
+ tr.setSelection(colSel);
51
+ dispatch(tr);
52
+ console.log('Column selected. Is first row?', isFirst);
53
+ }
54
+ return true;
55
+ },
56
+ };
57
+ },
58
+ });
59
+ function isFirstRow($pos) {
60
+ // Encuentra la tabla que contiene esta celda
61
+ let tableDepth = -1;
62
+ for (let d = $pos.depth; d >= 0; d--) {
63
+ const node = $pos.node(d);
64
+ if (node.type.name === 'table') {
65
+ tableDepth = d;
66
+ break;
67
+ }
68
+ }
69
+ if (tableDepth === -1)
70
+ return false;
71
+ const tableNode = $pos.node(tableDepth);
72
+ // Busca la fila padre (tableRow)
73
+ let rowNode = null;
74
+ for (let d = $pos.depth; d > tableDepth; d--) {
75
+ const node = $pos.node(d);
76
+ if (node.type.name === 'tableRow') {
77
+ rowNode = node;
78
+ break;
79
+ }
80
+ }
81
+ if (!rowNode)
82
+ return false;
83
+ // Compara con la primera fila de la tabla
84
+ return tableNode.child(0) === rowNode;
85
+ }
86
+ // 👇 Declaración de tipos para que TypeScript entienda las nuevas props
@@ -0,0 +1 @@
1
+ export declare const CustomTableHeader: any;
@@ -0,0 +1,33 @@
1
+ import { TableHeader } from '@tiptap/extension-table/header';
2
+ import { CellSelection, deleteRow, deleteColumn } from 'prosemirror-tables';
3
+ import { SvelteNodeViewRenderer } from 'svelte-tiptap';
4
+ import TableCellNodeView from './TableCellNodeView.svelte';
5
+ export const CustomTableHeader = TableHeader.extend({
6
+ addNodeView() {
7
+ return SvelteNodeViewRenderer(TableCellNodeView, { as: 'th', stopEvent: () => false });
8
+ },
9
+ // addCommands() {
10
+ // return {
11
+ // selectRow:
12
+ // (cell: any) =>
13
+ // ({ tr, dispatch }: any) => {
14
+ // if (dispatch) {
15
+ // const pos = tr.doc.resolve(cell)
16
+ // const sel = CellSelection.rowSelection(pos)
17
+ // tr.setSelection(sel)
18
+ // }
19
+ // return true
20
+ // },
21
+ // selectColumn:
22
+ // (cell: any) =>
23
+ // ({ tr, dispatch }: any) => {
24
+ // if (dispatch) {
25
+ // const pos = tr.doc.resolve(cell)
26
+ // const sel = CellSelection.colSelection(pos)
27
+ // tr.setSelection(sel)
28
+ // }
29
+ // return true
30
+ // },
31
+ // }
32
+ // },
33
+ });
File without changes
File without changes