@flexiui/svelte-rich-text 0.0.19 → 0.0.20

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,44 @@
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
+ onclick={decrementFontSize}
829
+ class="fl-font-size-editor-button">-</button
830
+ >
831
+ <input type="text" bind:value={fontSize} />
832
+ <button
833
+ onclick={incrementFontSize}
834
+ class="fl-font-size-editor-button">+</button
835
+ >
836
+ </div>
837
+ </div>
838
+
839
+ <!-- Line height -->
840
+ <div role="group" class="fl-rich-text-toolbar-group">
841
+ <button
842
+ class="fl-font-size-button"
843
+ aria-label="Line height"
844
+ type="button"
845
+ onclick={(e) => toogleDropdown(e.currentTarget, "line-height-dropdown")}
846
+ >
847
+ <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>
848
+
849
+ <svg
850
+ class="toogle-dropdown-button-icon"
851
+ aria-hidden="true"
852
+ xmlns="http://www.w3.org/2000/svg"
853
+ fill="none"
854
+ viewBox="0 0 10 6"
855
+ >
856
+ <use href="#dropdown-arrow"></use>
857
+ </svg>
858
+ </button>
859
+ </div>
860
+
861
+ <!-- Bold, Italic, Underline, etc. -->
683
862
  <div role="group" class="fl-rich-text-toolbar-group">
684
863
  <button
685
864
  type="button"
@@ -800,7 +979,9 @@
800
979
  </button>
801
980
  </div>
802
981
 
982
+ <!-- Link, special box, horizontal rule, etc. -->
803
983
  <div role="group" class="fl-rich-text-toolbar-group">
984
+ <!-- Link -->
804
985
  <button
805
986
  type="button"
806
987
  onclick={() => setLink()}
@@ -825,6 +1006,7 @@
825
1006
  >
826
1007
  </button>
827
1008
 
1009
+ <!-- Special box -->
828
1010
  <button
829
1011
  class="fl-bubble-menu-mark-button"
830
1012
  class:is-active={$editor?.isActive("specialBox")}
@@ -835,6 +1017,7 @@
835
1017
  <span class="special-box-icon">A</span>
836
1018
  </button>
837
1019
 
1020
+ <!-- Horizontal rule -->
838
1021
  <button
839
1022
  type="button"
840
1023
  onclick={() => $editor.chain().focus().setHorizontalRule().run()}
@@ -855,6 +1038,7 @@
855
1038
  >
856
1039
  </button>
857
1040
 
1041
+ <!-- Hard break -->
858
1042
  <button
859
1043
  type="button"
860
1044
  onclick={() => $editor.chain().focus().setHardBreak().run()}
@@ -877,10 +1061,12 @@
877
1061
  >
878
1062
  </button>
879
1063
 
1064
+ <!-- Text color dropdown -->
880
1065
  <button
881
1066
  aria-label="Toggle text color dropdown"
882
1067
  type="button"
883
- onclick={(e) => toogleDropdown(e.currentTarget, "text-color-dropdown")}
1068
+ onclick={(e) =>
1069
+ toogleDropdown(e.currentTarget, "text-color-dropdown")}
884
1070
  >
885
1071
  <span
886
1072
  class="fl-button-color-text-popover"
@@ -893,18 +1079,63 @@
893
1079
  aria-hidden="true"
894
1080
  xmlns="http://www.w3.org/2000/svg"
895
1081
  fill="none"
896
- viewBox="0 0 10 6"
1082
+ viewBox="0 0 20 12"
1083
+ >
1084
+ <defs>
1085
+ <symbol id="dropdown-arrow" viewBox="0 0 10 6" fill="none">
1086
+ <path
1087
+ stroke="currentColor"
1088
+ stroke-linecap="round"
1089
+ stroke-linejoin="round"
1090
+ stroke-width="2"
1091
+ d="m1 1 4 4 4-4"
1092
+ ></path>
1093
+ </symbol>
1094
+ </defs>
1095
+ <use href="#dropdown-arrow"></use>
1096
+ </svg>
1097
+ </button>
1098
+
1099
+ <!-- Highlight dropdown -->
1100
+ <button
1101
+ class="fl-bubble-menu-mark-button"
1102
+ type="button"
1103
+ aria-label="Highlight"
1104
+ onclick={(e) => toogleDropdown(e.currentTarget, "highlight")}
1105
+ >
1106
+ <svg
1107
+ width="24"
1108
+ height="24"
1109
+ class="tiptap-button-icon"
1110
+ viewBox="0 0 24 24"
1111
+ fill="currentColor"
1112
+ xmlns="http://www.w3.org/2000/svg"
897
1113
  ><path
