@sikka/hawa 0.0.236 → 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.
@@ -27,11 +27,19 @@ const stylers = {
27
27
  strike: { id: "strike", css: "line-through", content: "S" },
28
28
  }
29
29
 
30
- // FIXME: Highlighting a part of styled text with a bit on the left with an overall length not equal to clipboard copied text will result in paste issues
30
+ // FIXME: ? Highlighting a part of styled text with a bit on the left with an overall length not equal to clipboard copied text will result in paste issues
31
31
 
32
32
  // FIXME: Highlighting the beginning characters of styled text and then pasting text sometimes doesn't register as right intersecting
33
33
  // This expecially happens when the selection is for example, [0, 2] and the styling is [0, 3], this might be failure of addition which doesn't offset the styling
34
34
 
35
+ // TODO: Refactor styling splicing into one method
36
+ // TODO: Refactor function that simplifies a list of stylings
37
+ // TODO: Turn stylings into a class, this should also change .finish to .end
38
+
39
+ // Possible logic changes:
40
+ // Paste = Removal + Addition -> Styling Removal + Styling Addition
41
+ // Drag & Drop = Removal + Addition -> Styling Removal + Styling Addition
42
+
35
43
  export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
36
44
  props
37
45
  ) => {
@@ -39,8 +47,11 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
39
47
  content: "",
40
48
  stylings: [], // A styling is an object with 2 indices specifying a substring of text, and the applied effect
41
49
  revert: [0, 0],
42
- lastCopy: [],
43
- pasted: { status: false, length: 0 },
50
+ clipboard: [],
51
+ events: {
52
+ paste: { is: false, length: null },
53
+ drop: { is: false, text: null, drag: null },
54
+ },
44
55
  })
45
56
 
46
57
  const field = useRef(null)
@@ -55,41 +66,57 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
55
66
  return i
56
67
  }
57
68
 
58
- // Full reversion achieved !
59
- const getFieldSelection = () => {
60
- if (document.activeElement != field.current) return [0, 0]
61
-
69
+ const getSelectionPrecedingSum = (name) => {
62
70
  let selection = window.getSelection()
63
71
  let nodes = Array.from(field.current.childNodes)
64
72
 
73
+ // All current occurences for text or br:
74
+ // Pasting on empty text (text)
75
+ // Cutting/removing all text (br)
76
+ // Typing the first character in empty text (text)
77
+ // Dragging text to the end of the text (text)
78
+
65
79
  nodes = nodes.filter(
66
80
  (item: any) => !["#text", "BR"].includes(item.nodeName)
67
81
  )
68
82
 
69
- let startParent: any = selection.anchorNode.parentNode
83
+ let parent: any = selection[name].parentNode
84
+ let special = 0
85
+
86
+ // Special case for empty text
87
+ // if (parent == field.current) {
88
+ // console.log("hi")
89
+ // }
70
90
 
71
- let startNodeIndex =
72
- startParent == field.current
73
- ? nodes.length
74
- : // : parseInt(startParent.dataset.childIndex)
75
- getChildIndex(startParent)
91
+ // Special case for dropping text near or inside styled text
92
+ if (!Array.from(parent.parentNode.classList).includes("selection-ignore")) {
93
+ parent = parent.parentNode
94
+
95
+ let index = getChildIndex(selection[name].parentNode)
96
+ special = Array.from(parent.childNodes)
97
+ .slice(0, index)
98
+ .map((e: any) => e.textContent.length)
99
+ .reduce((a, b) => a + b, 0)
100
+ }
101
+
102
+ let index = parent == field.current ? nodes.length : getChildIndex(parent)
103
+
104
+ let sum =
105
+ nodes
106
+ .slice(0, index)
107
+ .map((span: any) => span.textContent.length)
108
+ .reduce((a, b) => a + b, 0) + special
109
+
110
+ return sum
111
+ }
76
112
 
77
- let startPrecedingSum = nodes
78
- .slice(0, startNodeIndex)
79
- .map((span: any) => span.textContent.length)
80
- .reduce((a, b) => a + b, 0)
113
+ const getFieldSelection = () => {
114
+ if (document.activeElement != field.current) return [0, 0]
81
115
 
82
- let endParent: any = selection.focusNode.parentNode
83
- let endNodeIndex =
84
- endParent == field.current
85
- ? nodes.length
86
- : // : parseInt(endParent.dataset.childIndex)
87
- getChildIndex(endParent)
116
+ let selection = window.getSelection()
88
117
 
89
- let endPrecedingSum = nodes
90
- .slice(0, endNodeIndex)
91
- .map((span: any) => span.textContent.length)
92
- .reduce((a, b) => a + b, 0)
118
+ let startPrecedingSum = getSelectionPrecedingSum("anchorNode")
119
+ let endPrecedingSum = getSelectionPrecedingSum("focusNode")
93
120
 
94
121
  let result = [
95
122
  startPrecedingSum + selection.anchorOffset,
@@ -301,36 +328,6 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
301
328
  })
302
329
  }
