@flexiui/svelte-rich-text 0.0.19 → 0.0.21

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.
@@ -1,17 +1,20 @@
1
1
  <script lang="ts">
2
+ import { NodeLineHeight } from './extensions/NodeLineHeight';
2
3
  import {
3
4
  Mathematics,
4
5
  migrateMathStrings,
5
6
  } from "@tiptap/extension-mathematics";
6
7
  import { HEADINGS, rgbToHex } from "./utils";
7
8
  import "./styles.css";
8
- import 'katex/dist/katex.min.css'
9
+ import "katex/dist/katex.min.css";
9
10
 
10
11
  import { onMount, onDestroy } from "svelte";
11
12
  import type { Readable } from "svelte/store";
12
13
 
13
14
  import { createEditor, Editor, EditorContent } from "svelte-tiptap";
14
15
 
16
+ import { Paragraph } from "@tiptap/extension-paragraph";
17
+ import { Heading } from "@tiptap/extension-heading";
15
18
  import StarterKit from "@tiptap/starter-kit";
16
19
  import Highlight from "@tiptap/extension-highlight";
17
20
  import TextAlign from "@tiptap/extension-text-align";
@@ -132,6 +135,7 @@
132
135
  },
133
136
  },
134
137
  }),
138
+ NodeLineHeight,
135
139
  ...customExtensions,
136
140
  ];
137
141
 
@@ -142,6 +146,7 @@
142
146
  let cleanup: () => void;
143
147
  let currentTriggerEl: HTMLElement | null = null;
144
148
  let activeDropdownType = $state(null);
149
+ let enterPressed = $state(false);
145
150
 