1114
+ fill-rule="evenodd"
1115
+ clip-rule="evenodd"
1116
+ 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"
1117
+ fill="currentColor"
1118
+ ></path>
1119
+ </svg>
1120
+ <svg
1121
+ class="toogle-dropdown-button-icon"
1122
+ aria-hidden="true"
1123
+ xmlns="http://www.w3.org/2000/svg"
1124
+ fill="none"
1125
+ viewBox="0 0 10 6"
1126
+ >
1127
+ <path
898
1128
  stroke="currentColor"
899
1129
  stroke-linecap="round"
900
1130
  stroke-linejoin="round"
901
1131
  stroke-width="2"
902
1132
  d="m1 1 4 4 4-4"
903
- ></path></svg
904
- >
1133
+ ></path>
1134
+ </svg>
905
1135
  </button>
906
1136
  </div>
907
1137
 
1138
+ <!-- Inline math -->
908
1139
  <div role="group" class="fl-rich-text-toolbar-group">
909
1140
  <button
910
1141
  type="button"
@@ -929,7 +1160,9 @@
929
1160
  </button>
930
1161
  </div>
931
1162
 
1163
+ <!-- Text align, clear formatting, clear nodes -->
932
1164
  <div role="group" class="fl-rich-text-toolbar-group">
1165
+ <!-- Text align left -->
933
1166
  <button
934
1167
  type="button"
935
1168
  onclick={() => $editor.chain().focus().toggleTextAlign("left").run()}
@@ -951,6 +1184,7 @@
951
1184
  >
952
1185
  </button>
953
1186
 
1187
+ <!-- Text align center -->
954
1188
  <button
955
1189
  type="button"