303
330
 
304
- const getRangeIntersectStylings = (start, end, startOffset, finishOffset) => {
305
- // Get all stylings intersecting
306
- let stylings = []
307
- for (let i = start; i < end; i++) {
308
- stylings.push(getIntersectStylings(i, startOffset, finishOffset))
309
- }
310
-
311
- // Remove duplicates
312
- stylings = stylings.flat().filter((item) => item)
313
- stylings = stylings.filter(
314
- (item, index) =>
315
- stylings.findIndex(
316
- (_item) =>
317
- _item.start == item.start &&
318
- _item.finish == item.finish &&
319
- _item.type == item.type
320
- ) == index
321
- )
322
-
323
- return stylings
324
- }
325
-
326
- const compareStylings = (styling1, styling2) => {
327
- return (
328
- styling1.type == styling2.type &&
329
- styling1.start == styling2.start &&
330
- styling1.finish == styling2.finish
331
- )
332
- }
333
-
334
331
  // Get stylings encompassing an index within it's range
335
332
  const getIntersectStylings = (index, startOffset = 0, finishOffset = 0) => {
336
333
  // Find all stylings with encompassing range
@@ -342,437 +339,306 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
342
339
  return matches
343
340
  }
344
341
 
345
- // Get stylings after an index
346
- const getSucceedStylings = (index) => {
347
- // Find all stylings after the index
348
- let matches = text.stylings.filter(({ start, finish }) => start >= index)
349
-
350
- return matches
351
- }
352
-
353
- const getStylingIndex = (styling) => {
354
- return text.stylings.findIndex(
355
- (item) =>
356
- item.start == styling.start &&
357
- item.finish == styling.finish &&
358
- item.type == styling.type
359
- )
360
- }
361
-
362
- const handleScenario = (stylings, predicate) => {
363
- let matches = stylings.filter(({ start: _start, finish: _finish }) =>
364
- predicate(_start, _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)
365
348
  )
366
349
 
367
- let indices = []
368
-
369
- stylings.map((styling, index) => {
370
- let result = matches.find((item) => compareStylings(styling, item))
371
- if (!result) return
372
- indices.push({
373
- index: index,
374
- type: styling.type,
375
- start: styling.start,
376
- finish: styling.finish,
377
- })
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,
356
+ }
378
357
  })
379
358
 
380
- return indices
359
+ return result
381
360
  }
382
361
 