146
151
  const TEXT_COLOR_PALETTE = [
147
152
  "rgb(94, 23, 235)",
@@ -223,9 +228,114 @@
223
228
  editor = createEditor({
224
229
  extensions,
225
230
  content,
231
+ editorProps: {
232
+ attributes: {
233
+ class: "fl-rich-text-content-eee",
234
+ },
235
+ handleKeyDown: (view, event) => {
236
+ if (event.key === "Enter" && !event.ctrlKey) {
237
+ console.log("Enter");
238
+ enterPressed = true;
239
+
240
+ setTimeout(() => {
241
+ enterPressed = false;
242
+ const { from } = view.state.selection;
243
+
244
+ // Obtener el nodo de ProseMirror en la posición actual
245
+ const pos = view.state.doc.resolve(from);
246
+ const nodeBefore = pos.node(pos.depth);
247
+ const parentNode = pos.node(pos.depth - 1);
248
+
249
+ console.log("Node type:", nodeBefore.type.name);
250
+ console.log("Parent node type:", parentNode?.type.name);
251
+
252
+ // Solo ejecutar si estamos en un párrafo Y el padre no es una lista
253
+ const isInList = parentNode?.type.name === "listItem" ||
254
+ parentNode?.type.name === "bulletList" ||
255
+ parentNode?.type.name === "orderedList";
256
+
257
+ if (nodeBefore.type.name === "paragraph" && !isInList) {
258
+ const domAtPos = view.domAtPos(from);
259
+ let element = domAtPos.node;
260
+
261
+ if (element.nodeType === Node.TEXT_NODE) {
262
+ element = element.parentElement;
263
+ }
264
+
265
+ if (element instanceof HTMLElement) {
266
+ const computedSize = window.getComputedStyle(element).fontSize;
267
+ const computedLineHeight = window.getComputedStyle(element).lineHeight;
268
+ console.log({ computedSize, computedLineHeight });
269
+
270
+ const lineHeightPx = parseFloat(computedLineHeight.replace("px", ""))
271
+ const fontSizePx = parseFloat(computedSize.replace("px", ""))
272
+
273
+ const lineHeightUnitless = lineHeightPx / fontSizePx;
274
+
275
+ console.log(lineHeightUnitless.toFixed(2)); // ej: "x.xx"
276
+
277
+ fontSize = Math.round(Number(computedSize.replace("px", "")));
278
+ $editor.chain().focus().unsetFontSize().run();
279
+
280
+ $editor.chain().focus().unsetNodeLineHeight().run();
281
+ }
282
+ }
283
+ }, 200);
284
+ }
285
+ },
286
+ },
226
287
  onTransaction: ({ editor, transaction }) => {
227
288
  editorEvents.onTransaction({ editor, transaction });
228
289
  editor = editor;
290
+
291
+ if (enterPressed) {
292
+ console.log("Enter pressed");
293
+ return;
294
+ }
295
+
296
+ const { from } = editor.state.selection;
297
+
298
+ // Obtener el elemento DOM
299
+ const domAtPos = editor.view.domAtPos(from);
300
+ let element = domAtPos.node;
301
+
302
+ // Si es un text node, obtener su padre
303
+ if (element.nodeType === Node.TEXT_NODE) {
304
+ element = element.parentElement;
305
+ }
306
+
307
+ // Obtener el font-size computado
308
+ if (element instanceof HTMLElement) {
309
+ const computedSize = window.getComputedStyle(element).fontSize;
310
+ const computedLineHeight = window.getComputedStyle(element).lineHeight;
311
+ console.log("Get element font size:", computedSize);
312
+ console.log("Get element line height:", computedLineHeight);
313
+ const lineHeightPx = parseFloat(computedLineHeight.replace("px", ""))
314
+ const fontSizePx = parseFloat(computedSize.replace("px", ""))
315
+ const lineHeightUnitless = lineHeightPx / fontSizePx;
316
+
317
+ console.log(lineHeightUnitless.toFixed(2)); // ej: "x.xx"
318
+
319
+ // O desde Tiptap
320
+ const tiptapSize = editor.getAttributes("textStyle").fontSize;
321
+ const tiptapLineHeight = editor.getAttributes("textStyle").lineHeight;
322
+ console.log("Tiptap font size:", tiptapSize ? tiptapSize : "default");
323
+ console.log("Tiptap line height:", tiptapLineHeight ? tiptapLineHeight : "default");
324
+
325
+ if (tiptapSize) {
326
+ fontSize = Number(tiptapSize.replace("px", ""));
327
+ } else {
328
+ fontSize = Math.round(Number(computedSize.replace("px", "")));
329
+ }
330
+
331
+ if (tiptapLineHeight) {
332
+ lineHeight = Number(tiptapLineHeight.replace("px", ""));
333
+ } else {
334
+ lineHeight = Number(lineHeightUnitless.toFixed(2));
335
+ }
336
+
337
+
338
+ }
229
339
  },
230
340
 
231
341
  onBeforeCreate({ editor }) {
@@ -252,6 +362,7 @@
252
362
  onSelectionUpdate({ editor }) {
253
363
  editorEvents.onSelectionUpdate({ editor });
254
364
  },
365
+
255
366
  onFocus({ editor, event }) {
256
367
  editorEvents.onFocus({ editor, event });
257
368
  },
@@ -349,11 +460,45 @@
349
460
  return $editor.chain().insertInlineMath({ latex }).focus().run();
350
461
  }
351
462
  }
463
+
464
+ let fontSize = $state(16) as number;
465
+ let lineHeight = $state(null) as number;
466
+
467
+ function decrementFontSize() {
468
+ fontSize = fontSize - 1;
469
+ $editor
470
+ .chain()
471
+ .focus()
472
+ .setFontSize(fontSize + "px")
473
+ .run();
474
+ }
475
+
476
+ function incrementFontSize() {
477
+ fontSize = fontSize + 1;
478
+ $editor
479
+ .chain()
480
+ .focus()
481
+ .setFontSize(fontSize + "px")
482
+ .run();
483
+ }
484
+
485
+ function handleRangeInput(e: any) {
486
+
487
+
488
+
489
+ $editor
490
+ .chain()
491
+ .focus()
492
+ .setNodeLineHeight(e.target.value.toString())
493
+ .run();
494
+
495
+ }
352
496
  </script>
353
497
 
354
498
  <div class="fl-rich-text {className}" class:editable>
355
499
  {#if editor}
356
500
  <header class="fl-rich-text-toolbar">
501
+ <!-- Undo/Redo -->
357
502
  <div role="group" class="fl-rich-text-toolbar-group">
358
503
  <button
359
504
  type="button"
@@ -399,7 +544,9 @@
399
544
  </button>
400
545
  </div>
401
546
 
547
+ <!-- Heading & list dropdowns -->
402
548
  <div class="fl-rich-text-toolbar-group">
549
+ <!-- Heading -->
403
550
  <button
404
551
  type="button"
405
552
  onclick={(e) => toogleDropdown(e.currentTarget, "headings-dropdown")}
@@ -433,18 +580,12 @@
433
580
  xmlns="http://www.w3.org/2000/svg"
434
581
  fill="none"
435
582
  viewBox="0 0 10 6"
436
- ><path
437
- stroke="currentColor"
438
- stroke-linecap="round"
439
- stroke-linejoin="round"
440
- stroke-width="2"
441
- d="m1 1 4 4 4-4"
442
- ></path></svg
443
583
  >
584
+ <use href="#dropdown-arrow"></use>
585
+ </svg>
444
586
  </button>
445
- </div>
446
587
 
447
- <div role="group" class="fl-rich-text-toolbar-group">
588
+ <!-- List -->
448
589
  <button
449
590
  aria-label="List"
450
591
  type="button"
@@ -616,16 +757,15 @@
616
757
  xmlns="http://www.w3.org/2000/svg"
617
758
  fill="none"
618
759
  viewBox="0 0 10 6"
619
- ><path
620
- stroke="currentColor"
621
- stroke-linecap="round"
622
- stroke-linejoin="round"
623
- stroke-width="2"
624
- d="m1 1 4 4 4-4"
625
- ></path></svg
626
760
  >
761
+ <use href="#dropdown-arrow"></use>
762
+ </svg>
627
763
  </button>
764
+ </div>
628
765
 
766
+ <!-- Code block & blockquote -->
767
+ <div role="group" class="fl-rich-text-toolbar-group">
768
+ <!-- Code block -->
629
769
  <button
630
770
  aria-label="Code block"
631
771
  type="button"
@@ -658,6 +798,7 @@
658
798
  >
659
799
  </button>
660
800
 
801
+ <!-- Blockquote -->
661
802
  <button
662
803
  aria-label="Blockquote"
663
804
  type="button"
@@ -680,6 +821,48 @@
680
821
  </button>
681
822
  </div>
682
823
 
824
+ <!-- Font size editor -->
825
+ <div role="group" class="fl-rich-text-toolbar-group flex-auto">
826
+ <div class="fl-font-size-editor flex-auto">
827
+ <button
828
+ type="button"
829
+ aria-label="Decrease font size"
830
+ onclick={decrementFontSize}
831
+ class="fl-font-size-editor-button">-</button
832
+ >
833
+ <input type="text" bind:value={fontSize} />
834
+ <button
835
+ type="button"
836
+ aria-label="Increase font size"
837
+ onclick={incrementFontSize}
838
+ class="fl-font-size-editor-button">+</button
839
+ >
840
+ </div>
841
+ </div>
842
+
843
+ <!-- Line height -->
844
+ <div role="group" class="fl-rich-text-toolbar-group">
845
+ <button
846
+ class="fl-font-size-button"
847
+ aria-label="Line height"
848
+ type="button"
849
+ onclick={(e) => toogleDropdown(e.currentTarget, "line-height-dropdown")}
850
+ >
851
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-line-height"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 8l3 -3l3 3" /><path d="M3 16l3 3l3 -3" /><path d="M6 5l0 14" /><path d="M13 6l7 0" /><path d="M13 12l7 0" /><path d="M13 18l7 0" /></svg>
852
+
853
+ <svg
854
+ class="toogle-dropdown-button-icon"
855
+ aria-hidden="true"
856
+ xmlns="http://www.w3.org/2000/svg"
857
+ fill="none"
858
+ viewBox="0 0 10 6"
859
+ >
860
+ <use href="#dropdown-arrow"></use>
861
+ </svg>
862
+ </button>
863
+ </div>
864
+
865
+ <!-- Bold, Italic, Underline, etc. -->
683
866
  <div role="group" class="fl-rich-text-toolbar-group">
684
867
  <button
685
868
  type="button"
@@ -800,7 +983,9 @@
800
983
  </button>
801
984
  </div>
802
985
 
986
+ <!-- Link, special box, horizontal rule, etc. -->
803
987
  <div role="group" class="fl-rich-text-toolbar-group">
988
+ <!-- Link -->
804
989
  <button
805
990
  type="button"
806
991
  onclick={() => setLink()}
@@ -825,6 +1010,7 @@
825
1010
  >
826
1011
  </button>
827
1012
 
1013
+ <!-- Special box -->
828
1014
  <button
829
1015
  class="fl-bubble-menu-mark-button"
830
1016
  class:is-active={$editor?.isActive("specialBox")}
@@ -835,6 +1021,7 @@
835
1021
  <span class="special-box-icon">A</span>
836
1022
  </button>
837
1023
 
1024
+ <!-- Horizontal rule -->
838
1025
  <button
839
1026
  type="button"
840
1027
  onclick={() => $editor.chain().focus().setHorizontalRule().run()}
@@ -855,6 +1042,7 @@
855
1042
  >
856
1043
  </button>
857
1044
 
1045
+ <!-- Hard break -->
858
1046
  <button
859
1047
  type="button"
860
1048
  onclick={() => $editor.chain().focus().setHardBreak().run()}
@@ -877,10 +1065,12 @@
877
1065
  >
878
1066
  </button>
879
1067
 
1068
+ <!-- Text color dropdown -->
880
1069
  <button
881
1070
  aria-label="Toggle text color dropdown"
882
1071
  type="button"
883
- onclick={(e) => toogleDropdown(e.currentTarget, "text-color-dropdown")}
1072
+ onclick={(e) =>
1073
+ toogleDropdown(e.currentTarget, "text-color-dropdown")}
884
1074
  >
885
1075
  <span
886
1076
  class="fl-button-color-text-popover"
@@ -893,18 +1083,63 @@
893
1083
  aria-hidden="true"
894
1084
  xmlns="http://www.w3.org/2000/svg"
895
1085
  fill="none"
896
- viewBox="0 0 10 6"
1086
+ viewBox="0 0 20 12"
1087
+ >
1088
+ <defs>
1089
+ <symbol id="dropdown-arrow" viewBox="0 0 10 6" fill="none">
1090
+ <path
1091
+ stroke="currentColor"
1092
+ stroke-linecap="round"
1093
+ stroke-linejoin="round"
1094
+ stroke-width="2"
1095
+ d="m1 1 4 4 4-4"
1096
+ ></path>
1097
+ </symbol>
1098
+ </defs>
1099
+ <use href="#dropdown-arrow"></use>
1100
+ </svg>
1101
+ </button>
1102
+
1103
+ <!-- Highlight dropdown -->
1104
+ <button
1105
+ class="fl-bubble-menu-mark-button"
1106
+ type="button"
1107
+ aria-label="Highlight"
1108
+ onclick={(e) => toogleDropdown(e.currentTarget, "highlight")}
1109
+ >
1110
+ <svg
1111
+ width="24"
1112
+ height="24"
1113
+ class="tiptap-button-icon"
1114
+ viewBox="0 0 24 24"
1115
+ fill="currentColor"
1116
+ xmlns="http://www.w3.org/2000/svg"
897
1117
  ><path
1118
+ fill-rule="evenodd"
1119
+ clip-rule="evenodd"
1120
+ d="M14.7072 4.70711C15.0977 4.31658 15.0977 3.68342 14.7072 3.29289C14.3167 2.90237 13.6835 2.90237 13.293 3.29289L8.69294 7.89286L8.68594 7.9C8.13626 8.46079 7.82837 9.21474 7.82837 10C7.82837 10.2306 7.85491 10.4584 7.90631 10.6795L2.29289 16.2929C2.10536 16.4804 2 16.7348 2 17V20C2 20.5523 2.44772 21 3 21H12C12.2652 21 12.5196 20.8946 12.7071 20.7071L15.3205 18.0937C15.5416 18.1452 15.7695 18.1717 16.0001 18.1717C16.7853 18.1717 17.5393 17.8639 18.1001 17.3142L22.7072 12.7071C23.0977 12.3166 23.0977 11.6834 22.7072 11.2929C22.3167 10.9024 21.6835 10.9024 21.293 11.2929L16.6971 15.8887C16.5105 16.0702 16.2605 16.1717 16.0001 16.1717C15.7397 16.1717 15.4897 16.0702 15.303 15.8887L10.1113 10.697C9.92992 10.5104 9.82837 10.2604 9.82837 10C9.82837 9.73963 9.92992 9.48958 10.1113 9.30297L14.7072 4.70711ZM13.5858 17L9.00004 12.4142L4 17.4142V19H11.5858L13.5858 17Z"
1121
+ fill="currentColor"
1122
+ ></path>
1123
+ </svg>
1124
+ <svg
1125
+ class="toogle-dropdown-button-icon"
1126
+ aria-hidden="true"
1127
+ xmlns="http://www.w3.org/2000/svg"
1128
+ fill="none"
1129
+ viewBox="0 0 10 6"
1130
+ >
1131
+ <path
898
1132
  stroke="currentColor"
899
1133
  stroke-linecap="round"
900
1134
  stroke-linejoin="round"
901
1135
  stroke-width="2"
902
1136
  d="m1 1 4 4 4-4"
903
- ></path></svg
904
- >
1137
+ ></path>
1138
+ </svg>
905
1139
  </button>
906
1140
  </div>
907
1141
 
1142
+ <!-- Inline math -->
908
1143
  <div role="group" class="fl-rich-text-toolbar-group">
909
1144
  <button
910
1145
  type="button"
@@ -929,7 +1164,9 @@
929
1164
  </button>
930
1165
  </div>
931
1166
 
1167
+ <!-- Text align, clear formatting, clear nodes -->
932
1168
  <div role="group" class="fl-rich-text-toolbar-group">
1169
+ <!-- Text align left -->
933
1170
  <button
934
1171
  type="button"
935
1172
  onclick={() => $editor.chain().focus().toggleTextAlign("left").run()}
@@ -951,6 +1188,7 @@
951
1188
  >
952
1189
  </button>
953
1190
 
1191
+ <!-- Text align center -->
954
1192
  <button
955
1193
  type="button"
956
1194
  onclick={() =>
@@ -973,6 +1211,7 @@
973
1211
  >
974
1212
  </button>
975
1213
 
1214
+ <!-- Text align right -->
976
1215
  <button
977
1216
  type="button"
978
1217
  onclick={() => $editor.chain().focus().toggleTextAlign("right").run()}
@@ -994,6 +1233,7 @@
994
1233
  >
995
1234
  </button>
996
1235
 
1236
+ <!-- Clear formatting -->
997
1237
  <button
998
1238
  aria-label="Clear formatting"
999
1239
  type="button"
@@ -1018,6 +1258,7 @@
1018
1258
  >
1019
1259
  </button>
1020
1260
 
1261
+ <!-- Clear nodes -->
1021
1262
  <button
1022
1263
  type="button"
1023
1264
  onclick={() => $editor.chain().focus().clearNodes().run()}
@@ -1304,7 +1545,10 @@
1304
1545
  onblur={(event: any) => {
1305
1546
  const inclued = recentCustomColors.includes(event?.target?.value);
1306
1547
  if (!inclued) {
1307
- recentCustomColors = [...recentCustomColors, event?.target?.value];
1548
+ recentCustomColors = [
1549
+ ...recentCustomColors,
1550
+ event?.target?.value,
1551
+ ];
1308
1552
  }
1309
1553
  $editor.chain().focus().setColor(event?.target?.value).run();
1310
1554
  hideDropdown();
@@ -1312,7 +1556,10 @@
1312
1556
  onchange={(event: any) => {
1313
1557
  const inclued = recentCustomColors.includes(event?.target?.value);
1314
1558
  if (!inclued) {
1315
- recentCustomColors = [...recentCustomColors, event?.target?.value];
1559
+ recentCustomColors = [
1560
+ ...recentCustomColors,
1561
+ event?.target?.value,
1562
+ ];
1316
1563
  }
1317
1564
  $editor.chain().focus().setColor(event?.target?.value).run();
1318
1565
  hideDropdown();
@@ -1420,178 +1667,156 @@
1420
1667
  </button>
1421
1668
  {/if}
1422
1669
  </div>
1423
- {/if}
1424
- </div>
1425
-
1426
- <style>
1427
- .flex-auto {
1428
- flex: auto;
1429
- }
1430
-
1431
- .fl-rich-text {
1432
- display: flex;
1433
- flex-direction: column;
1434
- width: 100%;
1435
- height: 100%;
1436
- min-height: 56px;
1437
- border-radius: 10px;
1438
- overflow: hidden;
1439
- background-color: var(--bg-color);
1440
- color: var(--text-color);
1441
- border: 1px solid #eeeeee0d;
1442
- box-sizing: border-box;
1443
- }
1444
-
1445
- .fl-rich-text-toolbar {
1446
- display: flex;
1447
- flex-wrap: nowrap;
1448
- overflow: auto;
1449
- align-items: center;
1450
- gap: var(--toolbar-gap);
1451
- padding: var(--toolbar-padding);
1452
- }
1453
-
1454
- .fl-rich-text-toolbar-group {
1455
- display: flex;
1456
- flex-wrap: nowrap;
1457
- gap: var(--toolbar-gap);
1458
-
1459
- button {
1460
- padding: 8px 8px;
1461
- flex: auto;
1462
- border: none;
1463
- background: rgba(255, 255, 255, 0.1);
1464
- border-radius: 8px;
1465
- color: var(--text-color);
1466
- font-size: 14px;
1467
- display: flex;
1468
- align-items: center;
1469
- justify-content: center;
1470
- white-space: nowrap;
1471
- gap: 2px;
1472
- cursor: pointer;
1473
- line-height: 1;
1474
-
1475
- & svg {
1476
- width: 16px;
1477
- height: 16px;
1478
-
1479
- &.toogle-dropdown-button-icon {
1480
- width: 7px;
1481
- height: 7px;
1482
- margin-left: 4px;
1483
- }
1484
- }
1485
-
1486
- &:disabled {
1487
- cursor: not-allowed;
1488
- opacity: 0.5;
1489
- }
1490
- }
1491
- }
1492
-
1493
- button.is-active {
1494
- background: var(--purple);
1495
- color: white;
1496
- }
1497
-
1498
- .special-box-icon {
1499
- width: 16px;
1500
- height: 16px;
1501
- display: flex;
1502
- align-items: center;
1503
- justify-content: center;
1504
- font-weight: 500;
1505
- border: none;
1506
- padding: 0px;
1507
- font-size: 12px;
1508
- border-radius: 3px;
1509
- outline: 1px dashed #818181;
1510
- scale: 1.1;
1511
- }
1512
-
1513
- .fl-button-color-text-popover {
1514
- color: inherit;
1515
- width: 16px;
1516
- height: 16px;
1517
- border-radius: 100%;
1518
- border: 1px solid #d7d7d78a;
1519
- box-sizing: border-box;
1520
- display: flex;
1521
- align-items: center;
1522
- justify-content: center;
1523
- }
1524
-
1525
- .fl-editor-color-palette {
1526
- width: 100%;
1527
- display: grid;
1528
- grid-template-columns: repeat(7, 1fr);
1529
- gap: 6px;
1530
- align-items: center;
1531
-
1532
- .fl-color-swatch {
1533
- display: flex;
1534
- min-width: 17px;
1535
- border-radius: 100%;
1536
- align-items: center;
1537
- justify-content: center;
1538
- outline: 1px solid #83828238;
1539
- position: relative;
1540
- aspect-ratio: 1;
1541
- border: none;
1542
- padding: 0;
1543
- cursor: pointer;
1544
-
1545
- &.active {
1546
- box-shadow: 0 0 0 2px #ffffff30;
1547
- }
1548
-
1549
- &.unset-color::after {
1550
- content: "";
1551
- width: 2px;
1552
- height: 100%;
1553
- background: red;
1554
- position: absolute;
1555
- transform: rotate(30deg) scaleY(1.2);
1556
- }
1557
- }
1670
+ {:else if activeDropdownType === "highlight"}
1671
+ <div class="fl-editor-color-palette">
1672
+ <button
1673
+ class="fl-color-swatch fl-color-picker-btn"
1674
+ aria-label="Highlight color picker"
1675
+ type="button"
1676
+ >
1677
+ <input
1678
+ type="color"
1679
+ onblur={(event: any) => {
1680
+ const inclued = recentCustomColors.includes(event?.target?.value);
1681
+ if (!inclued) {
1682
+ recentCustomColors = [
1683
+ ...recentCustomColors,
1684
+ event?.target?.value,
1685
+ ];
1686
+ }
1687
+ $editor
1688
+ .chain()
1689
+ .focus()
1690
+ .setHighlight({ color: event?.target?.value })
1691
+ .run();
1692
+ hideDropdown();
1693
+ }}
1694
+ onchange={(event: any) => {
1695
+ const inclued = recentCustomColors.includes(event?.target?.value);
1696
+ if (!inclued) {
1697
+ recentCustomColors = [
1698
+ ...recentCustomColors,
1699
+ event?.target?.value,
1700
+ ];
1701
+ }
1558
1702
 
1559
- input[type="color"] {
1560
- display: inline-flex;
1561
- vertical-align: bottom;
1562
- border: none;
1563
- border-radius: var(--radius);
1564
- padding: 0;
1565
- min-width: 17px;
1566
- max-height: 17px;
1567
- aspect-ratio: 1;
1568
- background: transparent;
1569
- width: auto;
1570
- border-radius: 100%;
1571
-
1572
- &::-webkit-color-swatch-wrapper {
1573
- padding: 0;
1574
- }
1703
+ $editor
1704
+ .chain()
1705
+ .focus()
1706
+ .setHighlight({ color: event?.target?.value })
1707
+ .run();
1708
+ hideDropdown();
1709
+ }}
1710
+ value={rgbToHex($editor?.getAttributes("textStyle")?.color)}
1711
+ data-testid="setHiglight"
1712
+ id="colorPicker"
1713
+ />
1714
+ </button>
1575
1715
 
1576
- &::-webkit-color-swatch {
1577
- border: 0;
1578
- border-radius: var(--radius);
1579
- }
1716
+ {#each HIGHLIGHT_COLOR_PALETTE as color}
1717
+ <button
1718
+ class="fl-color-swatch"
1719
+ class:active={$editor?.isActive("textStyle", {
1720
+ color: color,
1721
+ })}
1722
+ onclick={() => {
1723
+ $editor?.chain().focus().setHighlight({ color }).run();
1724
+ hideDropdown();
1725
+ }}
1726
+ style="background-color: {color};"
1727
+ aria-label={color}
1728
+ >
1729
+ </button>
1730
+ {/each}
1580
1731
 
1581
- &::-moz-color-swatch {
1582
- border: 0;
1583
- border-radius: var(--radius);
1584
- }
1585
- }
1732
+ <button
1733
+ class="fl-color-swatch unset-color"
1734
+ onclick={() => {
1735
+ $editor?.chain().focus().unsetColor().run();
1736
+ hideDropdown();
1737
+ }}
1738
+ style="background-color: #ffffff;"
1739
+ aria-label="Unset color"
1740
+ >
1741
+ </button>
1586
1742
 
1587
- .fl-color-picker-btn {
1588
- cursor: pointer;
1589
- position: relative;
1590
- background: conic-gradient(in hsl longer hue, red 0 100%);
1743
+ {#if recentCustomColors.length > 0}
1744
+ {#each recentCustomColors as color}
1745
+ <button
1746
+ class="fl-color-swatch"
1747
+ class:active={$editor?.isActive("textStyle", {
1748
+ color: color,
1749
+ })}
1750
+ onclick={() => {
1751
+ $editor?.chain().focus().setHighlight({ color }).run();
1752
+ hideDropdown();
1753
+ }}
1754
+ style="background-color: {color};"
1755
+ aria-label={color}
1756
+ >
1757
+ </button>
1758
+ {/each}
1759
+ {:else}
1760
+ <button
1761
+ class="fl-color-swatch"
1762
+ style="outline: 1px dashed #ffffff66;background: transparent;"
1763
+ onclick={() => alert("Not implemented yet")}
1764
+ aria-label="Add new color"
1765
+ >
1766
+ <svg
1767
+ xmlns="http://www.w3.org/2000/svg"
1768
+ fill="none"
1769
+ viewBox="0 0 24 24"
1770
+ stroke-width="1.5"
1771
+ stroke="currentColor"
1772
+ class="size-6"
1773
+ style="
1774
+ width: 11px;
1775
+ height: 11px;
1776
+ "
1777
+ >
1778
+ <path
1779
+ stroke-linecap="round"
1780
+ stroke-linejoin="round"
1781
+ d="M12 4.5v15m7.5-7.5h-15"
1782
+ ></path>
1783
+ </svg>
1784
+ </button>
1591
1785
 
1592
- & input {
1593
- opacity: 0;
1594
- }
1595
- }
1596
- }
1597
- </style>
1786
+ <button
1787
+ class="fl-color-swatch"
1788
+ style="outline: 1px dashed #ffffff66;background: transparent;"
1789
+ onclick={() => alert("Not implemented yet")}
1790
+ aria-label="Add new color"
1791
+ >
1792
+ <svg
1793
+ xmlns="http://www.w3.org/2000/svg"
1794
+ fill="none"
1795
+ viewBox="0 0 24 24"
1796
+ stroke-width="1.5"
1797
+ stroke="currentColor"
1798
+ class="size-6"
1799
+ style="
1800
+ width: 11px;
1801
+ height: 11px;
1802
+ "
1803
+ >
1804
+ <path
1805
+ stroke-linecap="round"
1806
+ stroke-linejoin="round"
1807
+ d="M12 4.5v15m7.5-7.5h-15"
1808
+ ></path>
1809
+ </svg>
1810
+ </button>
1811
+ {/if}
1812
+ </div>
1813
+ {:else if activeDropdownType === "line-height-dropdown"}
1814
+ <div class="fl-range-element">
1815
+ <span class="fl-range-element-value">
1816
+ {lineHeight.toFixed(2)}
1817
+ </span>
1818
+ <input oninput={handleRangeInput} type="range" min="0.5" max="4" step="0.01" bind:value={lineHeight}>
1819
+ </div>
1820
+
1821
+ {/if}
1822
+ </div>
@@ -1,6 +1,6 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  import "./styles.css";
3
- import 'katex/dist/katex.min.css';
3
+ import "katex/dist/katex.min.css";
4
4
  declare const __propDef: {
5
5
  props: Record<string, never>;
6
6
  events: {
@@ -0,0 +1,10 @@
1
+ import { Extension } from '@tiptap/core';
2
+ declare module '@tiptap/core' {
3
+ interface Commands<ReturnType> {
4
+ lineHeight: {
5
+ setNodeLineHeight: (lineHeight: string) => ReturnType;
6
+ unsetNodeLineHeight: () => ReturnType;
7
+ };
8
+ }
9
+ }
10
+ export declare const NodeLineHeight: Extension<any, any>;
@@ -0,0 +1,41 @@
1
+ import { Extension } from '@tiptap/core';
2
+ export const NodeLineHeight = Extension.create({
3
+ name: 'nodeLineHeight',
4
+ addOptions() {
5
+ return {
6
+ types: ['paragraph', 'heading'], // Aplica a párrafos y encabezados
7
+ defaultLineHeight: 'normal',
8
+ };
9
+ },
10
+ addGlobalAttributes() {
11
+ return [
12
+ {
13
+ types: this.options.types,
14
+ attributes: {
15
+ lineHeight: {
16
+ default: this.options.defaultLineHeight,
17
+ parseHTML: element => element.style.lineHeight || this.options.defaultLineHeight,
18
+ renderHTML: attributes => {
19
+ if (!attributes.lineHeight || attributes.lineHeight === this.options.defaultLineHeight) {
20
+ return {};
21
+ }
22
+ return {
23
+ style: `line-height: ${attributes.lineHeight}`,
24
+ };
25
+ },
26
+ },
27
+ },
28
+ },
29
+ ];
30
+ },
31
+ addCommands() {
32
+ return {
33
+ setNodeLineHeight: (lineHeight) => ({ commands }) => {
34
+ return this.options.types.every((type) => commands.updateAttributes(type, { lineHeight }));
35
+ },
36
+ unsetNodeLineHeight: () => ({ commands }) => {
37
+ return this.options.types.every((type) => commands.resetAttributes(type, 'lineHeight'));
38
+ },
39
+ };
40
+ },
41
+ });
package/dist/styles.css CHANGED
@@ -7,7 +7,8 @@
7
7
  --gray-2: #e0e0e0;
8
8
  --gray-3: #c0c0c0;
9
9
  --toolbar-gap: 5px;
10
- --toolbar-padding: 5px;
10
+ --toolbar-padding: 6px;
11
+ --fl-editor-radius: 12px;
11
12
  }
12
13
 
13
14
  /* Basic editor styles */
@@ -182,6 +183,7 @@
182
183
  min-height: 56px;
183
184
  padding: 2rem;
184
185
  background: #242424;
186
+ border-radius: 0 0 var(--fl-editor-radius) var(--fl-editor-radius);
185
187
  }
186
188
 
187
189
  .fl-toolbar-dropdown-panel {
@@ -211,3 +213,234 @@
211
213
  background-color: #d4d6ff3c;
212
214
  }
213
215
  }
216
+
217
+
218
+ .flex-auto {
219
+ flex: auto;
220
+ }
221
+
222
+ .fl-rich-text {
223
+ display: flex;
224
+ flex-direction: column;
225
+ width: 100%;
226
+ height: 100%;
227
+ min-height: 56px;
228
+ background-color: var(--fl-bg-color, #242424);
229
+ color: var(--text-color);
230
+ box-sizing: border-box;
231
+ border-radius: var(--fl-editor-radius);
232
+ }
233
+
234
+ .fl-rich-text-toolbar {
235
+ display: flex;
236
+ flex-wrap: nowrap;
237
+ overflow: auto;
238
+ align-items: center;
239
+ gap: var(--toolbar-gap);
240
+ padding: var(--toolbar-padding);
241
+ position: sticky;
242
+ top: var(--sticky-position, 0);
243
+ z-index: var(--fl-toolbar-z-index, 10);
244
+ background: var(--fl-toolbar-bg, #242424);
245
+ border-radius: var(--fl-editor-radius);
246
+ }
247
+
248
+ .fl-rich-text-toolbar-group {
249
+ display: flex;
250
+ flex-wrap: nowrap;
251
+ gap: var(--toolbar-gap);
252
+
253
+ button {
254
+ padding: 8px 8px;
255
+ flex: auto;
256
+ border: none;
257
+ background: rgba(255, 255, 255, 0.1);
258
+ border-radius: 8px;
259
+ color: var(--text-color);
260
+ font-size: 14px;
261
+ display: flex;
262
+ align-items: center;
263
+ justify-content: center;
264
+ white-space: nowrap;
265
+ gap: 2px;
266
+ cursor: pointer;
267
+ line-height: 1;
268
+ min-height: 32px;
269
+
270
+ &.fl-font-size-button {
271
+ font-size: 14px;
272
+ }
273
+
274
+ & svg {
275
+ width: 16px;
276
+ height: 16px;
277
+
278
+ &.toogle-dropdown-button-icon {
279
+ width: 7px;
280
+ height: 7px;
281
+ margin-left: 4px;
282
+ }
283
+ }
284
+
285
+ &:disabled {
286
+ cursor: not-allowed;
287
+ opacity: 0.5;
288
+ }
289
+ }
290
+ }
291
+
292
+ button.is-active {
293
+ background: var(--purple);
294
+ color: white;
295
+ }
296
+
297
+ .special-box-icon {
298
+ width: 16px;
299
+ height: 16px;
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: center;
303
+ font-weight: 500;
304
+ border: none;
305
+ padding: 0px;
306
+ font-size: 12px;
307
+ border-radius: 3px;
308
+ outline: 1px dashed #818181;
309
+ scale: 1.1;
310
+ }
311
+
312
+ .fl-button-color-text-popover {
313
+ color: inherit;
314
+ width: 16px;
315
+ height: 16px;
316
+ border-radius: 100%;
317
+ border: 1px solid #d7d7d78a;
318
+ box-sizing: border-box;
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ }
323
+
324
+ .fl-editor-color-palette {
325
+ width: 100%;
326
+ display: grid;
327
+ grid-template-columns: repeat(7, 1fr);
328
+ gap: 6px;
329
+ align-items: center;
330
+
331
+ .fl-color-swatch {
332
+ display: flex;
333
+ min-width: 17px;
334
+ border-radius: 100%;
335
+ align-items: center;
336
+ justify-content: center;
337
+ outline: 1px solid #83828238;
338
+ position: relative;
339
+ aspect-ratio: 1;
340
+ border: none;
341
+ padding: 0;
342
+ cursor: pointer;
343
+
344
+ &.active {
345
+ box-shadow: 0 0 0 2px #ffffff30;
346
+ }
347
+
348
+ &.unset-color::after {
349
+ content: "";
350
+ width: 2px;
351
+ height: 100%;
352
+ background: red;
353
+ position: absolute;
354
+ transform: rotate(30deg) scaleY(1.2);
355
+ }
356
+ }
357
+
358
+ input[type="color"] {
359
+ display: inline-flex;
360
+ vertical-align: bottom;
361
+ border: none;
362
+ border-radius: var(--radius);
363
+ padding: 0;
364
+ min-width: 17px;
365
+ max-height: 17px;
366
+ aspect-ratio: 1;
367
+ background: transparent;
368
+ width: auto;
369
+ border-radius: 100%;
370
+
371
+ &::-webkit-color-swatch-wrapper {
372
+ padding: 0;
373
+ }
374
+
375
+ &::-webkit-color-swatch {
376
+ border: 0;
377
+ border-radius: var(--radius);
378
+ }
379
+
380
+ &::-moz-color-swatch {
381
+ border: 0;
382
+ border-radius: var(--radius);
383
+ }
384
+ }
385
+
386
+ .fl-color-picker-btn {
387
+ cursor: pointer;
388
+ position: relative;
389
+ background: conic-gradient(in hsl longer hue, red 0 100%);
390
+
391
+ & input {
392
+ opacity: 0;
393
+ }
394
+ }
395
+ }
396
+
397
+ .fl-font-size-editor {
398
+ display: flex;
399
+ align-items: center;
400
+ gap: 4px;
401
+ padding: 0 8px;
402
+ border-radius: 8px;
403
+ background: rgba(255, 255, 255, 0.1);
404
+
405
+ & button {
406
+ background: transparent;
407
+ border: none;
408
+ display: flex;
409
+ align-items: center;
410
+ justify-content: center;
411
+ padding: 2px;
412
+ backdrop-filter: blur(5px);
413
+ border-radius: 8px;
414
+ cursor: pointer;
415
+
416
+ & svg {
417
+ width: 18px;
418
+ height: 18px;
419
+ }
420
+ }
421
+
422
+ input {
423
+ width: 32px;
424
+ text-align: center;
425
+ border: none;
426
+ background: transparent;
427
+ color: var(--text-color);
428
+ font-size: 14px;
429
+ padding: 0;
430
+ }
431
+ }
432
+
433
+ .fl-range-element {
434
+ display: flex;
435
+ align-items: center;
436
+ gap: 8px;
437
+ }
438
+
439
+ .fl-range-element input {
440
+ accent-color: #ffffff;
441
+ }
442
+
443
+ .fl-range-element-value {
444
+ padding: 0 6px;
445
+ font-size: 14px;
446
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flexiui/svelte-rich-text",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "A lightweight and flexible rich text editor component for Svelte",
5
5
  "keywords": [
6
6
  "svelte",