@sikka/hawa 0.0.237 → 0.0.238

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.
@@ -47,9 +47,11 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
47
47
  content: "",
48
48
  stylings: [], // A styling is an object with 2 indices specifying a substring of text, and the applied effect
49
49
  revert: [0, 0],
50
- lastCopy: [],
51
- pasted: { status: false, length: 0 },
52
- dropped: { status: false, text: "", previous: [0, 0] },
50
+ clipboard: [],
51
+ events: {
52
+ paste: { is: false, length: null },
53
+ drop: { is: false, text: null, drag: null },
54
+ },
53
55
  })
54
56
 
55
57
  const field = useRef(null)
@@ -112,53 +114,10 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
112
114
  if (document.activeElement != field.current) return [0, 0]
113
115
 
114
116
  let selection = window.getSelection()
115
- // let nodes = Array.from(field.current.childNodes)
116
-
117
- // console.log(nodes)
118
-
119
- // nodes = nodes.filter(
120
- // (item: any) => !["#text", "BR"].includes(item.nodeName)
121
- // )
122
-
123
- // console.log(selection.anchorNode)
124
- // console.log(selection.focusNode)
125
- // console.log(selection.anchorNode.parentNode)
126
- // console.log(selection.focusNode.parentNode)
127
- // console.log(selection.anchorNode.parentNode.parentNode)
128
- // console.log(selection.focusNode.parentNode.parentNode)
129
- // console.log(selection.anchorOffset)
130
- // console.log(selection.focusOffset)
131
-
132
- // let startParent: any = selection.anchorNode.parentNode
133
-
134
- // let startNodeIndex =
135
- // startParent == field.current
136
- // ? nodes.length
137
- // : // : parseInt(startParent.dataset.childIndex)
138
- // getChildIndex(startParent)
139
-
140
- // let startPrecedingSum = nodes
141
- // .slice(0, startNodeIndex)
142
- // .map((span: any) => span.textContent.length)
143
- // .reduce((a, b) => a + b, 0)
144
117
 
145
118
  let startPrecedingSum = getSelectionPrecedingSum("anchorNode")
146
-
147
- // let endParent: any = selection.focusNode.parentNode
148
- // let endNodeIndex =
149
- // endParent == field.current
150
- // ? nodes.length
151
- // : // : parseInt(endParent.dataset.childIndex)
152
- // getChildIndex(endParent)
153
-
154
- // let endPrecedingSum = nodes
155
- // .slice(0, endNodeIndex)
156
- // .map((span: any) => span.textContent.length)
157
- // .reduce((a, b) => a + b, 0)
158
-
159
119
  let endPrecedingSum = getSelectionPrecedingSum("focusNode")
160
120
 
161
- console.log([startPrecedingSum, endPrecedingSum])
162
121
  let result = [
163
122
  startPrecedingSum + selection.anchorOffset,
164
123
  endPrecedingSum + selection.focusOffset,
@@ -369,36 +328,6 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
369
328
  })
370
329
  }
371
330
 
372
- const getRangeIntersectStylings = (start, end, startOffset, finishOffset) => {
373
- // Get all stylings intersecting
374
- let stylings = []
375
- for (let i = start; i < end; i++) {
376
- stylings.push(getIntersectStylings(i, startOffset, finishOffset))
377
- }
378
-
379
- // Remove duplicates
380
- stylings = stylings.flat().filter((item) => item)
381
- stylings = stylings.filter(
382
- (item, index) =>
383
- stylings.findIndex(
384
- (_item) =>
385
- _item.start == item.start &&
386
- _item.finish == item.finish &&
387
- _item.type == item.type
388
- ) == index
389
- )
390
-
391
- return stylings
392
- }
393
-
394
- const compareStylings = (styling1, styling2) => {
395
- return (
396
- styling1.type == styling2.type &&
397
- styling1.start == styling2.start &&
398
- styling1.finish == styling2.finish
399
- )
400
- }
401
-
402
331
  // Get stylings encompassing an index within it's range
403
332
  const getIntersectStylings = (index, startOffset = 0, finishOffset = 0) => {
404
333
  // Find all stylings with encompassing range
@@ -410,647 +339,306 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
410
339
  return matches
411
340
  }
412
341
 