383
- const handleDeletion = (length, start, end) => {
384
- let stylings = _text.current.stylings.slice()
385
- let changes = []
386
-
387
- // TODO: Refactor
388
-
389
- // Offset all succeeding stylings by length
390
- // changes.push(
391
- // handleScenario(
392
- // stylings,
393
- // (_start, _finish) => start < _start && end <= start
394
- // ).map((styling) => {
395
- // return {
396
- // ...styling,
397
- // start: styling.start - length,
398
- // finish: styling.finish - length,
399
- // }
400
- // })
401
- // )
402
-
403
- let succeeding = stylings.filter(
404
- ({ start: _start }) => start < _start && end <= _start
405
- )
406
- stylings.map((styling, index) => {
407
- let result = succeeding.find((item) => compareStylings(styling, item))
408
- if (!result) return
409
-
410
- changes.push({
411
- index: index,
412
- start: styling.start - length,
413
- finish: styling.finish - length,
414
- })
415
- })
416
-
417
- // Handle complete encapsulation over styling
418
- let encapsulating = stylings.filter(
419
- ({ start: _start, finish: _finish }) => start <= _start && end >= _finish
420
- )
421
- stylings.map((styling, index) => {
422
- let result = encapsulating.find((item) => compareStylings(styling, item))
423
- if (!result) return
424
-
425
- // This will effectively remove the styling, since collapsed ranges are automatically removed
426
- changes.push({
427
- index: index,
428
- start: styling.start,
429
- finish: styling.start,
430
- })
431
- })
362
+ const splitStyling = (styling, index, offset = 0) => {
363
+ // Get first split
364
+ let first = {
365
+ ...styling,
366
+ start: styling.start,
367
+ finish: index,
368
+ }
432
369
 
433
- // Handle deletion being encapsulated by styling
434
- let encapsulated = stylings.filter(
435
- ({ start: _start, finish: _finish }) =>
436
- (start > _start && end <= _finish) || (start >= _start && end < _finish)
437
- )
438
- stylings.map((styling, index) => {
439
- let result = encapsulated.find((item) => compareStylings(styling, item))
440
- if (!result) return
441
-
442
- changes.push({
443
- index: index,
444
- start: styling.start,
445
- finish: styling.finish - length,
446
- })
447
- })
370
+ // Get second split
371
+ let second = {
372
+ ...styling,
373
+ start: index + offset,
374
+ finish: styling.finish + offset,
375
+ }
448
376
 
449
- // Handle deletion being encapsulated by styling with left resumption
450
- let leftResumption = stylings.filter(
451
- ({ start: _start, finish: _finish }) =>
452
- end < _finish && end > _start && start < _start
453
- )
454
- stylings.map((styling, index) => {
455
- let result = leftResumption.find((item) => compareStylings(styling, item))
456
- if (!result) return
457
-
458
- changes.push({
459
- index: index,
460
- start: getMaximum([end, styling.start]) - length,
461
- finish: styling.finish - length,
462
- })
463
- })
377
+ return [first, second]
378
+ }
464
379
 
465
- let rightResumption = stylings.filter(
466
- ({ start: _start, finish: _finish }) =>
467
- start > _start && start < _finish && end > _finish
468
- )
469
- stylings.map((styling, index) => {
470
- let result = rightResumption.find((item) =>
471
- compareStylings(styling, item)
472
- )
473
- if (!result) return
380
+ const additionTo = (stylings, startIndex, length, defaultBehavior = true) => {
381
+ // console.log(`Adding text of length ${length} at index ${startIndex}`)
474
382
 
475
- changes.push({
476
- index: index,
477
- start: styling.start,
478
- finish: getMinimum([start, styling.finish]),
479
- })
480
- })
383
+ // Required operations:
384
+ // Offset succeeding stylings
385
+ // Split intersecting stylings
386
+ // Styling continuation at end (only for normal addition)
481
387
 
482
- // Apply changes
483
- changes.map(({ index, start, finish }) => {
484
- stylings[index] = {
485
- ...stylings[index],
486
- start: start,
487
- finish: finish,
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
+ }
488
397
  }
489
- })
490
-
491
- // Handle complete encapsulation by styling
492
- // stylings = stylings.map((styling) => {
493
- // if (start >= styling.start && end <= styling.finish) {
494
- // console.log("hi")
495
- // }
496
-
497
- // return styling
498
- // })
499
398
 
500
- // If deletion is surrounded by styling, decrease finish by length (this might not include first character, but try first)
501
- // 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
502
- // If deletion is intersecting wit ha styling after it, get minimum between deletion start and styling finish, and set the finish to it
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
+ }
411
+ }
503
412
 
504
- // console.log(succeeding)
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
+ }
422
+ }
505
423
 
506
- // If the start and finish of any styling is greater than the new length, remove it
424
+ return styling
425
+ })
507
426
 
508
- return stylings
427
+ return stylings.flat()
509
428
  }
510
429
 
