@sikka/hawa 0.0.232 → 0.0.233
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/es/index.es.js +2 -2
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/src/elements/FloatingCommentCE.tsx +106 -169
package/package.json
CHANGED
|
@@ -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",
|
|
@@ -29,94 +27,98 @@ const styleClasses = {
|
|
|
29
27
|
export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
30
28
|
props
|
|
31
29
|
) => {
|
|
32
|
-
const [text,
|
|
30
|
+
const [text, _setText] = useState({
|
|
33
31
|
content: "",
|
|
34
32
|
stylings: [], // A styling is an object with 2 indices specifying a substring of text, and the applied effect
|
|
35
33
|
revert: [0, 0],
|
|
36
34
|
})
|
|
37
35
|
|
|
38
36
|
const field = useRef(null)
|
|
37
|
+
const _text = useRef(text)
|
|
38
|
+
const setText = (data) => {
|
|
39
|
+
_text.current = data
|
|
40
|
+
_setText(data)
|
|
41
|
+
}
|
|
39
42
|
|
|
43
|
+
// Full reversion achieved !
|
|
40
44
|
const getFieldSelection = () => {
|
|
41
45
|
if (document.activeElement != field.current) return [0, 0]
|
|
42
46
|
|
|
43
47
|
let selection = window.getSelection()
|
|
44
|
-
let
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
}
|
|
48
|
+
let nodes = Array.from(field.current.childNodes)
|
|
49
|
+
nodes = nodes.filter(
|
|
50
|
+
(item: any) => !["#text", "BR"].includes(item.nodeName)
|
|
51
|
+
)
|
|
55
52
|
|
|
56
|
-
|
|
53
|
+
let startParent: any = selection.anchorNode.parentNode
|
|
54
|
+
let startNodeIndex =
|
|
55
|
+
startParent == field.current
|
|
56
|
+
? nodes.length
|
|
57
|
+
: parseInt(startParent.dataset.childIndex)
|
|
58
|
+
|
|
59
|
+
let startPrecedingSum = nodes
|
|
60
|
+
.slice(0, startNodeIndex)
|
|
61
|
+
.map((span: any) => span.textContent.length)
|
|
62
|
+
.reduce((a, b) => a + b, 0)
|
|
63
|
+
|
|
64
|
+
let endParent: any = selection.anchorNode.parentNode
|
|
65
|
+
let endNodeIndex =
|
|
66
|
+
endParent == field.current
|
|
67
|
+
? nodes.length
|
|
68
|
+
: parseInt(endParent.dataset.childIndex)
|
|
69
|
+
|
|
70
|
+
let endPrecedingSum = nodes
|
|
71
|
+
.slice(0, endNodeIndex)
|
|
72
|
+
.map((span: any) => span.textContent.length)
|
|
73
|
+
.reduce((a, b) => a + b, 0)
|
|
74
|
+
|
|
75
|
+
let result = [
|
|
76
|
+
startPrecedingSum + selection.anchorOffset,
|
|
77
|
+
endPrecedingSum + selection.focusOffset,
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
// Sort to make the minimum selection the start selection
|
|
81
|
+
return result.sort((a, b) => a - b)
|
|
57
82
|
}
|
|
58
83
|
|
|
59
84
|
useEffect(() => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (start == 0 && end == 0) return
|
|
85
|
+
setTimeout(function () {
|
|
86
|
+
let [start, end] = text.revert
|
|
64
87
|
|
|
65
|
-
|
|
66
|
-
let oldEnd = end
|
|
88
|
+
if (start == 0 && end == 0) return
|
|
67
89
|
|
|
68
|
-
|
|
90
|
+
let startNode = null
|
|
91
|
+
let endNode = null
|
|
69
92
|
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
93
|
+
let total = 0
|
|
94
|
+
let nodes = Array.from(field.current.childNodes)
|
|
77
95
|
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
97
|
+
let node: any = nodes[i]
|
|
98
|
+
let sum = node.textContent.length + total
|
|
80
99
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
`Current nodes: ${nodes.map((e: any) => e.textContent.length).join(", ")}`
|
|
86
|
-
)
|
|
100
|
+
if (startNode == null && start >= total && start <= sum) {
|
|
101
|
+
startNode = nodes[i]
|
|
102
|
+
start -= total
|
|
103
|
+
}
|
|
87
104
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
if (endNode == null && end > total && end <= sum) {
|
|
106
|
+
endNode = nodes[i]
|
|
107
|
+
end -= total
|
|
108
|
+
}
|
|
91
109
|
|
|
92
|
-
|
|
93
|
-
startNode = nodes[i]
|
|
94
|
-
startNodeIndex = i
|
|
95
|
-
start -= total
|
|
110
|
+
total += node.textContent.length
|
|
96
111
|
}
|
|
97
112
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
endNodeIndex = i
|
|
101
|
-
end -= total
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
total += node.textContent.length
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
console.log(
|
|
108
|
-
`Reverting (${newStart} -> ${newEnd}) to (${oldStart} - ${start} - [node ${startNodeIndex}] -> ${oldEnd} - ${end} - [node ${endNodeIndex}])`
|
|
109
|
-
)
|
|
113
|
+
var range = document.createRange()
|
|
114
|
+
var sel = window.getSelection()
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
range.setStart(startNode.firstChild, start)
|
|
117
|
+
range.setEnd(endNode.firstChild, end)
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
sel.removeAllRanges()
|
|
119
|
-
sel.addRange(range)
|
|
119
|
+
sel.removeAllRanges()
|
|
120
|
+
sel.addRange(range)
|
|
121
|
+
}, 0)
|
|
120
122
|
}, [text.revert])
|
|
121
123
|
|
|
122
124
|
// utility functions
|
|
@@ -309,9 +311,11 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
const onChange = (value) => {
|
|
314
|
+
let [selectionStart, selectionEnd] = getFieldSelection()
|
|
315
|
+
|
|
312
316
|
let difference = value.length - text.content.length
|
|
313
317
|
|
|
314
|
-
let selection =
|
|
318
|
+
let selection = selectionStart - difference
|
|
315
319
|
|
|
316
320
|
let stylings = text.stylings.slice()
|
|
317
321
|
let succeeding = getSucceedStylings(selection)
|
|
@@ -350,14 +354,21 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
350
354
|
|
|
351
355
|
stylings = stylings.filter((_, index) => !spliced.includes(index))
|
|
352
356
|
|
|
353
|
-
|
|
357
|
+
// if (difference == 1) difference = 0
|
|
358
|
+
|
|
359
|
+
setText({
|
|
360
|
+
...text,
|
|
361
|
+
content: value,
|
|
362
|
+
stylings: stylings,
|
|
363
|
+
revert: [selectionStart, selectionEnd],
|
|
364
|
+
})
|
|
354
365
|
}
|
|
355
366
|
|
|
356
367
|
const getContent = () => {
|
|
357
|
-
let content =
|
|
368
|
+
let content = _text.current.content
|
|
358
369
|
|
|
359
370
|
// Get all styling indices
|
|
360
|
-
let indices =
|
|
371
|
+
let indices = _text.current.stylings
|
|
361
372
|
.map(({ start, finish }) => [start, finish])
|
|
362
373
|
.flat()
|
|
363
374
|
|
|
@@ -373,7 +384,7 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
373
384
|
if (indices[0] != 0) indices.unshift(0)
|
|
374
385
|
|
|
375
386
|
// Add last index if not present
|
|
376
|
-
let last =
|
|
387
|
+
let last = content.length
|
|
377
388
|
if (indices[indices.length - 1] != last) indices.push(last)
|
|
378
389
|
|
|
379
390
|
let result = []
|
|
@@ -385,6 +396,24 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
385
396
|
return result
|
|
386
397
|
}
|
|
387
398
|
|
|
399
|
+
// dangerouslySetInnerHTML incorrectly renders when the entire text is highlighted, copied, and then pasted in succession
|
|
400
|
+
useEffect(() => {
|
|
401
|
+
let html = getContent()
|
|
402
|
+
.map((_data, index) => {
|
|
403
|
+
let [start, data] = _data
|
|
404
|
+
|
|
405
|
+
// Get stylings encompassing an index within it's range
|
|
406
|
+
let stylings = getIntersectStylings(start)
|
|
407
|
+
|
|
408
|
+
return `<span class="${stylings
|
|
409
|
+
.map((styling) => styleClasses[styling.type])
|
|
410
|
+
.join(" ")}" data-child-index="${index}">${data}</span>`
|
|
411
|
+
})
|
|
412
|
+
.join("")
|
|
413
|
+
|
|
414
|
+
field.current.innerHTML = html
|
|
415
|
+
}, [text])
|
|
416
|
+
|
|
388
417
|
return (
|
|
389
418
|
<div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-blue-300 shadow-md">
|
|
390
419
|
<div className={clsx("flex w-full flex-row justify-start p-2")}>
|
|
@@ -397,19 +426,22 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
397
426
|
/>
|
|
398
427
|
<Property
|
|
399
428
|
name="I"
|
|
400
|
-
|
|
429
|
+
onMouseDown={(event) => {
|
|
430
|
+
event.preventDefault()
|
|
401
431
|
perform("italic")
|
|
402
432
|
}}
|
|
403
433
|
/>
|
|
404
434
|
<Property
|
|
405
435
|
name="U"
|
|
406
|
-
|
|
436
|
+
onMouseDown={(event) => {
|
|
437
|
+
event.preventDefault()
|
|
407
438
|
perform("under")
|
|
408
439
|
}}
|
|
409
440
|
/>
|
|
410
441
|
<Property
|
|
411
442
|
name="S"
|
|
412
|
-
|
|
443
|
+
onMouseDown={(event) => {
|
|
444
|
+
event.preventDefault()
|
|
413
445
|
perform("strike")
|
|
414
446
|
}}
|
|
415
447
|
/>
|
|
@@ -419,106 +451,11 @@ export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
|
419
451
|
<div
|
|
420
452
|
ref={field}
|
|
421
453
|
contentEditable="true"
|
|
422
|
-
role="textbox"
|
|
423
|
-
dangerouslySetInnerHTML={{
|
|
424
|
-
__html: getContent()
|
|
425
|
-
.map((_data, index) => {
|
|
426
|
-
let [start, data] = _data
|
|
427
|
-
|
|
428
|
-
// Get stylings encompassing an index within it's range
|
|
429
|
-
let stylings = getIntersectStylings(start)
|
|
430
|
-
|
|
431
|
-
return `<span class="${stylings
|
|
432
|
-
.map((styling) => styleClasses[styling.type])
|
|
433
|
-
.join("")}">${data}</span>`
|
|
434
|
-
// return `<span
|
|
435
|
-
// class="${stylings
|
|
436
|
-
// .map((styling) => styleClasses[styling.type])
|
|
437
|
-
// .join(" ")}"
|
|
438
|
-
|
|
439
|
-
// >
|
|
440
|
-
// ${data}
|
|
441
|
-
// </span>`
|
|
442
|
-
})
|
|
443
|
-
.join(""),
|
|
444
|
-
}}
|
|
445
454
|
className="h-[150px] w-full resize-none border-none p-2 outline-none"
|
|
446
|
-
onInput={(event) => {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
let [start, end] = getFieldSelection()
|
|
450
|
-
|
|
451
|
-
// console.log(target)
|
|
452
|
-
// console.log(target.selectionStart)
|
|
453
|
-
// console.log(target.selectionEnd)
|
|
454
|
-
|
|
455
|
-
setText({
|
|
456
|
-
...text,
|
|
457
|
-
content: target.textContent,
|
|
458
|
-
revert: [start, end],
|
|
459
|
-
})
|
|
455
|
+
onInput={(event: any) => {
|
|
456
|
+
onChange(event.target.textContent)
|
|
460
457
|
}}
|
|
461
458
|
></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
459
|
</div>
|
|
523
460
|
<div className="h-[1px] w-full bg-slate-600"> </div>
|
|
524
461
|
<button className="my-1 rounded bg-cyan-800 p-2 py-1 text-white">
|