956
1190
  onclick={() =>
@@ -973,6 +1207,7 @@
973
1207
  >
974
1208
  </button>
975
1209
 
1210
+ <!-- Text align right -->
976
1211
  <button
977
1212
  type="button"
978
1213
  onclick={() => $editor.chain().focus().toggleTextAlign("right").run()}
@@ -994,6 +1229,7 @@
994
1229
  >
995
1230
  </button>
996
1231
 
1232
+ <!-- Clear formatting -->
997
1233
  <button
998
1234
  aria-label="Clear formatting"
999
1235
  type="button"
@@ -1018,6 +1254,7 @@
1018
1254
  >
1019
1255
  </button>
1020
1256
 
1257
+ <!-- Clear nodes -->
1021
1258
  <button
1022
1259
  type="button"
1023
1260
  onclick={() => $editor.chain().focus().clearNodes().run()}
@@ -1304,7 +1541,10 @@
1304
1541
  onblur={(event: any) => {
1305
1542
  const inclued = recentCustomColors.includes(event?.target?.value);
1306
1543
  if (!inclued) {
1307
- recentCustomColors = [...recentCustomColors, event?.target?.value];
1544
+ recentCustomColors = [
1545
+ ...recentCustomColors,
1546
+ event?.target?.value,
1547
+ ];
1308
1548
  }
1309
1549
  $editor.chain().focus().setColor(event?.target?.value).run();
1310
1550
  hideDropdown();
@@ -1312,7 +1552,10 @@
1312
1552
  onchange={(event: any) => {
1313
1553
  const inclued = recentCustomColors.includes(event?.target?.value);
1314
1554
  if (!inclued) {
1315
- recentCustomColors = [...recentCustomColors, event?.target?.value];
1555
+ recentCustomColors = [
1556
+ ...recentCustomColors,
1557
+ event?.target?.value,
1558
+ ];
1316
1559
  }
1317
1560
  $editor.chain().focus().setColor(event?.target?.value).run();
1318
1561
  hideDropdown();
@@ -1420,178 +1663,156 @@
1420
1663
  </button>
1421
1664
  {/if}
1422
1665
  </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
- }
1666
+ {:else if activeDropdownType === "highlight"}
1667
+ <div class="fl-editor-color-palette">
1668
+ <button
1669
+ class="fl-color-swatch fl-color-picker-btn"
1670
+ aria-label="Highlight color picker"
1671
+ type="button"
1672
+ >
1673
+ <input
1674
+ type="color"
1675
+ onblur={(event: any) => {
1676
+ const inclued = recentCustomColors.includes(event?.target?.value);
1677
+ if (!inclued) {
1678
+ recentCustomColors = [
1679
+ ...recentCustomColors,
1680
+ event?.target?.value,
1681
+ ];
1682
+ }
1683
+ $editor
1684
+ .chain()
1685
+ .focus()
1686
+ .setHighlight({ color: event?.target?.value })
1687
+ .run();
1688
+ hideDropdown();
1689
+ }}
1690
+ onchange={(event: any) => {
1691
+ const inclued = recentCustomColors.includes(event?.target?.value);
1692
+ if (!inclued) {
1693
+ recentCustomColors = [
1694
+ ...recentCustomColors,
1695
+ event?.target?.value,
1696
+ ];
1697
+ }
1558
1698
 
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
- }
1699
+ $editor
1700
+ .chain()
1701
+ .focus()
1702
+ .setHighlight({ color: event?.target?.value })
1703
+ .run();
1704
+ hideDropdown();
1705
+ }}
1706
+ value={rgbToHex($editor?.getAttributes("textStyle")?.color)}
1707
+ data-testid="setHiglight"
1708
+ id="colorPicker"
1709
+ />
1710
+ </button>
1575
1711
 
1576
- &::-webkit-color-swatch {
1577
- border: 0;
1578
- border-radius: var(--radius);
1579
- }
1712
+ {#each HIGHLIGHT_COLOR_PALETTE as color}
1713
+ <button
1714
+ class="fl-color-swatch"
1715
+ class:active={$editor?.isActive("textStyle", {
1716
+ color: color,
1717
+ })}
1718
+ onclick={() => {
1719
+ $editor?.chain().focus().setHighlight({ color }).run();
1720
+ hideDropdown();
1721
+ }}
1722
+ style="background-color: {color};"
1723
+ aria-label={color}
1724
+ >
1725
+ </button>
1726
+ {/each}
1580
1727
 
1581
- &::-moz-color-swatch {
1582
- border: 0;
1583
- border-radius: var(--radius);
1584
- }
1585
- }
1728
+ <button
1729
+ class="fl-color-swatch unset-color"
1730
+ onclick={() => {
1731
+ $editor?.chain().focus().unsetColor().run();
1732
+ hideDropdown();
1733
+ }}
1734
+ style="background-color: #ffffff;"
1735
+ aria-label="Unset color"
1736
+ >
1737
+ </button>
1586
1738
 
1587
- .fl-color-picker-btn {
1588
- cursor: pointer;
1589
- position: relative;
1590
- background: conic-gradient(in hsl longer hue, red 0 100%);
1739
+ {#if recentCustomColors.length > 0}
1740
+ {#each recentCustomColors as color}
1741
+ <button
1742
+ class="fl-color-swatch"
1743
+ class:active={$editor?.isActive("textStyle", {
1744
+ color: color,
1745
+ })}
1746
+ onclick={() => {
1747
+ $editor?.chain().focus().setHighlight({ color }).run();
1748
+ hideDropdown();
1749
+ }}
1750
+ style="background-color: {color};"
1751
+ aria-label={color}
1752
+ >
1753
+ </button>
1754
+ {/each}
1755
+ {:else}
1756
+ <button
1757
+ class="fl-color-swatch"
1758
+ style="outline: 1px dashed #ffffff66;background: transparent;"
1759
+ onclick={() => alert("Not implemented yet")}
1760
+ aria-label="Add new color"
1761
+ >
1762
+ <svg
1763
+ xmlns="http://www.w3.org/2000/svg"
1764
+ fill="none"
1765
+ viewBox="0 0 24 24"
1766
+ stroke-width="1.5"
1767
+ stroke="currentColor"
1768
+ class="size-6"
1769
+ style="
1770
+ width: 11px;
1771
+ height: 11px;
1772
+ "
1773
+ >
1774
+ <path
1775
+ stroke-linecap="round"
1776
+ stroke-linejoin="round"
1777
+ d="M12 4.5v15m7.5-7.5h-15"
1778
+ ></path>
1779
+ </svg>
1780
+ </button>
1591
1781
 
1592
- & input {
1593
- opacity: 0;
1594
- }
1595
- }
1596
- }
1597
- </style>
1782
+ <button
1783
+ class="fl-color-swatch"
1784
+ style="outline: 1px dashed #ffffff66;background: transparent;"
1785
+ onclick={() => alert("Not implemented yet")}
1786
+ aria-label="Add new color"
1787
+ >
1788
+ <svg
1789
+ xmlns="http://www.w3.org/2000/svg"
1790
+ fill="none"
1791
+ viewBox="0 0 24 24"
1792
+ stroke-width="1.5"
1793
+ stroke="currentColor"
1794
+ class="size-6"
1795
+ style="
1796
+ width: 11px;
1797
+ height: 11px;
1798
+ "
1799
+ >
1800
+ <path
1801
+ stroke-linecap="round"
1802
+ stroke-linejoin="round"
1803
+ d="M12 4.5v15m7.5-7.5h-15"
1804
+ ></path>
1805
+ </svg>
1806
+ </button>
1807
+ {/if}
1808
+ </div>
1809
+ {:else if activeDropdownType === "line-height-dropdown"}
1810
+ <div class="fl-range-element">
1811
+ <span class="fl-range-element-value">
1812
+ {lineHeight.toFixed(2)}
1813
+ </span>
1814
+ <input oninput={handleRangeInput} type="range" min="0.5" max="4" step="0.01" bind:value={lineHeight}>
1815
+ </div>
1816
+
1817
+ {/if}
1818
+ </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.20",
4
4
  "description": "A lightweight and flexible rich text editor component for Svelte",
5
5
  "keywords": [
6
6
  "svelte",