511
- const handleAddition = (length, start, end) => {
512
- start -= length + 1
513
- end -= length + 1
430
+ const deletionOf = (stylings, startIndex, endIndex) => {
431
+ // console.log(`Removing text from ${startIndex} to ${endIndex}`)
514
432
 
515
- let stylings = _text.current.stylings.slice()
433
+ let length = Math.abs(endIndex - startIndex)
516
434
 
517
- let changes = []
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
518
441
 
519
- let succeeding = handleScenario(
520
- stylings,
521
- (_start, _finish) => _start > start && _start + 1 >= end
522
- ).map((styling) => {
523
- return {
524
- ...styling,
525
- start: styling.start + length,
526
- finish: styling.finish + length,
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
+ }
527
455
  }
528
- })
529
- changes.push(succeeding)
530
456
 
531
- console.log(`Addition (${length})`)
532
- console.log([start, end])
533
- if (stylings.length > 0) {
534
- console.log([stylings[0].start, stylings[0].finish])
535
- }
457
+ // A styling completely within deletion range
458
+ if (styling.start >= startIndex && styling.finish <= endIndex) {
459
+ return null
460
+ }
536
461
 
537
- // console.log(succeeding)
462
+ // A styling surrounding deletion range
463
+ if (styling.start <= startIndex && styling.finish >= endIndex) {
464
+ return {
465
+ ...styling,
466
+ finish: styling.finish - length,
467
+ }
468
+ }
538
469
 
539
- let preceding = handleScenario(
540
- stylings,
541
- // (_start, _finish) => start < _finish && start >= _start + 2 && length == 1 // This is to not style pasted text
542
- (_start, _finish) => start + 1 == _finish && length == 1 // This is to not style pasted text
543
- ).map((styling) => {
544
- return {
545
- ...styling,
546
- start: styling.start,
547
- finish: styling.finish + length,
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
+ }
548
477
  }
549
- })
550
- changes.push(preceding)
551
478
 
552
- let encapsulated = handleScenario(
553
- stylings,
554
- (_start, _finish) => start >= _start && end + 1 < _finish
555
- ).map((styling) => {
556
- return {
557
- ...styling,
558
- finish: styling.finish + length,
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
+ }
559
485
  }
486
+
487
+ return styling
560
488
  })
561
489
 
562
- changes.push(encapsulated)
490
+ return stylings.flat().filter((styling) => styling)
491
+ }
563
492
 
564
- console.log(length)
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
+ }
565
510
 
566
- console.log(`Encapsulated: ${encapsulated.length}`)
511
+ return stylings
512
+ }
567
513
 
568
- console.log(`Succeeding: ${succeeding.length}`)
569
- console.log(`Preceding: ${preceding.length}`)
570
- // console.log(preceding)
514
+ const processPaste = (stylings, difference, selectionStart, selectionEnd) => {
515
+ let pasteLength = _text.current.events.paste.length
571
516
 
572
- changes.flat().map(({ index, start, finish }) => {
573
- stylings[index] = {
574
- ...stylings[index],
575
- start: start,
576
- finish: finish,
577
- }
578
- })
517
+ // Get addition start index
518
+ let additionStart = selectionStart - pasteLength
519
+
520
+ // Get removal range
521
+ let removalStart = additionStart
522
+ let removalEnd = additionStart + pasteLength - difference
523
+
524
+ // Compute deletion
525
+ stylings = deletionOf(stylings, removalStart, removalEnd)
526
+
527
+ // Compute addition
528
+ stylings = additionTo(stylings, additionStart, pasteLength, false)
529
+
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
+ }
538
+ })
539
+ )
579
540
 
580
541
  return stylings
581
542
  }
582
543
 
