@flexiui/svelte-rich-text 0.0.57 → 0.0.59

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.
@@ -27,6 +27,7 @@
27
27
  className?: string;
28
28
  editable?: boolean;
29
29
  content?: string | {type: string, content: any[]} | null;
30
+ nodesLimit?: number;
30
31
  customExtensions?: any[];
31
32
  editorEvents?: {
32
33
  onTransaction?: (params: any) => void;
@@ -70,6 +71,7 @@
70
71
  className,
71
72
  editable,
72
73
  content,
74
+ nodesLimit,
73
75
  customExtensions = [],
74
76
  editorEvents = {
75
77
  onTransaction: () => {},
@@ -172,6 +174,8 @@
172
174
  let enterPressed = $state(false);
173
175
  let fontSize = $state(16) as number;
174
176
  let lineHeight = $state(null) as number;
177
+ let currentNodeCount = $state(0);
178
+ let showLimitWarning = $state(false);
175
179
 
176
180
  const TEXT_COLOR_PALETTE = [
177
181
  editorConfig.editorAccentColor,
@@ -253,6 +257,25 @@
253
257
  const isCellSelection = () =>
254
258
  $editor && $editor.state.selection instanceof CellSelection;
255
259
 
260
+ // función para contar nodos en el documento
261
+ function countNodes(doc: any): number {
262
+ // Solo contar los nodos de primer nivel (hijos directos del doc)
263
+ console.log(doc);
264
+ if (doc.type === 'doc' && doc.content) {
265
+ return doc.content.length;
266
+ }
267
+ return 0;
268
+ }
269
+
270
+ // función para actualizar el contador de nodos
271
+ function updateNodeCount() {
272
+ if ($editor) {
273
+
274
+ currentNodeCount = countNodes($editor.getJSON());
275
+
276
+ }
277
+ }
278
+
256
279
  onMount(() => {
257
280
  editor = createEditor({
258
281
  extensions,
@@ -263,8 +286,27 @@
263
286
  },
264
287
  handleKeyDown: (view, event) => {
265
288
  if (event.key === "Enter" && !event.ctrlKey) {
289
+ // Verificar si hay límite de nodos y si se ha alcanzado
266
290
  enterPressed = true;
267
291
 
292
+ if (nodesLimit) {
293
+ const currentCount = currentNodeCount;
294
+ if (currentCount >= nodesLimit) {
295
+
296
+ if(!showLimitWarning){
297
+ showLimitWarning = true;
298
+ setTimeout(() => {
299
+ showLimitWarning = false;
300
+ }, 3000);
301
+ }
302
+
303
+ event.preventDefault();
304
+ event.stopPropagation();
305
+ return true;
306
+
307
+ }
308
+ }
309
+
268
310
  setTimeout(() => {
269
311
  enterPressed = false;
270
312
  const { from } = view.state.selection;
@@ -318,14 +360,18 @@
318
360
  },
319
361
  },
320
362
  onTransaction: ({ editor, transaction }) => {
321
- editorEvents.onTransaction({ editor, transaction });
322
- editor = editor;
363
+
364
+ // Actualizar contador de nodos
365
+
366
+ updateNodeCount();
323
367
 
324
368
  if (enterPressed) {
325
- // console.log("Enter pressed");
326
369
  return;
327
370
  }
328
371
 
372
+ editorEvents.onTransaction({ editor, transaction });
373
+ editor = editor;
374
+
329
375
  const { from } = editor.state.selection;
330
376
 
331
377
  // Obtener el elemento DOM
@@ -377,6 +423,8 @@
377
423
  onCreate: ({ editor }) => {
378
424
  editorEvents.onCreate({ editor });
379
425
  migrateMathStrings(editor);
426
+
427
+ updateNodeCount();
380
428
  },
381
429
 
382
430
  onUpdate: ({ editor }) => {
@@ -461,7 +509,7 @@
461
509
  });
462
510
  }, 100);
463
511
  } catch (e) {
464
- console.log(e.message);
512
+ // console.log(e.message);
465
513
  }
466
514
  }
467
515
 
@@ -1510,6 +1558,22 @@
1510
1558
  {/if}
1511
1559
 
1512
1560
  <EditorContent editor={$editor} class="fl-rich-text-content" />
1561
+
1562
+ <!-- Bottom bar showing node count -->
1563
+ {#if nodesLimit}
1564
+ <div class="fl-node-count-bar">
1565
+ <span class="fl-node-count-text">
1566
+ <strong>Límite:</strong> {currentNodeCount} de {nodesLimit} nodos
1567
+ </span>
1568
+ <div class="fl-node-count-progress">
1569
+ <div
1570
+ class="fl-node-count-progress-bar"
1571
+ style="width: {Math.min((currentNodeCount / nodesLimit) * 100, 100)}%"
1572
+ ></div>
1573
+ </div>
1574
+ </div>
1575
+ {/if}
1576
+
1513
1577
  </div>
1514
1578
 
1515
1579
  <div
@@ -2107,9 +2171,9 @@
2107
2171
 
2108
2172
  // ⛔️ Ocultar si hay flag de selección completa por fila/columna
2109
2173
  if (isRow || isColumn) {
2110
- console.log(
2111
- "Ocultar si hay flag de selección completa por fila/columna"
2112
- );
2174
+ // console.log(
2175
+ // "Ocultar si hay flag de selección completa por fila/columna"
2176
+ // );
2113
2177
  return false;
2114
2178
  }
2115
2179
 
@@ -2793,9 +2857,21 @@
2793
2857
  </div>
2794
2858
  {/if}
2795
2859
 
2860
+ <!-- Warning message for node limit -->
2861
+ {#if showLimitWarning && nodesLimit}
2862
+ <div class="fl-node-limit-warning">
2863
+ No se pueden añadir más elementos. Límite alcanzado: {nodesLimit} elementos.
2864
+ </div>
2865
+ {/if}
2796
2866
 
2797
2867
  <style>
2798
2868
  .hidden {
2799
2869
  display: none;
2800
2870
  }
2871
+
2872
+ .fl-node-count-text {
2873
+ strong {
2874
+ margin-right: 4px;
2875
+ }
2876
+ }
2801
2877
  </style>
@@ -9,6 +9,7 @@ export interface Props {
9
9
  type: string;
10
10
  content: any[];
11
11
  } | null;
12
+ nodesLimit?: number;
12
13
  customExtensions?: any[];
13
14
  editorEvents?: {
14
15
  onTransaction?: (params: any) => void;
@@ -46,7 +46,7 @@
46
46
  id = id + "-" + Math.random().toString(36).substring(2, 15);
47
47
 
48
48
  audioAttributes.subscribe((value) => {
49
- console.log({ value });
49
+ // console.log({ value });
50
50
  });
51
51
 
52
52
  // audioAttributes.set({
@@ -73,7 +73,7 @@
73
73
 
74
74
  function togglePlayPause() {
75
75
  playing = !playing;
76
- console.log({ playing });
76
+ // console.log({ playing });
77
77
 
78
78
  if (playing) {
79
79
  activeAudioId.set(id);
@@ -130,15 +130,15 @@
130
130
  wavesurfer?.pause();
131
131
  }
132
132
  });
133
- console.log({ src });
134
- console.log({ WaveSurfer });
133
+ // console.log({ src });
134
+ // console.log({ WaveSurfer });
135
135
 
136
136
  if (WaveSurfer) {
137
137
 
138
138
  setTimeout(() => {
139
139
  const identifier = "waveform-" + id;
140
- console.log({ identifier });
141
- console.log({ waveformEl });
140
+ // console.log({ identifier });
141
+ // console.log({ waveformEl });
142
142
 
143
143
  wavesurfer = WaveSurfer?.create({
144
144
  container: waveformEl,
@@ -193,7 +193,7 @@
193
193
 
194
194
  audio.onended = () => {
195
195
  playing = false;
196
- console.log("Audio playback ended");
196
+ // console.log("Audio playback ended");
197
197
  };
198
198
 
199
199
  audio.addEventListener("timeupdate", () => {
@@ -205,7 +205,7 @@
205
205
 
206
206
  audio.onended = () => {
207
207
  playing = false;
208
- console.log("Audio playback ended");
208
+ // console.log("Audio playback ended");
209
209
  };
210
210
 
211
211
  audio.addEventListener("play", () => {
@@ -5,7 +5,7 @@
5
5
 
6
6
  const { node, updateAttributes, selected, getPos, editor }: NodeViewProps = $props();
7
7
 
8
- console.log(node);
8
+ // console.log(node);
9
9
  let cols = $state(node.attrs.cols || 2);
10
10
  let gap = $state(node.attrs.gap || 1);
11
11
  let showIndicator = $state(node.attrs.showIndicator || false);
@@ -119,10 +119,10 @@
119
119
  }
120
120
 
121
121
  async function handleDragOver(e: any) {
122
- console.log(e);
122
+ // console.log(e);
123
123
  dragging = true;
124
124
  e.preventDefault();
125
- console.log("Drag over");
125
+ // console.log("Drag over");
126
126
  }
127
127
 
128
128
  async function handleDrop(e: DragEvent) {
@@ -140,18 +140,18 @@
140
140
  );
141
141
 
142
142
  if (files.length > 0) {
143
- console.log("Dropped local files:", files);
143
+ // console.log("Dropped local files:", files);
144
144
  await uploadFiles(files);
145
145
  }
146
146
  } else {
147
147
  dragging = false;
148
- console.log("Dropped external content:", types);
148
+ // console.log("Dropped external content:", types);
149
149
 
150
150
  // puede ser desde navegador (ejemplo: arrastrar imagen desde otra pestaña)
151
151
  if (types.includes("text/uri-list")) {
152
152
  const url = e.dataTransfer?.getData("text/uri-list");
153
153
  if (url) {
154
- console.log("Dropped image URL:", url);
154
+ // console.log("Dropped image URL:", url);
155
155
  insertImage(url);
156
156
  }
157
157
  }
@@ -159,19 +159,19 @@
159
159
  }
160
160
 
161
161
  function handleDragEnter(e: any) {
162
- console.log("Drag enter");
162
+ // console.log("Drag enter");
163
163
  dragging = true;
164
164
  }
165
165
 
166
166
  function handleDragLeave(e: any) {
167
- console.log("Drag leave");
167
+ // console.log("Drag leave");
168
168
  dragging = false;
169
169
  }
170
170
 
171
171
  function handleFileChange(e: any) {
172
- console.log("File change");
172
+ // console.log("File change");
173
173
  const files = [...e.target.files];
174
- console.log(files);
174
+ // console.log(files);
175
175
 
176
176
  uploadFiles(files);
177
177
  }
@@ -200,7 +200,7 @@
200
200
  // console.log({ errorMessage: docs.message });
201
201
 
202
202
  if (json.error === "Token expired") {
203
- console.log("Token expired: MediaModal.svelte");
203
+ // console.log("Token expired: MediaModal.svelte");
204
204
  // return Astro.redirect('/admin/logout');
205
205
  const refreshTokenData = await refreshUserToken(refreshToken);
206
206
 
@@ -217,7 +217,7 @@
217
217
  console.error({ error, message });
218
218
  }
219
219
  } else if (json.error === "Token missing or invalid") {
220
- console.log("Token missing or invalid");
220
+ // console.log("Token missing or invalid");
221
221
  }
222
222
  }
223
223
 
@@ -246,9 +246,9 @@
246
246
  }
247
247
 
248
248
  function dragAreaClickHandler(e) {
249
- console.log("Clicked drag area");
249
+ // console.log("Clicked drag area");
250
250
  if (addGridMediaIconEl) {
251
- console.log("Add grid Media icon el: ", addGridMediaIconEl);
251
+ // console.log("Add grid Media icon el: ", addGridMediaIconEl);
252
252
  showTooltip(addGridMediaIconEl);
253
253
  }
254
254
  }
@@ -15,7 +15,7 @@ export const CustomTableCell = TableCell.extend({
15
15
  },
16
16
  addNodeView() {
17
17
  const editor = this.editor; // ✅ aquí lo tienes disponible
18
- console.log('Editor:', editor);
18
+ // console.log('Editor:', editor)
19
19
  return SvelteNodeViewRenderer(TableCellNodeView, {
20
20
  as: 'td',
21
21
  stopEvent: () => false,
@@ -34,7 +34,7 @@ export const CustomTableCell = TableCell.extend({
34
34
  this.storage.prevGripSelectionIsInFirstRow = isFirst; // ✅ nuevo flag
35
35
  tr.setSelection(rowSel);
36
36
  dispatch(tr);
37
- console.log('Row selected. Is first row?', isFirst);
37
+ // console.log('Row selected. Is first row?', isFirst);
38
38
  }
39
39
  return true;
40
40
  },
@@ -49,7 +49,7 @@ export const CustomTableCell = TableCell.extend({
49
49
  this.storage.prevGripSelectionIsInFirstRow = isFirst; // ✅ nuevo flag
50
50
  tr.setSelection(colSel);
51
51
  dispatch(tr);
52
- console.log('Column selected. Is first row?', isFirst);
52
+ // console.log('Column selected. Is first row?', isFirst);
53
53
  }
54
54
  return true;
55
55
  },
package/dist/styles.css CHANGED
@@ -411,7 +411,7 @@ button {
411
411
  }
412
412
 
413
413
  .fl-range-element input {
414
- accent-color: #ffffff;
414
+ accent-color: var(--fl-editor-accent-color);
415
415
  }
416
416
 
417
417
  .fl-range-element-value {
@@ -490,6 +490,64 @@ button {
490
490
  }
491
491
  }
492
492
 
493
+ /* Node limit styles */
494
+ .fl-node-limit-warning {
495
+ position: fixed;
496
+ bottom: 20px;
497
+ right: 20px;
498
+ background: #ff4444;
499
+ color: white;
500
+ padding: 12px 16px;
501
+ border-radius: 8px;
502
+ font-size: 14px;
503
+ font-weight: 500;
504
+ box-shadow: 0 4px 12px rgba(255, 68, 68, 0.3);
505
+ z-index: 1000;
506
+ animation: slideInRight 0.3s ease-out;
507
+ }
508
+
509
+ .fl-node-count-bar {
510
+ background: var(--fl-toolbar-bg, #242424);
511
+ border-top: 1px solid #333;
512
+ padding: 8px 16px;
513
+ display: flex;
514
+ align-items: center;
515
+ gap: 12px;
516
+ font-size: 12px;
517
+ color: var(--fl-toolbar-text-color, currentColor);
518
+ }
519
+
520
+ .fl-node-count-text {
521
+ font-weight: 500;
522
+ min-width: fit-content;
523
+ }
524
+
525
+ .fl-node-count-progress {
526
+ flex: 1;
527
+ height: 4px;
528
+ background: rgba(255, 255, 255, 0.1);
529
+ border-radius: 2px;
530
+ overflow: hidden;
531
+ }
532
+
533
+ .fl-node-count-progress-bar {
534
+ height: 100%;
535
+ background: var(--fl-editor-accent-color, #535bf2);
536
+ transition: width 0.2s ease;
537
+ border-radius: 2px;
538
+ }
539
+
540
+ @keyframes slideInRight {
541
+ from {
542
+ transform: translateX(100%);
543
+ opacity: 0;
544
+ }
545
+ to {
546
+ transform: translateX(0);
547
+ opacity: 1;
548
+ }
549
+ }
550
+
493
551
  .media-grid-item-view-content > div {
494
552
  display: flex;
495
553
  height: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flexiui/svelte-rich-text",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "description": "A lightweight and flexible rich text editor component for Svelte",
5
5
  "keywords": [
6
6
  "svelte",