@sikka/hawa 0.0.232 → 0.0.234
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.
- package/dist/styles.css +3 -0
- package/es/index.es.js +2 -2
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/src/elements/FloatingCommentCE.tsx +511 -188
- package/src/styles.css +3 -0
|
@@ -17,8 +17,6 @@ type ComponentTypes = {
|
|
|
17
17
|
foo?: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// TODO: Handle copy pasting by assigning to local storage ?
|
|
21
|
-
|
|
22
20
|
const styleClasses = {
|
|
23
21
|
bold: "font-bold",
|
|
24
22
|
italic: "italic",
|
|
@@ -26,97 +24,121 @@ const styleClasses = {
|
|
|
26
24
|
strike: "line-through",
|
|
27
25
|
}
|
|
28
26
|
|
|
27
|
+
// FIXME: Deleting a styled part of text while selecting characters on the borders or more
|
|
28
|
+
// FIXME: Deleting more than one character of a styled part of text
|
|
29
|
+
// FIXME: Pasting styled text doesn't offset succeeding stylings
|
|
30
|
+
// FIXME: Pasting styled text inside other stylings
|
|
31
|
+
// FIXME: Typing characters behind and after stylings
|
|
32
|
+
|
|
33
|
+
// FIXME: Pasting behind the second character in styled text
|
|
34
|
+
// FIXME: Pasting over styled text with resumptions and/or highlighted text exceeding pasted text length
|
|
35
|
+
|
|
29
36
|
export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
30
37
|
props
|
|
31
38
|
) => {
|
|
32
|
-
const [text,
|
|
39
|
+
const [text, _setText] = useState({
|
|
33
40
|
content: "",
|
|
34
41
|
stylings: [], // A styling is an object with 2 indices specifying a substring of text, and the applied effect
|
|
35
42
|
revert: [0, 0],
|
|
43
|
+
lastCopy: [],
|
|
44
|
+
pasted: { status: false, length: 0 },
|
|
36
45
|
})
|
|
37
46
|
|
|
38
47
|
const field = useRef(null)
|
|
48
|
+
const _text = useRef(text)
|
|
49
|
+
const setText = (data) => {
|
|
50
|
+
_text.current = data
|
|
51
|
+
_setText(data)
|
|
52
|
+
}
|
|
39
53
|
|
|
54
|
+
const getChildIndex = (child) => {
|
|
55
|
+
for (var i = 0; (child = child.previousSibling); i++);
|
|
56
|
+
return i
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Full reversion achieved !
|
|
40
60
|
const getFieldSelection = () => {
|
|
41
61
|
if (document.activeElement != field.current) return [0, 0]
|
|
42
62
|
|
|
43
63
|
let selection = window.getSelection()
|
|
44
|
-
let
|
|
45
|
-
let end
|
|
46
|
-
|
|
47
|
-
console.log(selection)
|
|
48
|
-
console.log(document.activeElement)
|
|
49
|
-
console.log(`Range count when fetching selection: ${selection.rangeCount}`)
|
|
50
|
-
if (selection.rangeCount) {
|
|
51
|
-
let range = selection.getRangeAt(0)
|
|
52
|
-
start = range.startOffset
|
|
53
|
-
end = range.endOffset
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return [start, end]
|
|
57
|
-
}
|
|
64
|
+
let nodes = Array.from(field.current.childNodes)
|
|
58
65
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
nodes = nodes.filter(
|
|
67
|
+
(item: any) => !["#text", "BR"].includes(item.nodeName)
|
|
68
|
+
)
|
|
62
69
|
|
|
63
|
-
|
|
70
|
+
let startParent: any = selection.anchorNode.parentNode
|
|
71
|
+
|
|
72
|
+
let startNodeIndex =
|
|
73
|
+
startParent == field.current
|
|
74
|
+
? nodes.length
|
|
75
|
+
: // : parseInt(startParent.dataset.childIndex)
|
|
76
|
+
getChildIndex(startParent)
|
|
77
|
+
|
|
78
|
+
let startPrecedingSum = nodes
|
|
79
|
+
.slice(0, startNodeIndex)
|
|
80
|
+
.map((span: any) => span.textContent.length)
|
|
81
|
+
.reduce((a, b) => a + b, 0)
|
|
82
|
+
|
|
83
|
+
let endParent: any = selection.focusNode.parentNode
|
|
84
|
+
let endNodeIndex =
|
|
85
|
+
endParent == field.current
|
|
86
|
+
? nodes.length
|
|
87
|
+
: // : parseInt(endParent.dataset.childIndex)
|
|
88
|
+
getChildIndex(endParent)
|
|
89
|
+
|
|
90
|
+
let endPrecedingSum = nodes
|
|
91
|
+
.slice(0, endNodeIndex)
|
|
92
|
+
.map((span: any) => span.textContent.length)
|
|
93
|
+
.reduce((a, b) => a + b, 0)
|
|
94
|
+
|
|
95
|
+
let result = [
|
|
96
|
+
startPrecedingSum + selection.anchorOffset,
|
|
97
|
+
endPrecedingSum + selection.focusOffset,
|
|
98
|
+
]
|
|
64
99
|
|
|
65
|
-
|
|
66
|
-
|
|
100
|
+
// Sort to make the minimum selection the start selection
|
|
101
|
+
return result.sort((a, b) => a - b)
|
|
102
|
+
}
|
|
67
103
|
|
|
68
|
-
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
setTimeout(function () {
|
|
106
|
+
let [start, end] = _text.current.revert
|
|
69
107
|
|
|
70
|
-
|
|
71
|
-
// add to each child node the sum of all previous child nodes
|
|
72
|
-
// Create ranges for 0 - n, n - n1, n - n2, ... where nx is the length of a child node
|
|
73
|
-
// Add 1 to the index
|
|
74
|
-
// Iterate through each range, and check if the index is greater than the start of the range, and less than or equal the end of the range
|
|
75
|
-
let startNode = null
|
|
76
|
-
let endNode = null
|
|
108
|
+
if (start == 0 && end == 0) return
|
|
77
109
|
|
|
78
|
-
|
|
79
|
-
|
|
110
|
+
let startNode = null
|
|
111
|
+
let endNode = null
|
|
80
112
|
|
|
81
|
-
|
|
82
|
-
|
|
113
|
+
let total = 0
|
|
114
|
+
let nodes = Array.from(field.current.childNodes)
|
|
83
115
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
117
|
+
let node: any = nodes[i]
|
|
118
|
+
let sum = node.textContent.length + total
|
|
87
119
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
if (startNode == null && start >= total && start <= sum) {
|
|
121
|
+
startNode = nodes[i]
|
|
122
|
+
start -= total
|
|
123
|
+
}
|
|
91
124
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
125
|
+
if (endNode == null && end > total && end <= sum) {
|
|
126
|
+
endNode = nodes[i]
|
|
127
|
+
end -= total
|
|
128
|
+
}
|
|
97
129
|
|
|
98
|
-
|
|
99
|
-
endNode = nodes[i]
|
|
100
|
-
endNodeIndex = i
|
|
101
|
-
end -= total
|
|
130
|
+
total += node.textContent.length
|
|
102
131
|
}
|
|
103
132
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
console.log(
|
|
108
|
-
`Reverting (${newStart} -> ${newEnd}) to (${oldStart} - ${start} - [node ${startNodeIndex}] -> ${oldEnd} - ${end} - [node ${endNodeIndex}])`
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
var range = document.createRange()
|
|
112
|
-
var sel = window.getSelection()
|
|
133
|
+
var range = document.createRange()
|
|
134
|
+
var sel = window.getSelection()
|
|
113
135
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// range.collapse(true)
|
|
136
|
+
range.setStart(startNode.firstChild, start)
|
|
137
|
+
range.setEnd(endNode.firstChild, end)
|
|
117
138
|
|
|
118
|
-
|
|
119
|
-
|
|
139
|
+
sel.removeAllRanges()
|
|
140
|
+
sel.addRange(range)
|
|
141
|
+
}, 1)
|
|
120
142
|
}, [text.revert])
|
|
121
143
|
|
|
122
144
|
// utility functions
|
|
@@ -239,7 +261,7 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
239
261
|
}
|
|
240
262
|
|
|
241
263
|
const perform = (id) => {
|
|
242
|
-
let stylings =
|
|
264
|
+
let stylings = _text.current.stylings.slice()
|
|
243
265
|
let [selectionStart, selectionEnd] = getFieldSelection()
|
|
244
266
|
|
|
245
267
|
if (selectionStart == selectionEnd) return
|
|
@@ -280,6 +302,36 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
280
302
|
})
|
|
281
303
|
}
|
|
282
304
|
|
|
305
|
+
const getRangeIntersectStylings = (start, end, startOffset, finishOffset) => {
|
|
306
|
+
// Get all stylings intersecting
|
|
307
|
+
let stylings = []
|
|
308
|
+
for (let i = start; i < end; i++) {
|
|
309
|
+
stylings.push(getIntersectStylings(i, startOffset, finishOffset))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Remove duplicates
|
|
313
|
+
stylings = stylings.flat().filter((item) => item)
|
|
314
|
+
stylings = stylings.filter(
|
|
315
|
+
(item, index) =>
|
|
316
|
+
stylings.findIndex(
|
|
317
|
+
(_item) =>
|
|
318
|
+
_item.start == item.start &&
|
|
319
|
+
_item.finish == item.finish &&
|
|
320
|
+
_item.type == item.type
|
|
321
|
+
) == index
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
return stylings
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const compareStylings = (styling1, styling2) => {
|
|
328
|
+
return (
|
|
329
|
+
styling1.type == styling2.type &&
|
|
330
|
+
styling1.start == styling2.start &&
|
|
331
|
+
styling1.finish == styling2.finish
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
283
335
|
// Get stylings encompassing an index within it's range
|
|
284
336
|
const getIntersectStylings = (index, startOffset = 0, finishOffset = 0) => {
|
|
285
337
|
// Find all stylings with encompassing range
|
|
@@ -308,56 +360,288 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
308
360
|
)
|
|
309
361
|
}
|
|
310
362
|
|
|
311
|
-
const
|
|
312
|
-
let
|
|
363
|
+
const handleScenario = (stylings, predicate) => {
|
|
364
|
+
let matches = stylings.filter(({ start: _start, finish: _finish }) =>
|
|
365
|
+
predicate(_start, _finish)
|
|
366
|
+
)
|
|
313
367
|
|
|
314
|
-
let
|
|
368
|
+
let indices = []
|
|
315
369
|
|
|
316
|
-
|
|
317
|
-
|
|
370
|
+
stylings.map((styling, index) => {
|
|
371
|
+
let result = matches.find((item) => compareStylings(styling, item))
|
|
372
|
+
if (!result) return
|
|
373
|
+
indices.push({
|
|
374
|
+
index: index,
|
|
375
|
+
start: styling.start,
|
|
376
|
+
finish: styling.finish,
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
return indices
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const handleDeletion = (length, start, end) => {
|
|
384
|
+
let stylings = _text.current.stylings.slice()
|
|
318
385
|
let changes = []
|
|
319
386
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
387
|
+
console.log(`Deletion`)
|
|
388
|
+
console.log([start, end])
|
|
389
|
+
|
|
390
|
+
// TODO: Refactor
|
|
391
|
+
|
|
392
|
+
// Offset all succeeding stylings by length
|
|
393
|
+
// changes.push(
|
|
394
|
+
// handleScenario(
|
|
395
|
+
// stylings,
|
|
396
|
+
// (_start, _finish) => start < _start && end <= start
|
|
397
|
+
// ).map((styling) => {
|
|
398
|
+
// return {
|
|
399
|
+
// ...styling,
|
|
400
|
+
// start: styling.start - length,
|
|
401
|
+
// finish: styling.finish - length,
|
|
402
|
+
// }
|
|
403
|
+
// })
|
|
404
|
+
// )
|
|
405
|
+
|
|
406
|
+
let succeeding = stylings.filter(
|
|
407
|
+
({ start: _start }) => start < _start && end <= _start
|
|
408
|
+
)
|
|
409
|
+
stylings.map((styling, index) => {
|
|
410
|
+
let result = succeeding.find((item) => compareStylings(styling, item))
|
|
411
|
+
if (!result) return
|
|
412
|
+
|
|
413
|
+
changes.push({
|
|
414
|
+
index: index,
|
|
415
|
+
start: styling.start - length,
|
|
416
|
+
finish: styling.finish - length,
|
|
417
|
+
})
|
|
418
|
+
})
|
|
323
419
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
420
|
+
// Handle complete encapsulation over styling
|
|
421
|
+
let encapsulating = stylings.filter(
|
|
422
|
+
({ start: _start, finish: _finish }) => start <= _start && end >= _finish
|
|
423
|
+
)
|
|
424
|
+
stylings.map((styling, index) => {
|
|
425
|
+
let result = encapsulating.find((item) => compareStylings(styling, item))
|
|
426
|
+
if (!result) return
|
|
427
|
+
|
|
428
|
+
// This will effectively remove the styling, since collapsed ranges are automatically removed
|
|
429
|
+
changes.push({
|
|
430
|
+
index: index,
|
|
431
|
+
start: styling.start,
|
|
432
|
+
finish: styling.start,
|
|
433
|
+
})
|
|
434
|
+
})
|
|
330
435
|
|
|
331
|
-
|
|
436
|
+
// Handle deletion being encapsulated by styling
|
|
437
|
+
let encapsulated = stylings.filter(
|
|
438
|
+
({ start: _start, finish: _finish }) =>
|
|
439
|
+
(start > _start && end <= _finish) || (start >= _start && end < _finish)
|
|
440
|
+
)
|
|
441
|
+
stylings.map((styling, index) => {
|
|
442
|
+
let result = encapsulated.find((item) => compareStylings(styling, item))
|
|
443
|
+
if (!result) return
|
|
444
|
+
|
|
445
|
+
changes.push({
|
|
446
|
+
index: index,
|
|
447
|
+
start: styling.start,
|
|
448
|
+
finish: styling.finish - length,
|
|
449
|
+
})
|
|
450
|
+
})
|
|
332
451
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
452
|
+
// Handle deletion being encapsulated by styling with left resumption
|
|
453
|
+
let leftResumption = stylings.filter(
|
|
454
|
+
({ start: _start, finish: _finish }) =>
|
|
455
|
+
end < _finish && end > _start && start < _start
|
|
456
|
+
)
|
|
457
|
+
stylings.map((styling, index) => {
|
|
458
|
+
let result = leftResumption.find((item) => compareStylings(styling, item))
|
|
459
|
+
if (!result) return
|
|
460
|
+
|
|
461
|
+
changes.push({
|
|
462
|
+
index: index,
|
|
463
|
+
start: getMaximum([end, styling.start]) - length,
|
|
464
|
+
finish: styling.finish - length,
|
|
465
|
+
})
|
|
466
|
+
})
|
|
336
467
|
|
|
337
|
-
|
|
338
|
-
|
|
468
|
+
let rightResumption = stylings.filter(
|
|
469
|
+
({ start: _start, finish: _finish }) =>
|
|
470
|
+
start > _start && start < _finish && end > _finish
|
|
471
|
+
)
|
|
472
|
+
stylings.map((styling, index) => {
|
|
473
|
+
let result = rightResumption.find((item) =>
|
|
474
|
+
compareStylings(styling, item)
|
|
475
|
+
)
|
|
476
|
+
if (!result) return
|
|
477
|
+
|
|
478
|
+
changes.push({
|
|
479
|
+
index: index,
|
|
480
|
+
start: styling.start,
|
|
481
|
+
finish: getMinimum([start, styling.finish]),
|
|
482
|
+
})
|
|
483
|
+
})
|
|
339
484
|
|
|
340
|
-
|
|
341
|
-
|
|
485
|
+
// Apply changes
|
|
486
|
+
changes.map(({ index, start, finish }) => {
|
|
342
487
|
stylings[index] = {
|
|
343
488
|
...stylings[index],
|
|
344
489
|
start: start,
|
|
345
490
|
finish: finish,
|
|
346
491
|
}
|
|
492
|
+
})
|
|
347
493
|
|
|
348
|
-
|
|
349
|
-
|
|
494
|
+
// Handle complete encapsulation by styling
|
|
495
|
+
// stylings = stylings.map((styling) => {
|
|
496
|
+
// if (start >= styling.start && end <= styling.finish) {
|
|
497
|
+
// console.log("hi")
|
|
498
|
+
// }
|
|
499
|
+
|
|
500
|
+
// return styling
|
|
501
|
+
// })
|
|
350
502
|
|
|
351
|
-
|
|
503
|
+
// If deletion is surrounded by styling, decrease finish by length (this might not include first character, but try first)
|
|
504
|
+
// 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
|
|
505
|
+
// If deletion is intersecting wit ha styling after it, get minimum between deletion start and styling finish, and set the finish to it
|
|
352
506
|
|
|
353
|
-
|
|
507
|
+
// console.log(succeeding)
|
|
508
|
+
|
|
509
|
+
// If the start and finish of any styling is greater than the new length, remove it
|
|
510
|
+
|
|
511
|
+
return stylings
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const handleAddition = (length, start, end) => {
|
|
515
|
+
start -= length
|
|
516
|
+
end -= length
|
|
517
|
+
|
|
518
|
+
let stylings = _text.current.stylings.slice()
|
|
519
|
+
|
|
520
|
+
let changes = []
|
|
521
|
+
|
|
522
|
+
let succeeding = handleScenario(
|
|
523
|
+
stylings,
|
|
524
|
+
(_start, _finish) => _start + 1 >= start && _start + 1 >= end
|
|
525
|
+
).map((styling) => {
|
|
526
|
+
return {
|
|
527
|
+
...styling,
|
|
528
|
+
start: styling.start + length,
|
|
529
|
+
finish: styling.finish + length,
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
changes.push(succeeding)
|
|
533
|
+
|
|
534
|
+
console.log(`Addition (${length})`)
|
|
535
|
+
console.log([start, end])
|
|
536
|
+
console.log(`Succeeding:`)
|
|
537
|
+
console.log(succeeding)
|
|
538
|
+
|
|
539
|
+
let preceding = handleScenario(
|
|
540
|
+
stylings,
|
|
541
|
+
(_start, _finish) => start <= _finish && start >= _start + 2
|
|
542
|
+
).map((styling) => {
|
|
543
|
+
return {
|
|
544
|
+
...styling,
|
|
545
|
+
start: styling.start,
|
|
546
|
+
finish: styling.finish + length,
|
|
547
|
+
}
|
|
548
|
+
})
|
|
549
|
+
changes.push(preceding)
|
|
550
|
+
|
|
551
|
+
console.log(`Preceding:`)
|
|
552
|
+
console.log(preceding)
|
|
553
|
+
|
|
554
|
+
changes.flat().map(({ index, start, finish }) => {
|
|
555
|
+
stylings[index] = {
|
|
556
|
+
...stylings[index],
|
|
557
|
+
start: start,
|
|
558
|
+
finish: finish,
|
|
559
|
+
}
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
return stylings
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const handlePaste = (stylings, start, end) => {
|
|
566
|
+
console.log([start, end])
|
|
567
|
+
console.log(stylings)
|
|
568
|
+
|
|
569
|
+
return stylings
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const onChange = (value) => {
|
|
573
|
+
setTimeout(function () {
|
|
574
|
+
let [selectionStart, selectionEnd] = getFieldSelection()
|
|
575
|
+
|
|
576
|
+
let difference = value.length - _text.current.content.length
|
|
577
|
+
|
|
578
|
+
let start = selectionStart - difference
|
|
579
|
+
let end = selectionEnd - difference
|
|
580
|
+
|
|
581
|
+
let stylings = _text.current.stylings.slice()
|
|
582
|
+
|
|
583
|
+
let succeeding = getSucceedStylings(start)
|
|
584
|
+
let changes = []
|
|
585
|
+
|
|
586
|
+
for (let succeed of succeeding) {
|
|
587
|
+
let index = getStylingIndex(succeed)
|
|
588
|
+
let styling = stylings[index]
|
|
589
|
+
|
|
590
|
+
changes.push([
|
|
591
|
+
index,
|
|
592
|
+
styling.start + difference,
|
|
593
|
+
styling.finish + difference,
|
|
594
|
+
])
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (difference < 0) {
|
|
598
|
+
stylings = handleDeletion(
|
|
599
|
+
Math.abs(difference),
|
|
600
|
+
selectionStart,
|
|
601
|
+
selectionEnd + Math.abs(difference)
|
|
602
|
+
)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (difference > 0) {
|
|
606
|
+
stylings = handleAddition(
|
|
607
|
+
Math.abs(difference),
|
|
608
|
+
selectionStart,
|
|
609
|
+
selectionEnd
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Remove empty stylings and invisible stylings
|
|
614
|
+
stylings = stylings.filter(
|
|
615
|
+
(styling) =>
|
|
616
|
+
!(
|
|
617
|
+
styling.start == styling.finish ||
|
|
618
|
+
(styling.start >= value.length && styling.finish >= value.length)
|
|
619
|
+
)
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
if (_text.current.pasted.status) {
|
|
623
|
+
stylings = handlePaste(
|
|
624
|
+
stylings,
|
|
625
|
+
selectionStart - _text.current.pasted.length,
|
|
626
|
+
selectionStart
|
|
627
|
+
)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
setText({
|
|
631
|
+
..._text.current,
|
|
632
|
+
content: value,
|
|
633
|
+
stylings: stylings,
|
|
634
|
+
revert: [selectionStart, selectionEnd],
|
|
635
|
+
pasted: { status: false, length: 0 },
|
|
636
|
+
})
|
|
637
|
+
}, 0)
|
|
354
638
|
}
|
|
355
639
|
|
|
356
640
|
const getContent = () => {
|
|
357
|
-
let content =
|
|
641
|
+
let content = _text.current.content
|
|
358
642
|
|
|
359
643
|
// Get all styling indices
|
|
360
|
-
let indices =
|
|
644
|
+
let indices = _text.current.stylings
|
|
361
645
|
.map(({ start, finish }) => [start, finish])
|
|
362
646
|
.flat()
|
|
363
647
|
|
|
@@ -373,7 +657,7 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
373
657
|
if (indices[0] != 0) indices.unshift(0)
|
|
374
658
|
|
|
375
659
|
// Add last index if not present
|
|
376
|
-
let last =
|
|
660
|
+
let last = content.length
|
|
377
661
|
if (indices[indices.length - 1] != last) indices.push(last)
|
|
378
662
|
|
|
379
663
|
let result = []
|
|
@@ -385,6 +669,56 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
385
669
|
return result
|
|
386
670
|
}
|
|
387
671
|
|
|
672
|
+
const serializeStyleInRange = (start, end) => {
|
|
673
|
+
// Get all stylings intersecting
|
|
674
|
+
let stylings = []
|
|
675
|
+
for (let i = start; i < end; i++) {
|
|
676
|
+
stylings.push(getIntersectStylings(i))
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Remove duplicates
|
|
680
|
+
stylings = stylings.flat().filter((item) => item)
|
|
681
|
+
stylings = stylings.filter(
|
|
682
|
+
(item, index) =>
|
|
683
|
+
stylings.findIndex(
|
|
684
|
+
(_item) =>
|
|
685
|
+
_item.start == item.start &&
|
|
686
|
+
_item.finish == item.finish &&
|
|
687
|
+
_item.type == item.type
|
|
688
|
+
) == index
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
// Clamp start and finish values and offset by start index
|
|
692
|
+
stylings = stylings.map((styling) => {
|
|
693
|
+
return {
|
|
694
|
+
...styling,
|
|
695
|
+
start: getMaximum([styling.start, start]) - start,
|
|
696
|
+
finish: getMinimum([styling.finish, end]) - start,
|
|
697
|
+
}
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
return stylings
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// dangerouslySetInnerHTML incorrectly renders when the entire text is highlighted, copied, and then pasted in succession
|
|
704
|
+
useEffect(() => {
|
|
705
|
+
let html = getContent()
|
|
706
|
+
.map((_data, index) => {
|
|
707
|
+
let [start, data] = _data
|
|
708
|
+
|
|
709
|
+
// Get stylings encompassing an index within it's range
|
|
710
|
+
let stylings = getIntersectStylings(start)
|
|
711
|
+
// console.log(data)
|
|
712
|
+
// console.log(stylings)
|
|
713
|
+
return `<span class="${stylings
|
|
714
|
+
.map((styling) => styleClasses[styling.type])
|
|
715
|
+
.join(" ")}" data-child-index="${index}">${data}</span>`
|
|
716
|
+
})
|
|
717
|
+
.join("")
|
|
718
|
+
|
|
719
|
+
field.current.innerHTML = html
|
|
720
|
+
}, [text.content, text.stylings, text.revert])
|
|
721
|
+
|
|
388
722
|
return (
|
|
389
723
|
<div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-blue-300 shadow-md">
|
|
390
724
|
<div className={clsx("flex w-full flex-row justify-start p-2")}>
|
|
@@ -397,19 +731,22 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
397
731
|
/>
|
|
398
732
|
<Property
|
|
399
733
|
name="I"
|
|
400
|
-
|
|
734
|
+
onMouseDown={(event) => {
|
|
735
|
+
event.preventDefault()
|
|
401
736
|
perform("italic")
|
|
402
737
|
}}
|
|
403
738
|
/>
|
|
404
739
|
<Property
|
|
405
740
|
name="U"
|
|
406
|
-
|
|
741
|
+
onMouseDown={(event) => {
|
|
742
|
+
event.preventDefault()
|
|
407
743
|
perform("under")
|
|
408
744
|
}}
|
|
409
745
|
/>
|
|
410
746
|
<Property
|
|
411
747
|
name="S"
|
|
412
|
-
|
|
748
|
+
onMouseDown={(event) => {
|
|
749
|
+
event.preventDefault()
|
|
413
750
|
perform("strike")
|
|
414
751
|
}}
|
|
415
752
|
/>
|
|
@@ -419,106 +756,92 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
419
756
|
<div
|
|
420
757
|
ref={field}
|
|
421
758
|
contentEditable="true"
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
// class="${stylings
|
|
436
|
-
// .map((styling) => styleClasses[styling.type])
|
|
437
|
-
// .join(" ")}"
|
|
438
|
-
|
|
439
|
-
// >
|
|
440
|
-
// ${data}
|
|
441
|
-
// </span>`
|
|
442
|
-
})
|
|
443
|
-
.join(""),
|
|
444
|
-
}}
|
|
445
|
-
className="h-[150px] w-full resize-none border-none p-2 outline-none"
|
|
446
|
-
onInput={(event) => {
|
|
447
|
-
let target: any = event.target
|
|
759
|
+
className="h-[150px] w-full resize-none overflow-auto overflow-x-hidden border-none p-2 outline-none"
|
|
760
|
+
onPaste={(event) => {
|
|
761
|
+
// pastes all copied text from the content editable as plain text
|
|
762
|
+
event.preventDefault()
|
|
763
|
+
const data = event.clipboardData.getData("text/plain")
|
|
764
|
+
document.execCommand("insertHTML", false, data)
|
|
765
|
+
|
|
766
|
+
console.log(data)
|
|
767
|
+
|
|
768
|
+
setText({
|
|
769
|
+
..._text.current,
|
|
770
|
+
pasted: { status: true, length: data.length },
|
|
771
|
+
})
|
|
448
772
|
|
|
773
|
+
// let [start, end] = getFieldSelection()
|
|
774
|
+
|
|
775
|
+
// console.log(index)
|
|
776
|
+
// console.log(start)
|
|
777
|
+
|
|
778
|
+
// let index = start - data.length
|
|
779
|
+
// let stylings = text.stylings.slice()
|
|
780
|
+
|
|
781
|
+
// FIXME:
|
|
782
|
+
// stylings.push({
|
|
783
|
+
// type: "bold",
|
|
784
|
+
// start: 5,
|
|
785
|
+
// finish: 7,
|
|
786
|
+
// })
|
|
787
|
+
|
|
788
|
+
// let copy = text.lastCopy
|
|
789
|
+
// if (copy.length != 0) {
|
|
790
|
+
// for (let styling of copy) {
|
|
791
|
+
// stylings.push({
|
|
792
|
+
// type: styling.type,
|
|
793
|
+
// start: styling.start + start,
|
|
794
|
+
// finish: styling.finish + start,
|
|
795
|
+
// })
|
|
796
|
+
// }
|
|
797
|
+
// }
|
|
798
|
+
|
|
799
|
+
// let content: any = text.content
|
|
800
|
+
// let original = content.length
|
|
801
|
+
|
|
802
|
+
// // If not collapsed, insert text
|
|
803
|
+
// if (start == end) {
|
|
804
|
+
// content = content.split("")
|
|
805
|
+
// content.splice(start, 0, data)
|
|
806
|
+
// content = content.join("")
|
|
807
|
+
// } else {
|
|
808
|
+
// console.log(content)
|
|
809
|
+
// // Otherwise, replace substring
|
|
810
|
+
// content =
|
|
811
|
+
// content.substring(0, start) +
|
|
812
|
+
// data +
|
|
813
|
+
// content.substring(end, content.length)
|
|
814
|
+
// }
|
|
815
|
+
|
|
816
|
+
// let difference = content.length - original
|
|
817
|
+
|
|
818
|
+
// stylings = handleDeletion()
|
|
819
|
+
|
|
820
|
+
// setText({
|
|
821
|
+
// ...text,
|
|
822
|
+
// content: content,
|
|
823
|
+
// stylings: stylings,
|
|
824
|
+
// revert: [start + data.length, start + data.length],
|
|
825
|
+
// })
|
|
826
|
+
}}
|
|
827
|
+
onInput={(event: any) => {
|
|
828
|
+
onChange(event.target.textContent)
|
|
829
|
+
}}
|
|
830
|
+
onCopy={(event) => {
|
|
449
831
|
let [start, end] = getFieldSelection()
|
|
450
832
|
|
|
451
|
-
|
|
452
|
-
// console.log(target.selectionStart)
|
|
453
|
-
// console.log(target.selectionEnd)
|
|
833
|
+
let data = serializeStyleInRange(start, end)
|
|
454
834
|
|
|
455
835
|
setText({
|
|
456
836
|
...text,
|
|
457
|
-
|
|
458
|
-
revert: [start, end],
|
|
837
|
+
lastCopy: data,
|
|
459
838
|
})
|
|
460
839
|
}}
|
|
840
|
+
// onKeyDown={(event: any) => {
|
|
841
|
+
// event.preventDefault()
|
|
842
|
+
// console.log(event)
|
|
843
|
+
// }}
|
|
461
844
|
></div>
|
|
462
|
-
|
|
463
|
-
{/* <RichTextarea
|
|
464
|
-
ref={field}
|
|
465
|
-
value={text.content}
|
|
466
|
-
style={{ width: "100%" }} // tailwind w-full does not work
|
|
467
|
-
className="order-none h-[150px] resize-none p-2 outline-none"
|
|
468
|
-
onChange={(e) => {
|
|
469
|
-
onChange(e.target.value)
|
|
470
|
-
}}
|
|
471
|
-
>
|
|
472
|
-
{(value) => {
|
|
473
|
-
// Get all styling indices
|
|
474
|
-
let indices = text.stylings
|
|
475
|
-
.map(({ start, finish }) => [start, finish])
|
|
476
|
-
.flat()
|
|
477
|
-
|
|
478
|
-
// Sort ascendingly
|
|
479
|
-
indices = indices.sort((a, b) => a - b)
|
|
480
|
-
|
|
481
|
-
// Remove duplicates
|
|
482
|
-
indices = indices.filter(
|
|
483
|
-
(element, index) => indices.indexOf(element) == index
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
// Add first index if not present
|
|
487
|
-
if (indices[0] != 0) indices.unshift(0)
|
|
488
|
-
|
|
489
|
-
// Add last index if not present
|
|
490
|
-
let last = text.content.length
|
|
491
|
-
if (indices[indices.length - 1] != last) indices.push(last)
|
|
492
|
-
|
|
493
|
-
let result = []
|
|
494
|
-
|
|
495
|
-
for (let i = 0; i < indices.length - 1; i++) {
|
|
496
|
-
result.push([
|
|
497
|
-
indices[i],
|
|
498
|
-
value.substring(indices[i], indices[i + 1]),
|
|
499
|
-
])
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return result.map((_data, index) => {
|
|
503
|
-
let [start, data] = _data
|
|
504
|
-
|
|
505
|
-
// Get stylings encompassing an index within it's range
|
|
506
|
-
let stylings = getIntersectStylings(start)
|
|
507
|
-
|
|
508
|
-
return (
|
|
509
|
-
<span
|
|
510
|
-
key={index}
|
|
511
|
-
className={`${stylings
|
|
512
|
-
.map((styling) => styleClasses[styling.type])
|
|
513
|
-
.join(" ")}
|
|
514
|
-
`}
|
|
515
|
-
>
|
|
516
|
-
{data}
|
|
517
|
-
</span>
|
|
518
|
-
)
|
|
519
|
-
})
|
|
520
|
-
}}
|
|
521
|
-
</RichTextarea> */}
|
|
522
845
|
</div>
|
|
523
846
|
<div className="h-[1px] w-full bg-slate-600"> </div>
|
|
524
847
|
<button className="my-1 rounded bg-cyan-800 p-2 py-1 text-white">
|