583
- const handlePaste = (stylings, difference, start, end) => {
584
- console.log(`Paste:`)
585
- console.log([start, end])
586
- console.log(stylings)
587
- let changes = []
544
+ const processDrop = (stylings, difference, dropStart, dropEnd) => {
545
+ let dropLength = _text.current.events.drop.text.length
588
546
 
589
- // Get stylings being encapsulated by pasting range
547
+ let [dragStart, dragEnd] = _text.current.events.drop.drag
590
548
 
591
- let encapsulating = handleScenario(
592
- stylings,
593
- (_start, _end) => start <= _start && end >= _end // This is needed because of conflict with addition/deletion
594
- ).map((styling) => {
595
- // Effective removal
596
- return {
597
- ...styling,
598
- start: styling.start,
599
- finish: styling.start,
600
- }
601
- })
602
- changes.push(encapsulating)
603
-
604
- // Get encapsulated stylings
605
- let encapsulated = handleScenario(
606
- stylings,
607
- (_start, _end) => _start < start && _end > end
608
- ).map((styling) => {
609
- // Right splice
610
- changes.push({
611
- ...styling,
612
- index: -1,
613
- start: end,
614
- finish: styling.finish,
615
- })
549
+ let dropDifference = dropStart - dragStart
616
550
 
617
- // Left splice
618
- changes.push({
619
- ...styling,
620
- index: -1,
621
- start: styling.start,
622
- finish: start,
623
- })
551
+ // Get stylings at drag range
552
+ let dragStylings = getStylingsInRange(stylings, dragStart, dragEnd)
624
553
 
625
- // Effective removal
626
- return {
627
- ...styling,
628
- start: styling.start,
629
- finish: styling.start,
630
- }
631
- })
632
- changes.push(encapsulated)
633
-
634
- // Get intersecting stylings from the left
635
- let left = handleScenario(
636
- stylings,
637
- (_start, _end) =>
638
- _end > start && _end <= end - difference + 1 && _start < start
639
- ).map((styling) => {
640
- return {
641
- ...styling,
642
- finish: start,
643
- }
644
- })
554
+ // Removal range
555
+ let removalStart = dragStart
556
+ let removalEnd = dragEnd
645
557
 
646
- changes.push(left)
558
+ // If the drag precedes the drop (positive difference)
559
+ if (dropDifference > 0) {
560
+ // Addition start index
561
+ let additionStart = dropEnd
647
562
 
648
- // Get intersecting stylings from the right
649
- let right = handleScenario(
650
- stylings,
651
- (_start, _end) =>
652
- _end > end - difference && _start >= start && _start < end - difference
653
- ).map((styling) => {
654
- return {
655
- ...styling,
656
- start: end,
657
- }
658
- })
563
+ // Compute addition first
564
+ stylings = additionTo(stylings, additionStart, dropLength, false)
659
565
 
660
- changes.push(right)
661
-
662
- console.log(`Encapsulating: ${encapsulating.length}`)
663
- console.log(`Encapsulated: ${encapsulated.length}`)
664
- console.log(`Left Intersecting: ${left.length}`)
665
- console.log(`Right Intersecting: ${right.length}`)
666
-
667
- changes = changes.flat()
668
- changes.map((styling) => {
669
- let { index, start, finish } = styling
670
- if (index == -1) {
671
- stylings.push({
672
- type: styling.type,
673
- start: start,
674
- finish: finish,
675
- })
676
- return
677
- }
566
+ // Compute removal second
567
+ stylings = deletionOf(stylings, removalStart, removalEnd)
568
+ }
678
569
 
679
- stylings[index] = {
680
- ...stylings[index],
681
- start: start,
682
- finish: finish,
683
- }
684
- })
570
+ // If the drop precedes the drag (negative difference)
571
+ if (dropDifference < 0) {
572
+ // Addition start index
573
+ let additionStart = dropStart
685
574
 
686
- console.log(stylings)
575
+ // Compute removal first
576
+ stylings = deletionOf(stylings, removalStart, removalEnd)
577
+
578
+ // Compute addition second
579
+ stylings = additionTo(stylings, additionStart, dropLength, false)
580
+ }
581
+
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
+ )
687
592
 
688
593
  return stylings
689
594
  }
690
595
 