413
- // Get stylings after an index
414
- const getSucceedStylings = (index) => {
415
- // Find all stylings after the index
416
- let matches = text.stylings.filter(({ start, finish }) => start >= index)
417
-
418
- return matches
419
- }
420
-
421
- const getStylingIndex = (styling) => {
422
- return text.stylings.findIndex(
423
- (item) =>
424
- item.start == styling.start &&
425
- item.finish == styling.finish &&
426
- item.type == styling.type
427
- )
428
- }
429
-
430
- const handleScenario = (stylings, predicate) => {
431
- let matches = stylings.filter(({ start: _start, finish: _finish }) =>
432
- predicate(_start, _finish)
433
- )
434
-
435
- let indices = []
436
-
437
- stylings.map((styling, index) => {
438
- let result = matches.find((item) => compareStylings(styling, item))
439
- if (!result) return
440
- indices.push({
441
- index: index,
442
- type: styling.type,
443
- start: styling.start,
444
- finish: styling.finish,
445
- })
446
- })
447
-
448
- return indices
449
- }
450
-
451
- const handleDeletion = (length, start, end) => {
452
- let stylings = _text.current.stylings.slice()
453
- let changes = []
454
-
455
- // TODO: Refactor
456
-
457
- // Offset all succeeding stylings by length
458
- // changes.push(
459
- // handleScenario(
460
- // stylings,
461
- // (_start, _finish) => start < _start && end <= start
462
- // ).map((styling) => {
463
- // return {
464
- // ...styling,
465
- // start: styling.start - length,
466
- // finish: styling.finish - length,
467
- // }
468
- // })
469
- // )
470
-
471
- let succeeding = stylings.filter(
472
- ({ start: _start }) => start < _start && end <= _start
473
- )
474
- stylings.map((styling, index) => {
475
- let result = succeeding.find((item) => compareStylings(styling, item))
476
- if (!result) return
477
-
478
- changes.push({
479
- index: index,
480
- start: styling.start - length,
481
- finish: styling.finish - length,
482
- })
483
- })
484
-
485
- // Handle complete encapsulation over styling
486
- let encapsulating = stylings.filter(
487
- ({ start: _start, finish: _finish }) => start <= _start && end >= _finish
488
- )
489
- stylings.map((styling, index) => {
490
- let result = encapsulating.find((item) => compareStylings(styling, item))
491
- if (!result) return
492
-
493
- // This will effectively remove the styling, since collapsed ranges are automatically removed
494
- changes.push({
495
- index: index,
496
- start: styling.start,
497
- finish: styling.start,
498
- })
499
- })
500
-
501
- // Handle deletion being encapsulated by styling
502
- let encapsulated = stylings.filter(
503
- ({ start: _start, finish: _finish }) =>
504
- (start > _start && end <= _finish) || (start >= _start && end < _finish)
505
- )
506
- stylings.map((styling, index) => {
507
- let result = encapsulated.find((item) => compareStylings(styling, item))
508
- if (!result) return
509
-
510
- changes.push({
511
- index: index,
512
- start: styling.start,
513
- finish: styling.finish - length,
514
- })
515
- })
516
-
517
- // Handle deletion being encapsulated by styling with left resumption
518
- let leftResumption = stylings.filter(
519
- ({ start: _start, finish: _finish }) =>
520
- end < _finish && end > _start && start < _start
521
- )
522
- stylings.map((styling, index) => {
523
- let result = leftResumption.find((item) => compareStylings(styling, item))
524
- if (!result) return
525
-
526
- changes.push({
527
- index: index,
528
- start: getMaximum([end, styling.start]) - length,
529
- finish: styling.finish - length,
530
- })
531
- })
532
-
533
- let rightResumption = stylings.filter(
534
- ({ start: _start, finish: _finish }) =>
535
- start > _start && start < _finish && end > _finish
342
+ const getStylingsInRange = (stylings, startIndex, endIndex) => {
343
+ // Get all intersecting stylings within range
344
+ let result = stylings.filter(
345
+ ({ start, finish }) =>
346
+ (finish > startIndex && start < endIndex) ||
347
+ (start < endIndex && finish > startIndex)
536
348
  )
537
- stylings.map((styling, index) => {
538
- let result = rightResumption.find((item) =>
539
- compareStylings(styling, item)
540
- )
541
- if (!result) return
542
349
 
543
- changes.push({
544
- index: index,
545
- start: styling.start,
546
- finish: getMinimum([start, styling.finish]),
547
- })
548
- })
549
-
550
- // Apply changes
551
- changes.map(({ index, start, finish }) => {
552
- stylings[index] = {
553
- ...stylings[index],
554
- start: start,
555
- finish: finish,
350
+ // Clamp start and end values, and offset by start index to reach the relative minimum
351
+ result = result.map((styling) => {
352
+ return {
353
+ ...styling,
354
+ start: getMaximum([styling.start, startIndex]) - startIndex,
355
+ finish: getMinimum([styling.finish, endIndex]) - startIndex,
556
356
  }
557
357
  })
558
358
 
559
- // Handle complete encapsulation by styling
560
- // stylings = stylings.map((styling) => {
561
- // if (start >= styling.start && end <= styling.finish) {
562
- // console.log("hi")
563
- // }
564
-
565
- // return styling
566
- // })
567
-
568
- // If deletion is surrounded by styling, decrease finish by length (this might not include first character, but try first)
569
- // If deletion is intersecting with a styling behind it, get maximum between deletion finish and styling start, and set the start to it, then offset styling by length
570
- // If deletion is intersecting wit ha styling after it, get minimum between deletion start and styling finish, and set the finish to it
359
+ return result
360
+ }
571
361
 
572
- // console.log(succeeding)
362
+ const splitStyling = (styling, index, offset = 0) => {
363
+ // Get first split
364
+ let first = {
365
+ ...styling,
366
+ start: styling.start,
367
+ finish: index,
368
+ }
573
369
 
574
- // If the start and finish of any styling is greater than the new length, remove it
370
+ // Get second split
371
+ let second = {
372
+ ...styling,
373
+ start: index + offset,
374
+ finish: styling.finish + offset,
375
+ }
575
376
 
576
- return stylings
377
+ return [first, second]
577
378
  }
578
379
 
579
- const handleAddition = (length, start, end) => {
580
- start -= length + 1
581
- end -= length + 1
380
+ const additionTo = (stylings, startIndex, length, defaultBehavior = true) => {
381
+ // console.log(`Adding text of length ${length} at index ${startIndex}`)
582
382
 
583
- let stylings = _text.current.stylings.slice()
383
+ // Required operations:
384
+ // Offset succeeding stylings
385
+ // Split intersecting stylings
386
+ // Styling continuation at end (only for normal addition)
584
387
 
585
- let changes = []
586
-
587
- let succeeding = handleScenario(
588
- stylings,
589
- (_start, _finish) => _start > start && _start + 1 >= end
590
- ).map((styling) => {
591
- return {
592
- ...styling,
593
- start: styling.start + length,
594
- finish: styling.finish + length,
388
+ stylings = stylings.map((styling) => {
389
+ // A succeeding styling
390
+ if (styling.start >= startIndex && styling.finish > startIndex) {
391
+ console.log("succeeding")
392
+ return {
393
+ ...styling,
394
+ start: styling.start + length,
395
+ finish: styling.finish + length,
396
+ }
595
397
  }
596
- })
597
- changes.push(succeeding)
598
398
 
599
- // console.log(`Addition (${length})`)
600
- // console.log([start, end])
601
- // if (stylings.length > 0) {
602
- // console.log([stylings[0].start, stylings[0].finish])
603
- // }
604
-
605
- // console.log(succeeding)
606
-
607
- let preceding = handleScenario(
608
- stylings,
609
- // (_start, _finish) => start < _finish && start >= _start + 2 && length == 1 // This is to not style pasted text
610
- (_start, _finish) => start + 1 == _finish && length == 1 // This is to not style pasted text
611
- ).map((styling) => {
612
- return {
613
- ...styling,
614
- start: styling.start,
615
- finish: styling.finish + length,
399
+ // An intersecting styling
400
+ if (styling.start < startIndex && styling.finish > startIndex) {
401
+ // Normal addition (non-drop & non-paste), adds to the length of the styling
402
+ if (defaultBehavior) {
403
+ return {
404
+ ...styling,
405
+ finish: styling.finish + length,
406
+ }
407
+ } else {
408
+ // Special addition (drop & paste), splits the styling and offsets the second half by length of addition
409
+ return splitStyling(styling, startIndex, length)
410
+ }
616
411
  }
617
- })
618
- changes.push(preceding)
619
412
 
620
- let encapsulated = handleScenario(
621
- stylings,
622
- (_start, _finish) => start >= _start && end + 1 < _finish
623
- ).map((styling) => {
624
- return {
625
- ...styling,
626
- finish: styling.finish + length,
413
+ // A connected styling at the end
414
+ if (styling.finish == startIndex) {
415
+ // Normal addition (non-drop & non-paste), continues the styling by addition length
416
+ if (defaultBehavior) {
417
+ return {
418
+ ...styling,
419
+ finish: styling.finish + length,
420
+ }
421
+ }
627
422
  }
423
+
424
+ return styling
628
425
  })
629
426
 
630
- changes.push(encapsulated)
427
+ return stylings.flat()
428
+ }
631
429
 
632
- // console.log(length)
430
+ const deletionOf = (stylings, startIndex, endIndex) => {
431
+ // console.log(`Removing text from ${startIndex} to ${endIndex}`)
633
432
 
634
- // console.log(`Encapsulated: ${encapsulated.length}`)
433
+ let length = Math.abs(endIndex - startIndex)
635
434
 
636
- // console.log(`Succeeding: ${succeeding.length}`)
637
- // console.log(`Preceding: ${preceding.length}`)
638
- // console.log(preceding)
435
+ // Required operations:
436
+ // Offset succeeding stylings
437
+ // Remove stylings completely within range
438
+ // Shrink stylings surrounding range
439
+ // Clamp left resumptions and offset
440
+ // Clamp right resumptions
639
441
 
640
- changes.flat().map(({ index, start, finish }) => {
641
- stylings[index] = {
642
- ...stylings[index],
643
- start: start,
644
- finish: finish,
442
+ stylings = stylings.map((styling) => {
443
+ // A succeeding styling, but not a right resumption
444
+ if (
445
+ styling.start >= startIndex &&
446
+ styling.start >= endIndex &&
447
+ styling.finish >= startIndex &&
448
+ styling.finish >= endIndex
449
+ ) {
450
+ return {
451
+ ...styling,
452
+ start: styling.start - length,
453
+ finish: styling.finish - length,
454
+ }
645
455
  }
646
- })
647
-
648
- return stylings
649
- }
650
456
 
651
- const handlePaste = (stylings, difference, start, end) => {
652
- // console.log(`Paste:`)
653
- // console.log([start, end])
654
- // console.log(stylings)
655
- let changes = []
656
-
657
- // Get stylings being encapsulated by pasting range
658
- let encapsulating = handleScenario(
659
- stylings,
660
- (_start, _end) => start <= _start && end >= _end // This is needed because of conflict with addition/deletion
661
- ).map((styling) => {
662
- // Effective removal
663
- return {
664
- ...styling,
665
- start: styling.start,
666
- finish: styling.start,
457
+ // A styling completely within deletion range
458
+ if (styling.start >= startIndex && styling.finish <= endIndex) {
459
+ return null
667
460
  }
668
- })
669
- changes.push(encapsulating)
670
-
671
- // Get encapsulated stylings
672
- let encapsulated = handleScenario(
673
- stylings,
674
- (_start, _end) => _start < start && _end > end
675
- ).map((styling) => {
676
- // Right splice
677
- changes.push({
678
- ...styling,
679
- index: -1,
680
- start: end,
681
- finish: styling.finish,
682
- })
683
461
 
684
- // Left splice
685
- changes.push({
686
- ...styling,
687
- index: -1,
688
- start: styling.start,
689
- finish: start,
690
- })
691
-
692
- // Effective removal
693
- return {
694
- ...styling,
695
- start: styling.start,
696
- finish: styling.start,
697
- }
698
- })
699
- changes.push(encapsulated)
700
-
701
- // Get intersecting stylings from the left
702
- let left = handleScenario(
703
- stylings,
704
- (_start, _end) =>
705
- _end > start && _end <= end - difference + 1 && _start < start
706
- ).map((styling) => {
707
- return {
708
- ...styling,
709
- finish: start,
462
+ // A styling surrounding deletion range
463
+ if (styling.start <= startIndex && styling.finish >= endIndex) {
464
+ return {
465
+ ...styling,
466
+ finish: styling.finish - length,
467
+ }
710
468
  }
711
- })
712
-
713
- changes.push(left)
714
469
 
715
- // Get intersecting stylings from the right
716
- let right = handleScenario(
717
- stylings,
718
- (_start, _end) =>
719
- _end > end - difference && _start >= start && _start < end - difference
720
- ).map((styling) => {
721
- return {
722
- ...styling,
723
- start: end,
470
+ // A styling not fully within deletion range, while the range exceeds to the left
471
+ if (styling.start > startIndex && styling.start < endIndex) {
472
+ return {
473
+ ...styling,
474
+ start: getMaximum([styling.start, endIndex]) - length,
475
+ finish: styling.finish - length,
476
+ }
724
477
  }
725
- })
726
478
 
727
- changes.push(right)
728
-
729
- // console.log(`Encapsulating: ${encapsulating.length}`)
730
- // console.log(`Encapsulated: ${encapsulated.length}`)
731
- // console.log(`Left Intersecting: ${left.length}`)
732
- // console.log(`Right Intersecting: ${right.length}`)
733
-
734
- changes = changes.flat()
735
- changes.map((styling) => {
736
- let { index, start, finish } = styling
737
- if (index == -1) {
738
- stylings.push({
739
- type: styling.type,
740
- start: start,
741
- finish: finish,
742
- })
743
- return
479
+ // A styling not full within deletion range, while the range exceeds to the right
480
+ if (styling.finish > startIndex && styling.finish < endIndex) {
481
+ return {
482
+ ...styling,
483
+ finish: getMinimum([styling.finish, startIndex]),
484
+ }
744
485
  }
745
486
 
746
- stylings[index] = {
747
- ...stylings[index],
748
- start: start,
749
- finish: finish,
750
- }
487
+ return styling
751
488
  })
752
489
 
753
- // console.log(stylings)
754
-
755
- return stylings
490
+ return stylings.flat().filter((styling) => styling)
756
491
  }
757
492
 
758
- const handleDrop = (value) => {
759
- let [dropStart, dropEnd] = getFieldSelection()
760
-
761
- let stylings = _text.current.stylings.slice()
762
- let dropped = _text.current.dropped.text
763
-
764
- let changes = []
765
-
766
- let [dragStart, dragEnd] = _text.current.dropped.previous
767
-
768
- console.log(`Drag: ${[dragStart, dragEnd]}`)
769
- console.log(`Drop: ${[dropStart, dropEnd]}`)
770
- console.log(stylings)
771
-
772
- // negative - right, positive - left
773
- // let direction = dragStart - dropStart < 0 ? -1 : 1
774
-
775
- // Possible refactorization:
776
- // Since the drop invokes the onInput event twice, the first is the removal of dragged text, and the second is the addition of dropped text
777
- // If the handling occurs at the second occurence of onInput, the first could be handled naturally by the handleDeletion function
493
+ const processNormal = (
494
+ stylings,
495
+ difference,
496
+ selectionStart,
497
+ selectionEnd
498
+ ) => {
499
+ if (difference == 0) return stylings
500
+
501
+ if (difference > 0) {
502
+ stylings = additionTo(stylings, selectionStart - difference, difference)
503
+ } else {
504
+ stylings = deletionOf(
505
+ stylings,
506
+ selectionStart,
507
+ selectionEnd + Math.abs(difference)
508
+ )
509
+ }
778
510
 
779
- // offset: length * direction
511
+ return stylings
512
+ }
780
513
 
781
- // To be offset in right conditions:
782
- // (_start >= dg.end && _end > dg.end)
783
- // (_end <= dp.start && _start < dp.start)
514
+ const processPaste = (stylings, difference, selectionStart, selectionEnd) => {
515
+ let pasteLength = _text.current.events.paste.length
784
516
 
785
- // To be offset in left conditions:
786
- // (_end <= dg.start && _start < dg.start)
787
- // (_start >= dp.end && _end > dp.end)
517
+ // Get addition start index
518
+ let additionStart = selectionStart - pasteLength
788
519
 
789
- // Get all intersecting stylings
790
- let dragStylings = stylings.filter(
791
- ({ start, finish }) =>
792
- !(
793
- (start < dragStart && finish <= dragStart) ||
794
- (start >= dragEnd && finish > dragEnd)
795
- )
796
- )
520
+ // Get removal range
521
+ let removalStart = additionStart
522
+ let removalEnd = additionStart + pasteLength - difference
797
523
 
798
- // Offset stylings between drag and drop locations
799
- stylings
800
- .filter(({ start, finish }) => start >= dragEnd && finish > dragEnd)
801
- .map((styling) => {
802
- changes.push({
803
- original: styling,
804
- changed: {
805
- ...styling,
806
- start: styling.start - dropped.length,
807
- finish: styling.finish - dropped.length,
808
- },
809
- })
810
- })
524
+ // Compute deletion
525
+ stylings = deletionOf(stylings, removalStart, removalEnd)
811
526
 
812
- // Handle complete encapsulation over styling
813
- stylings
814
- .filter(({ start, finish }) => dragStart <= start && dragEnd >= finish)
815
- .map((styling) => {
816
- changes.push({
817
- original: styling,
818
- changed: { ...styling, start: styling.start, finish: styling.start },
819
- })
820
- })
527
+ // Compute addition
528
+ stylings = additionTo(stylings, additionStart, pasteLength, false)
821
529
 
822
- // Handle drag range being encapsulated by styling
823
- stylings
824
- .filter(
825
- ({ start, finish }) =>
826
- (dragStart > start && dragEnd <= finish) ||
827
- (dragStart >= start && dragEnd < finish)
828
- )
829
- .map((styling) => {
830
- changes.push({
831
- original: styling,
832
- changed: {
833
- ...styling,
834
- start: styling.start,
835
- finish: styling.finish - dropped.length,
836
- },
837
- })
530
+ // Add rich copied stylings offset by paste start index
531
+ stylings = stylings.concat(
532
+ _text.current.clipboard.map((styling) => {
533
+ return {
534
+ ...styling,
535
+ start: styling.start + additionStart,
536
+ finish: styling.finish + additionStart,
537
+ }
838
538
  })
839
-
840
- // Handle resumptions
841
- let resumptions = stylings.filter(
842
- ({ start, finish }) =>
843
- (finish > dragStart &&
844
- start > dragStart &&
845
- start < dragEnd &&
846
- finish > dragEnd) ||
847
- (start < dragEnd &&
848
- finish < dragEnd &&
849
- finish > dragStart &&
850
- start < dragStart)
851
539
  )
852
540
 
853
- resumptions.map((styling) => {
854
- let right =
855
- styling.start < dragEnd &&
856
- styling.finish < dragEnd &&
857
- styling.finish > dragStart &&
858
- styling.start < dragStart
541
+ return stylings
542
+ }
859
543
 
860
- changes.push({
861
- original: styling,
862
- changed: {
863
- ...styling,
864
- start: !right
865
- ? getMaximum([dragEnd, styling.start]) - dropped.length // Only decrease if the drag
866
- : styling.start,
867
- finish: right
868
- ? getMinimum([dragStart, styling.finish])
869
- : styling.finish - dropped.length,
870
- },
871
- })
872
- })
544
+ const processDrop = (stylings, difference, dropStart, dropEnd) => {
545
+ let dropLength = _text.current.events.drop.text.length
873
546
 
874
- // Apply drag changes
875
- changes.map(({ original, changed }) => {
876
- let index = stylings.findIndex((styling) =>
877
- compareStylings(original, styling)
878
- )
547
+ let [dragStart, dragEnd] = _text.current.events.drop.drag
879
548
 
880
- stylings[index] = changed
881
- })
549
+ let dropDifference = dropStart - dragStart
882
550
 
883
- changes = []
551
+ // Get stylings at drag range
552
+ let dragStylings = getStylingsInRange(stylings, dragStart, dragEnd)
884
553
 
885
- // Positive offset succeeding stylings
886
- stylings
887
- .filter(({ start, finish }) => start >= dropStart && finish > dropStart)
888
- .map((styling) => {
889
- changes.push({
890
- original: styling,
891
- changed: {
892
- ...styling,
893
- start: styling.start + dropped.length,
894
- finish: styling.finish + dropped.length,
895
- },
896
- })
897
- })
554
+ // Removal range
555
+ let removalStart = dragStart
556
+ let removalEnd = dragEnd
898
557
 
899
- // Splice intersecting stylings
900
- stylings
901
- .filter(({ start, finish }) => start < dropStart && finish > dropStart)
902
- .map((styling) => {
903
- changes.push({
904
- original: styling,
905
- changed: {
906
- ...styling,
907
- start: styling.start,
908
- finish: dropStart,
909
- },
910
- })
911
-
912
- changes.push({
913
- original: null,
914
- changed: {
915
- ...styling,
916
- start: dropEnd,
917
- finish: styling.finish + dropped.length,
918
- },
919
- })
920
- })
558
+ // If the drag precedes the drop (positive difference)
559
+ if (dropDifference > 0) {
560
+ // Addition start index
561
+ let additionStart = dropEnd
921
562
 
922
- // Apply drop changes
923
- changes.map(({ original, changed }) => {
924
- if (original == null) {
925
- stylings.push(changed)
926
- return
927
- }
563
+ // Compute addition first
564
+ stylings = additionTo(stylings, additionStart, dropLength, false)
928
565
 
929
- let index = stylings.findIndex((styling) =>
930
- compareStylings(original, styling)
931
- )
566
+ // Compute removal second
567
+ stylings = deletionOf(stylings, removalStart, removalEnd)
568
+ }
932
569
 
933
- stylings[index] = changed
934
- })
570
+ // If the drop precedes the drag (negative difference)
571
+ if (dropDifference < 0) {
572
+ // Addition start index
573
+ let additionStart = dropStart
935
574
 
936
- // Remove empty stylings
937
- stylings = stylings.filter((styling) => styling.start != styling.finish)
575
+ // Compute removal first
576
+ stylings = deletionOf(stylings, removalStart, removalEnd)
938
577
 
939
- // Clamp start and end values, negative offset by dragStart, and positive offset by dropStart
940
- dragStylings = dragStylings.map((styling) => {
941
- return {
942
- ...styling,
943
- start: getMaximum([styling.start, dragStart]) - dragStart + dropStart,
944
- finish: getMinimum([styling.finish, dragEnd]) - dragStart + dropStart,
945
- }
946
- })
578
+ // Compute addition second
579
+ stylings = additionTo(stylings, additionStart, dropLength, false)
580
+ }
947
581
 
948
- // Push to current stylings
949
- stylings = stylings.concat(dragStylings)
582
+ // Add rich dragged stylings offset
583
+ stylings = stylings.concat(
584
+ dragStylings.map((styling) => {
585
+ return {
586
+ ...styling,
587
+ start: styling.start + dropStart,
588
+ finish: styling.finish + dropStart,
589
+ }
590
+ })
591
+ )
950
592
 
951
- setText({
952
- ..._text.current,
953
- content: value,
954
- stylings: stylings,
955
- revert: [dropStart, dropEnd],
956
- pasted: { status: false, length: 0 },
957
- dropped: { status: false, text: "", previous: [0, 0] },
958
- })
593
+ return stylings
959
594
  }
960
595
 
961
596
  const onChange = (value) => {
962
- setTimeout(function () {
963
- if (_text.current.dropped.status) {
964
- // Drops from text in the content editable invoke the onChange function twice
965
- if (value.length == _text.current.content.length) {
966
- handleDrop(value)
967
- }
968
- return
969
- }
597
+ // Since drop events cause onChange to invoke twice, ignore the first incomplete event
598
+ if (
599
+ _text.current.events.drop.is &&
600
+ value.length != _text.current.content.length
601
+ )
602
+ return
970
603
 
604
+ setTimeout(function () {
971
605
  let [selectionStart, selectionEnd] = getFieldSelection()
972
606
  let difference = value.length - _text.current.content.length
607
+ let stylings = _text.current.stylings
973
608
 
974
- let start = selectionStart - difference
975
- let end = selectionEnd - difference
976
-
977
- let stylings = _text.current.stylings.slice()
978
-
979
- let succeeding = getSucceedStylings(start)
980
- let changes = []
981
-
982
- for (let succeed of succeeding) {
983
- let index = getStylingIndex(succeed)
984
- let styling = stylings[index]
985
-
986
- changes.push([
987
- index,
988
- styling.start + difference,
989
- styling.finish + difference,
990
- ])
991
- }
992
-
993
- if (difference < 0) {
994
- stylings = handleDeletion(
995
- Math.abs(difference),
609
+ // Paste event
610
+ if (_text.current.events.paste.is) {
611
+ stylings = processPaste(
612
+ stylings,
613
+ difference,
996
614
  selectionStart,
997
- selectionEnd + Math.abs(difference)
615
+ selectionEnd
998
616
  )
999
- }
1000
-
1001
- if (difference > 0) {
1002
- stylings = handleAddition(
1003
- Math.abs(difference),
617
+ } else if (_text.current.events.drop.is) {
618
+ stylings = processDrop(
619
+ stylings,
620
+ difference,
1004
621
  selectionStart,
1005
622
  selectionEnd
1006
623
  )
1007
- }
1008
-
1009
- // Remove empty stylings and invisible stylings
1010
- stylings = stylings.filter(
1011
- (styling) =>
1012
- !(
1013
- styling.start == styling.finish ||
1014
- (styling.start >= value.length && styling.finish >= value.length)
1015
- )
1016
- )
1017
-
1018
- if (_text.current.pasted.status) {
1019
- stylings = handlePaste(
624
+ } else {
625
+ stylings = processNormal(
1020
626
  stylings,
1021
627
  difference,
1022
- selectionStart - _text.current.pasted.length,
1023
- selectionStart
628
+ selectionStart,
629
+ selectionEnd
1024
630
  )
1025
-
1026
- for (let styling of _text.current.lastCopy) {
1027
- styling = {
1028
- ...styling,
1029
- start: styling.start + selectionStart - _text.current.pasted.length,
1030
- finish:
1031
- styling.finish + selectionStart - _text.current.pasted.length,
1032
- }
1033
-
1034
- stylings.push(styling)
1035
- }
1036
631
  }
1037
632
 
1038
- // Remove empty stylings and invisible stylings
1039
- stylings = stylings.filter(
1040
- (styling) =>
1041
- !(
1042
- styling.start == styling.finish ||
1043
- (styling.start >= value.length && styling.finish >= value.length)
1044
- )
1045
- )
1046
-
1047
633
  setText({
1048
634
  ..._text.current,
1049
635
  content: value,
1050
636
  stylings: stylings,
1051
637
  revert: [selectionStart, selectionEnd],
1052
- pasted: { status: false, length: 0 },
1053
- dropped: { status: false, text: "", previous: [0, 0] },
638
+ events: {
639
+ paste: { is: false, length: null },
640
+ drop: { is: false, text: null, drag: null },
641
+ },
1054
642
  })
1055
643
  }, 0)
1056
644
  }
@@ -1087,37 +675,6 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
1087
675
  return result
1088
676
  }
1089
677
 
1090
- const serializeStyleInRange = (start, end) => {
1091
- // Get all stylings intersecting
1092
- let stylings = []
1093
- for (let i = start; i < end; i++) {
1094
- stylings.push(getIntersectStylings(i))
1095
- }
1096
-
1097
- // Remove duplicates
1098
- stylings = stylings.flat().filter((item) => item)
1099
- stylings = stylings.filter(
1100
- (item, index) =>
1101
- stylings.findIndex(
1102
- (_item) =>
1103
- _item.start == item.start &&
1104
- _item.finish == item.finish &&
1105
- _item.type == item.type
1106
- ) == index
1107
- )
1108
-
1109
- // Clamp start and finish values and offset by start index
1110
- stylings = stylings.map((styling) => {
1111
- return {
1112
- ...styling,
1113
- start: getMaximum([styling.start, start]) - start,
1114
- finish: getMinimum([styling.finish, end]) - start,
1115
- }
1116
- })
1117
-
1118
- return stylings
1119
- }
1120
-
1121
678
  // dangerouslySetInnerHTML incorrectly renders when the entire text is highlighted, copied, and then pasted in succession
1122
679
  useEffect(() => {
1123
680
  let html = getContent()
@@ -1126,8 +683,6 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
1126
683
 
1127
684
  // Get stylings encompassing an index within it's range
1128
685
  let stylings = getIntersectStylings(start)
1129
- // console.log(data)
1130
- // console.log(stylings)
1131
686
  return `<span class="${stylings
1132
687
  .map((styling) => stylers[styling.type].css)
1133
688
  .join(" ")}" data-child-index="${index}">${data}</span>`
@@ -1150,22 +705,6 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
1150
705
  }
1151
706
  }
1152
707
 
1153
- const calculateTextWidth = (
1154
- text: string,
1155
- font: { size: number; family: string }
1156
- ) => {
1157
- let element = document.createElement("div")
1158
- let { size, family } = font
1159
-
1160
- element.className = `text-[${size}px] font-['${family}'] absolute float-left whitespace-nowrap invisible`
1161
- element.innerHTML = text
1162
-
1163
- document.body.appendChild(element)
1164
-
1165
- let rect = element.getBoundingClientRect()
1166
- return rect.width
1167
- }
1168
-
1169
708
  return (
1170
709
  <div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded shadow-md">
1171
710
  <div className={clsx("flex w-full flex-row justify-start p-2")}>
@@ -1197,24 +736,26 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
1197
736
  const data = event.clipboardData.getData("text/plain")
1198
737
  document.execCommand("insertHTML", false, data)
1199
738
 
1200
- console.log(data)
1201
-
1202
739
  setText({
1203
740
  ..._text.current,
1204
- pasted: { status: true, length: data.length },
741
+ events: {
742
+ ..._text.current.events,
743
+ paste: { is: true, length: data.length },
744
+ },
1205
745
  })
1206
746
  }}
1207
747
  onInput={(event: any) => {
1208
748
  onChange(event.target.textContent)
1209
749
  }}
1210
- onCopy={(event) => {
750
+ onCopy={() => {
1211
751
  let [start, end] = getFieldSelection()
1212
752
 
1213
- let data = serializeStyleInRange(start, end)
753
+ let stylings = _text.current.stylings.slice()
754
+ stylings = getStylingsInRange(stylings, start, end)
1214
755
 
1215
756
  setText({
1216
757
  ...text,
1217
- lastCopy: data,
758
+ clipboard: stylings,
1218
759
  })
1219
760
  }}
1220
761
  onDrop={(event) => {
@@ -1222,16 +763,11 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
1222
763
 
1223
764
  if (text.trim() == "") return
1224
765
 
1225
- // console.log(getSelectionPrecedingSum("anchorNode"))
1226
- // console.log(getSelectionPrecedingSum("focusNode"))
1227
- // console.log(window.getSelection())
1228
-
1229
766
  setText({
1230
767
  ..._text.current,
1231
- dropped: {
1232
- status: true,
1233
- text: text,
1234
- previous: getFieldSelection(),
768
+ events: {
769
+ ..._text.current.events,
770
+ drop: { is: true, text: text, drag: getFieldSelection() },
1235
771
  },
1236
772
  })
1237
773
  }}