691
596
  const onChange = (value) => {
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
603
+
692
604
  setTimeout(function () {
693
605
  let [selectionStart, selectionEnd] = getFieldSelection()
694
-
695
606
  let difference = value.length - _text.current.content.length
607
+ let stylings = _text.current.stylings
696
608
 
697
- let start = selectionStart - difference
698
- let end = selectionEnd - difference
699
-
700
- let stylings = _text.current.stylings.slice()
701
-
702
- let succeeding = getSucceedStylings(start)
703
- let changes = []
704
-
705
- for (let succeed of succeeding) {
706
- let index = getStylingIndex(succeed)
707
- let styling = stylings[index]
708
-
709
- changes.push([
710
- index,
711
- styling.start + difference,
712
- styling.finish + difference,
713
- ])
714
- }
715
-
716
- if (difference < 0) {
717
- stylings = handleDeletion(
718
- Math.abs(difference),
609
+ // Paste event
610
+ if (_text.current.events.paste.is) {
611
+ stylings = processPaste(
612
+ stylings,
613
+ difference,
719
614
  selectionStart,
720
- selectionEnd + Math.abs(difference)
615
+ selectionEnd
721
616
  )
722
- }
723
-
724
- if (difference > 0) {
725
- stylings = handleAddition(
726
- Math.abs(difference),
617
+ } else if (_text.current.events.drop.is) {
618
+ stylings = processDrop(
619
+ stylings,
620
+ difference,
727
621
  selectionStart,
728
622
  selectionEnd
729
623
  )
730
- }
731
-
732
- // Remove empty stylings and invisible stylings
733
- stylings = stylings.filter(
734
- (styling) =>
735
- !(
736
- styling.start == styling.finish ||
737
- (styling.start >= value.length && styling.finish >= value.length)
738
- )
739
- )
740
-
741
- if (_text.current.pasted.status) {
742
- stylings = handlePaste(
624
+ } else {
625
+ stylings = processNormal(
743
626
  stylings,
744
627
  difference,
745
- selectionStart - _text.current.pasted.length,
746
- selectionStart
628
+ selectionStart,
629
+ selectionEnd
747
630
  )
748
-
749
- for (let styling of _text.current.lastCopy) {
750
- styling = {
751
- ...styling,
752
- start: styling.start + selectionStart - _text.current.pasted.length,
753
- finish:
754
- styling.finish + selectionStart - _text.current.pasted.length,
755
- }
756
-
757
- stylings.push(styling)
758
- }
759
631
  }
760
632
 
761
- // Remove empty stylings and invisible stylings
762
- stylings = stylings.filter(
763
- (styling) =>
764
- !(
765
- styling.start == styling.finish ||
766
- (styling.start >= value.length && styling.finish >= value.length)
767
- )
768
- )
769
-
770
633
  setText({
771
634
  ..._text.current,
772
635
  content: value,
773
636
  stylings: stylings,
774
637
  revert: [selectionStart, selectionEnd],
775
- pasted: { status: false, length: 0 },
638
+ events: {
639
+ paste: { is: false, length: null },
640
+ drop: { is: false, text: null, drag: null },
641
+ },
776
642
  })
777
643
  }, 0)
778
644
  }
@@ -809,37 +675,6 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
809
675
  return result
810
676
  }
811
677
 
812
- const serializeStyleInRange = (start, end) => {
813
- // Get all stylings intersecting
814
- let stylings = []
815
- for (let i = start; i < end; i++) {
816
- stylings.push(getIntersectStylings(i))
817
- }
818
-
819
- // Remove duplicates
820
- stylings = stylings.flat().filter((item) => item)
821
- stylings = stylings.filter(
822
- (item, index) =>
823
- stylings.findIndex(
824
- (_item) =>
825
- _item.start == item.start &&
826
- _item.finish == item.finish &&
827
- _item.type == item.type
828
- ) == index
829
- )
830
-
831
- // Clamp start and finish values and offset by start index
832
- stylings = stylings.map((styling) => {
833
- return {
834
- ...styling,
835
- start: getMaximum([styling.start, start]) - start,
836
- finish: getMinimum([styling.finish, end]) - start,
837
- }
838
- })
839
-
840
- return stylings
841
- }
842
-
843
678
  // dangerouslySetInnerHTML incorrectly renders when the entire text is highlighted, copied, and then pasted in succession
844
679
  useEffect(() => {
845
680
  let html = getContent()
@@ -848,8 +683,6 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
848
683
 
849
684
  // Get stylings encompassing an index within it's range
850
685
  let stylings = getIntersectStylings(start)
851
- // console.log(data)
852
- // console.log(stylings)
853
686
  return `<span class="${stylings
854
687
  .map((styling) => stylers[styling.type].css)
855
688
  .join(" ")}" data-child-index="${index}">${data}</span>`
@@ -887,42 +720,13 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
887
720
  />
888
721
  )
889
722
  })}
890
-
891
- {/* <Property
892
- name="B"
893
- onMouseDown={(event) => {
894
- event.preventDefault() // This does not take focus away from field which allows the function to retrieve the current selection data
895
- perform("bold")
896
- }}
897
- />
898
- <Property
899
- name="I"
900
- onMouseDown={(event) => {
901
- event.preventDefault()
902
- perform("italic")
903
- }}
904
- />
905
- <Property
906
- name="U"
907
- onMouseDown={(event) => {
908
- event.preventDefault()
909
- perform("under")
910
- }}
911
- />
912
- <Property
913
- name="S"
914
- onMouseDown={(event) => {
915
- event.preventDefault()
916
- perform("strike")
917
- }}
918
- /> */}
919
723
  </div>
920
724
  <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
921
- <div className="w-full">
725
+ <div className="selection-ignore box-border w-full p-2">
922
726
  <div
923
727
  ref={field}
924
728
  contentEditable="true"
925
- className="rtl h-[150px] w-full resize-none overflow-auto overflow-x-hidden border-none p-2 outline-none"
729
+ className="selection-ignore rtl h-[150px] w-full resize-none overflow-auto overflow-x-hidden border-none font-['Arial'] text-[16px] outline-none"
926
730
  style={{
927
731
  direction: getTextDirection(),
928
732
  }}
@@ -932,84 +736,41 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
932
736
  const data = event.clipboardData.getData("text/plain")
933
737
  document.execCommand("insertHTML", false, data)
934
738
 
935
- console.log(data)
936
-
937
739
  setText({
938
740
  ..._text.current,
939
- pasted: { status: true, length: data.length },
741
+ events: {
742
+ ..._text.current.events,
743
+ paste: { is: true, length: data.length },
744
+ },
940
745
  })
941
-
942
- // let [start, end] = getFieldSelection()
943
-
944
- // console.log(index)
945
- // console.log(start)
946
-
947
- // let index = start - data.length
948
- // let stylings = text.stylings.slice()
949
-
950
- // FIXME:
951
- // stylings.push({
952
- // type: "bold",
953
- // start: 5,
954
- // finish: 7,
955
- // })
956
-
957
- // let copy = text.lastCopy
958
- // if (copy.length != 0) {
959
- // for (let styling of copy) {
960
- // stylings.push({
961
- // type: styling.type,
962
- // start: styling.start + start,
963
- // finish: styling.finish + start,
964
- // })
965
- // }
966
- // }
967
-
968
- // let content: any = text.content
969
- // let original = content.length
970
-
971
- // // If not collapsed, insert text
972
- // if (start == end) {
973
- // content = content.split("")
974
- // content.splice(start, 0, data)
975
- // content = content.join("")
976
- // } else {
977
- // console.log(content)
978
- // // Otherwise, replace substring
979
- // content =
980
- // content.substring(0, start) +
981
- // data +
982
- // content.substring(end, content.length)
983
- // }
984
-
985
- // let difference = content.length - original
986
-
987
- // stylings = handleDeletion()
988
-
989
- // setText({
990
- // ...text,
991
- // content: content,
992
- // stylings: stylings,
993
- // revert: [start + data.length, start + data.length],
994
- // })
995
746
  }}
996
747
  onInput={(event: any) => {
997
748
  onChange(event.target.textContent)
998
749
  }}
999
- onCopy={(event) => {
750
+ onCopy={() => {
1000
751
  let [start, end] = getFieldSelection()
1001
752
 
1002
- let data = serializeStyleInRange(start, end)
753
+ let stylings = _text.current.stylings.slice()
754
+ stylings = getStylingsInRange(stylings, start, end)
1003
755
 
1004
756
  setText({
1005
757
  ...text,
1006
- lastCopy: data,
758
+ clipboard: stylings,
759
+ })
760
+ }}
761
+ onDrop={(event) => {
762
+ let text = event.dataTransfer.getData("text")
763
+
764
+ if (text.trim() == "") return
765
+
766
+ setText({
767
+ ..._text.current,
768
+ events: {
769
+ ..._text.current.events,
770
+ drop: { is: true, text: text, drag: getFieldSelection() },
771
+ },
1007
772
  })
1008
773
  }}
1009
- // onKeyDown={(event: any) => {
1010
- // event.preventDefault()
1011
- // console.log(event)
1012
- // }}
1013
774
  ></div>
1014
775
  </div>
1015
776
  <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>