@sikka/hawa 0.0.233 → 0.0.235
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/_config.yml +1 -0
- package/dist/styles.css +3 -3
- package/docs/CNAME +1 -0
- package/docs/README.md +60 -0
- package/docs/_layouts/default.html +47 -0
- package/docs/assets/css/style.css +366 -0
- package/docs/documentation/229.1b36ccba973b04d66b4c.manager.bundle.js +1 -0
- package/docs/documentation/229.f7a1c971.iframe.bundle.js +1 -0
- package/docs/documentation/295.67c251ec00675ab59b60.manager.bundle.js +1 -0
- package/docs/documentation/51.07b491d3.iframe.bundle.js +2 -0
- package/docs/documentation/51.07b491d3.iframe.bundle.js.LICENSE.txt +8 -0
- package/docs/documentation/51.e44cb3212565a342a42d.manager.bundle.js +2 -0
- package/docs/documentation/51.e44cb3212565a342a42d.manager.bundle.js.LICENSE.txt +8 -0
- package/docs/documentation/551.67cd309b0648b0a52636.manager.bundle.js +1 -0
- package/docs/documentation/551.c82ea8f1.iframe.bundle.js +1 -0
- package/docs/documentation/701.07623c34.iframe.bundle.js +1 -0
- package/docs/documentation/767.e90d0d608aa8557f855d.manager.bundle.js +2 -0
- package/docs/documentation/767.e90d0d608aa8557f855d.manager.bundle.js.LICENSE.txt +94 -0
- package/docs/documentation/807.1424ceed.iframe.bundle.js +2 -0
- package/docs/documentation/807.1424ceed.iframe.bundle.js.LICENSE.txt +31 -0
- package/docs/documentation/807.4e87168c6f719304d458.manager.bundle.js +2 -0
- package/docs/documentation/807.4e87168c6f719304d458.manager.bundle.js.LICENSE.txt +31 -0
- package/docs/documentation/897.386c170cbd1467abc7ca.manager.bundle.js +2 -0
- package/docs/documentation/897.386c170cbd1467abc7ca.manager.bundle.js.LICENSE.txt +12 -0
- package/docs/documentation/897.d9a35fd0.iframe.bundle.js +2 -0
- package/docs/documentation/897.d9a35fd0.iframe.bundle.js.LICENSE.txt +12 -0
- package/docs/documentation/901.ae81c179.iframe.bundle.js +2 -0
- package/docs/documentation/901.ae81c179.iframe.bundle.js.LICENSE.txt +105 -0
- package/docs/documentation/935.3a33e233.iframe.bundle.js +1 -0
- package/docs/documentation/935.df0c782bef63c348b9da.manager.bundle.js +1 -0
- package/docs/documentation/favicon.ico +0 -0
- package/docs/documentation/iframe.html +364 -0
- package/docs/documentation/index.html +59 -0
- package/docs/documentation/main.051275cac7b0dc69501c.manager.bundle.js +1 -0
- package/docs/documentation/main.fb64f936.iframe.bundle.js +1 -0
- package/docs/documentation/project.json +1 -0
- package/docs/documentation/runtime~main.a832da8f0c3235fa0d36.manager.bundle.js +1 -0
- package/docs/documentation/runtime~main.d6831407.iframe.bundle.js +1 -0
- package/es/elements/index.d.ts +0 -1
- package/es/index.es.js +2 -2
- package/lib/elements/index.d.ts +0 -1
- package/lib/index.js +2 -2
- package/package.json +2 -3
- package/src/elements/FloatingComment.tsx +711 -92
- package/src/elements/index.ts +0 -1
- package/src/styles.css +3 -3
- package/es/elements/FloatingCommentCE.d.ts +0 -6
- package/lib/elements/FloatingCommentCE.d.ts +0 -6
- package/src/elements/FloatingCommentCE.tsx +0 -466
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import React, { useRef, useState, useEffect } from "react"
|
|
2
|
-
import { RichTextarea } from "rich-textarea"
|
|
3
2
|
import clsx from "clsx"
|
|
4
3
|
|
|
5
4
|
const Property = (props) => {
|
|
6
5
|
return (
|
|
7
6
|
<div
|
|
8
7
|
className="border-box mr-[5px] flex h-[32px] w-[32px] items-center justify-center rounded bg-gray-400 p-2"
|
|
9
|
-
|
|
8
|
+
onMouseDown={props.onMouseDown}
|
|
10
9
|
>
|
|
11
10
|
{props.name}
|
|
12
11
|
</div>
|
|
@@ -17,8 +16,6 @@ type ComponentTypes = {
|
|
|
17
16
|
foo?: string
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
// TODO: Handle copy pasting by assigning to local storage ?
|
|
21
|
-
|
|
22
19
|
const styleClasses = {
|
|
23
20
|
bold: "font-bold",
|
|
24
21
|
italic: "italic",
|
|
@@ -26,15 +23,118 @@ const styleClasses = {
|
|
|
26
23
|
strike: "line-through",
|
|
27
24
|
}
|
|
28
25
|
|
|
26
|
+
// 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
|
|
27
|
+
|
|
28
|
+
// FIXME: Highlighting the beginning characters of styled text and then pasting text sometimes doesn't register as right intersecting
|
|
29
|
+
// 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
|
|
30
|
+
|
|
29
31
|
export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
|
|
30
32
|
props
|
|
31
33
|
) => {
|
|
32
|
-
const [text,
|
|
34
|
+
const [text, _setText] = useState({
|
|
33
35
|
content: "",
|
|
34
36
|
stylings: [], // A styling is an object with 2 indices specifying a substring of text, and the applied effect
|
|
37
|
+
revert: [0, 0],
|
|
38
|
+
lastCopy: [],
|
|
39
|
+
pasted: { status: false, length: 0 },
|
|
35
40
|
})
|
|
36
41
|
|
|
37
42
|
const field = useRef(null)
|
|
43
|
+
const _text = useRef(text)
|
|
44
|
+
const setText = (data) => {
|
|
45
|
+
_text.current = data
|
|
46
|
+
_setText(data)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const getChildIndex = (child) => {
|
|
50
|
+
for (var i = 0; (child = child.previousSibling); i++);
|
|
51
|
+
return i
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Full reversion achieved !
|
|
55
|
+
const getFieldSelection = () => {
|
|
56
|
+
if (document.activeElement != field.current) return [0, 0]
|
|
57
|
+
|
|
58
|
+
let selection = window.getSelection()
|
|
59
|
+
let nodes = Array.from(field.current.childNodes)
|
|
60
|
+
|
|
61
|
+
nodes = nodes.filter(
|
|
62
|
+
(item: any) => !["#text", "BR"].includes(item.nodeName)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
let startParent: any = selection.anchorNode.parentNode
|
|
66
|
+
|
|
67
|
+
let startNodeIndex =
|
|
68
|
+
startParent == field.current
|
|
69
|
+
? nodes.length
|
|
70
|
+
: // : parseInt(startParent.dataset.childIndex)
|
|
71
|
+
getChildIndex(startParent)
|
|
72
|
+
|
|
73
|
+
let startPrecedingSum = nodes
|
|
74
|
+
.slice(0, startNodeIndex)
|
|
75
|
+
.map((span: any) => span.textContent.length)
|
|
76
|
+
.reduce((a, b) => a + b, 0)
|
|
77
|
+
|
|
78
|
+
let endParent: any = selection.focusNode.parentNode
|
|
79
|
+
let endNodeIndex =
|
|
80
|
+
endParent == field.current
|
|
81
|
+
? nodes.length
|
|
82
|
+
: // : parseInt(endParent.dataset.childIndex)
|
|
83
|
+
getChildIndex(endParent)
|
|
84
|
+
|
|
85
|
+
let endPrecedingSum = nodes
|
|
86
|
+
.slice(0, endNodeIndex)
|
|
87
|
+
.map((span: any) => span.textContent.length)
|
|
88
|
+
.reduce((a, b) => a + b, 0)
|
|
89
|
+
|
|
90
|
+
let result = [
|
|
91
|
+
startPrecedingSum + selection.anchorOffset,
|
|
92
|
+
endPrecedingSum + selection.focusOffset,
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
// Sort to make the minimum selection the start selection
|
|
96
|
+
return result.sort((a, b) => a - b)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
setTimeout(function () {
|
|
101
|
+
let [start, end] = _text.current.revert
|
|
102
|
+
|
|
103
|
+
if (start == 0 && end == 0) return
|
|
104
|
+
|
|
105
|
+
let startNode = null
|
|
106
|
+
let endNode = null
|
|
107
|
+
|
|
108
|
+
let total = 0
|
|
109
|
+
let nodes = Array.from(field.current.childNodes)
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
112
|
+
let node: any = nodes[i]
|
|
113
|
+
let sum = node.textContent.length + total
|
|
114
|
+
|
|
115
|
+
if (startNode == null && start >= total && start <= sum) {
|
|
116
|
+
startNode = nodes[i]
|
|
117
|
+
start -= total
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (endNode == null && end > total && end <= sum) {
|
|
121
|
+
endNode = nodes[i]
|
|
122
|
+
end -= total
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
total += node.textContent.length
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var range = document.createRange()
|
|
129
|
+
var sel = window.getSelection()
|
|
130
|
+
|
|
131
|
+
range.setStart(startNode.firstChild, start)
|
|
132
|
+
range.setEnd(endNode.firstChild, end)
|
|
133
|
+
|
|
134
|
+
sel.removeAllRanges()
|
|
135
|
+
sel.addRange(range)
|
|
136
|
+
}, 1)
|
|
137
|
+
}, [text.revert])
|
|
38
138
|
|
|
39
139
|
// utility functions
|
|
40
140
|
const getRange = (start, end) => {
|
|
@@ -156,9 +256,8 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
|
|
|
156
256
|
}
|
|
157
257
|
|
|
158
258
|
const perform = (id) => {
|
|
159
|
-
let stylings =
|
|
160
|
-
|
|
161
|
-
var selectionEnd = field.current.selectionEnd
|
|
259
|
+
let stylings = _text.current.stylings.slice()
|
|
260
|
+
let [selectionStart, selectionEnd] = getFieldSelection()
|
|
162
261
|
|
|
163
262
|
if (selectionStart == selectionEnd) return
|
|
164
263
|
|
|
@@ -194,9 +293,40 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
|
|
|
194
293
|
setText({
|
|
195
294
|
...text,
|
|
196
295
|
stylings: result,
|
|
296
|
+
revert: [selectionStart, selectionEnd],
|
|
197
297
|
})
|
|
198
298
|
}
|
|
199
299
|
|
|
300
|
+
const getRangeIntersectStylings = (start, end, startOffset, finishOffset) => {
|
|
301
|
+
// Get all stylings intersecting
|
|
302
|
+
let stylings = []
|
|
303
|
+
for (let i = start; i < end; i++) {
|
|
304
|
+
stylings.push(getIntersectStylings(i, startOffset, finishOffset))
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Remove duplicates
|
|
308
|
+
stylings = stylings.flat().filter((item) => item)
|
|
309
|
+
stylings = stylings.filter(
|
|
310
|
+
(item, index) =>
|
|
311
|
+
stylings.findIndex(
|
|
312
|
+
(_item) =>
|
|
313
|
+
_item.start == item.start &&
|
|
314
|
+
_item.finish == item.finish &&
|
|
315
|
+
_item.type == item.type
|
|
316
|
+
) == index
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return stylings
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const compareStylings = (styling1, styling2) => {
|
|
323
|
+
return (
|
|
324
|
+
styling1.type == styling2.type &&
|
|
325
|
+
styling1.start == styling2.start &&
|
|
326
|
+
styling1.finish == styling2.finish
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
200
330
|
// Get stylings encompassing an index within it's range
|
|
201
331
|
const getIntersectStylings = (index, startOffset = 0, finishOffset = 0) => {
|
|
202
332
|
// Find all stylings with encompassing range
|
|
@@ -225,140 +355,629 @@ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
|
|
|
225
355
|
)
|
|
226
356
|
}
|
|
227
357
|
|
|
228
|
-
const
|
|
229
|
-
let
|
|
358
|
+
const handleScenario = (stylings, predicate) => {
|
|
359
|
+
let matches = stylings.filter(({ start: _start, finish: _finish }) =>
|
|
360
|
+
predicate(_start, _finish)
|
|
361
|
+
)
|
|
230
362
|
|
|
231
|
-
let
|
|
363
|
+
let indices = []
|
|
364
|
+
|
|
365
|
+
stylings.map((styling, index) => {
|
|
366
|
+
let result = matches.find((item) => compareStylings(styling, item))
|
|
367
|
+
if (!result) return
|
|
368
|
+
indices.push({
|
|
369
|
+
index: index,
|
|
370
|
+
type: styling.type,
|
|
371
|
+
start: styling.start,
|
|
372
|
+
finish: styling.finish,
|
|
373
|
+
})
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
return indices
|
|
377
|
+
}
|
|
232
378
|
|
|
233
|
-
|
|
234
|
-
let
|
|
379
|
+
const handleDeletion = (length, start, end) => {
|
|
380
|
+
let stylings = _text.current.stylings.slice()
|
|
235
381
|
let changes = []
|
|
236
382
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
383
|
+
// TODO: Refactor
|
|
384
|
+
|
|
385
|
+
// Offset all succeeding stylings by length
|
|
386
|
+
// changes.push(
|
|
387
|
+
// handleScenario(
|
|
388
|
+
// stylings,
|
|
389
|
+
// (_start, _finish) => start < _start && end <= start
|
|
390
|
+
// ).map((styling) => {
|
|
391
|
+
// return {
|
|
392
|
+
// ...styling,
|
|
393
|
+
// start: styling.start - length,
|
|
394
|
+
// finish: styling.finish - length,
|
|
395
|
+
// }
|
|
396
|
+
// })
|
|
397
|
+
// )
|
|
398
|
+
|
|
399
|
+
let succeeding = stylings.filter(
|
|
400
|
+
({ start: _start }) => start < _start && end <= _start
|
|
401
|
+
)
|
|
402
|
+
stylings.map((styling, index) => {
|
|
403
|
+
let result = succeeding.find((item) => compareStylings(styling, item))
|
|
404
|
+
if (!result) return
|
|
405
|
+
|
|
406
|
+
changes.push({
|
|
407
|
+
index: index,
|
|
408
|
+
start: styling.start - length,
|
|
409
|
+
finish: styling.finish - length,
|
|
410
|
+
})
|
|
411
|
+
})
|
|
240
412
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
413
|
+
// Handle complete encapsulation over styling
|
|
414
|
+
let encapsulating = stylings.filter(
|
|
415
|
+
({ start: _start, finish: _finish }) => start <= _start && end >= _finish
|
|
416
|
+
)
|
|
417
|
+
stylings.map((styling, index) => {
|
|
418
|
+
let result = encapsulating.find((item) => compareStylings(styling, item))
|
|
419
|
+
if (!result) return
|
|
420
|
+
|
|
421
|
+
// This will effectively remove the styling, since collapsed ranges are automatically removed
|
|
422
|
+
changes.push({
|
|
423
|
+
index: index,
|
|
424
|
+
start: styling.start,
|
|
425
|
+
finish: styling.start,
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
// Handle deletion being encapsulated by styling
|
|
430
|
+
let encapsulated = stylings.filter(
|
|
431
|
+
({ start: _start, finish: _finish }) =>
|
|
432
|
+
(start > _start && end <= _finish) || (start >= _start && end < _finish)
|
|
433
|
+
)
|
|
434
|
+
stylings.map((styling, index) => {
|
|
435
|
+
let result = encapsulated.find((item) => compareStylings(styling, item))
|
|
436
|
+
if (!result) return
|
|
437
|
+
|
|
438
|
+
changes.push({
|
|
439
|
+
index: index,
|
|
440
|
+
start: styling.start,
|
|
441
|
+
finish: styling.finish - length,
|
|
442
|
+
})
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
// Handle deletion being encapsulated by styling with left resumption
|
|
446
|
+
let leftResumption = stylings.filter(
|
|
447
|
+
({ start: _start, finish: _finish }) =>
|
|
448
|
+
end < _finish && end > _start && start < _start
|
|
449
|
+
)
|
|
450
|
+
stylings.map((styling, index) => {
|
|
451
|
+
let result = leftResumption.find((item) => compareStylings(styling, item))
|
|
452
|
+
if (!result) return
|
|
453
|
+
|
|
454
|
+
changes.push({
|
|
455
|
+
index: index,
|
|
456
|
+
start: getMaximum([end, styling.start]) - length,
|
|
457
|
+
finish: styling.finish - length,
|
|
458
|
+
})
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
let rightResumption = stylings.filter(
|
|
462
|
+
({ start: _start, finish: _finish }) =>
|
|
463
|
+
start > _start && start < _finish && end > _finish
|
|
464
|
+
)
|
|
465
|
+
stylings.map((styling, index) => {
|
|
466
|
+
let result = rightResumption.find((item) =>
|
|
467
|
+
compareStylings(styling, item)
|
|
468
|
+
)
|
|
469
|
+
if (!result) return
|
|
470
|
+
|
|
471
|
+
changes.push({
|
|
472
|
+
index: index,
|
|
473
|
+
start: styling.start,
|
|
474
|
+
finish: getMinimum([start, styling.finish]),
|
|
475
|
+
})
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
// Apply changes
|
|
479
|
+
changes.map(({ index, start, finish }) => {
|
|
480
|
+
stylings[index] = {
|
|
481
|
+
...stylings[index],
|
|
482
|
+
start: start,
|
|
483
|
+
finish: finish,
|
|
484
|
+
}
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
// Handle complete encapsulation by styling
|
|
488
|
+
// stylings = stylings.map((styling) => {
|
|
489
|
+
// if (start >= styling.start && end <= styling.finish) {
|
|
490
|
+
// console.log("hi")
|
|
491
|
+
// }
|
|
247
492
|
|
|
248
|
-
|
|
493
|
+
// return styling
|
|
494
|
+
// })
|
|
249
495
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
496
|
+
// If deletion is surrounded by styling, decrease finish by length (this might not include first character, but try first)
|
|
497
|
+
// 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
|
|
498
|
+
// If deletion is intersecting wit ha styling after it, get minimum between deletion start and styling finish, and set the finish to it
|
|
499
|
+
|
|
500
|
+
// console.log(succeeding)
|
|
501
|
+
|
|
502
|
+
// If the start and finish of any styling is greater than the new length, remove it
|
|
503
|
+
|
|
504
|
+
return stylings
|
|
505
|
+
}
|
|
253
506
|
|
|
254
|
-
|
|
507
|
+
const handleAddition = (length, start, end) => {
|
|
508
|
+
start -= length + 1
|
|
509
|
+
end -= length + 1
|
|
510
|
+
|
|
511
|
+
let stylings = _text.current.stylings.slice()
|
|
512
|
+
|
|
513
|
+
let changes = []
|
|
514
|
+
|
|
515
|
+
let succeeding = handleScenario(
|
|
516
|
+
stylings,
|
|
517
|
+
(_start, _finish) => _start > start && _start + 1 >= end
|
|
518
|
+
).map((styling) => {
|
|
519
|
+
return {
|
|
520
|
+
...styling,
|
|
521
|
+
start: styling.start + length,
|
|
522
|
+
finish: styling.finish + length,
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
changes.push(succeeding)
|
|
526
|
+
|
|
527
|
+
console.log(`Addition (${length})`)
|
|
528
|
+
console.log([start, end])
|
|
529
|
+
if (stylings.length > 0) {
|
|
530
|
+
console.log([stylings[0].start, stylings[0].finish])
|
|
255
531
|
}
|
|
256
532
|
|
|
257
|
-
|
|
258
|
-
|
|
533
|
+
// console.log(succeeding)
|
|
534
|
+
|
|
535
|
+
let preceding = handleScenario(
|
|
536
|
+
stylings,
|
|
537
|
+
// (_start, _finish) => start < _finish && start >= _start + 2 && length == 1 // This is to not style pasted text
|
|
538
|
+
(_start, _finish) => start + 1 == _finish && length == 1 // This is to not style pasted text
|
|
539
|
+
).map((styling) => {
|
|
540
|
+
return {
|
|
541
|
+
...styling,
|
|
542
|
+
start: styling.start,
|
|
543
|
+
finish: styling.finish + length,
|
|
544
|
+
}
|
|
545
|
+
})
|
|
546
|
+
changes.push(preceding)
|
|
547
|
+
|
|
548
|
+
let encapsulated = handleScenario(
|
|
549
|
+
stylings,
|
|
550
|
+
(_start, _finish) => start >= _start && end + 1 < _finish
|
|
551
|
+
).map((styling) => {
|
|
552
|
+
return {
|
|
553
|
+
...styling,
|
|
554
|
+
finish: styling.finish + length,
|
|
555
|
+
}
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
changes.push(encapsulated)
|
|
559
|
+
|
|
560
|
+
console.log(length)
|
|
561
|
+
|
|
562
|
+
console.log(`Encapsulated: ${encapsulated.length}`)
|
|
563
|
+
|
|
564
|
+
console.log(`Succeeding: ${succeeding.length}`)
|
|
565
|
+
console.log(`Preceding: ${preceding.length}`)
|
|
566
|
+
// console.log(preceding)
|
|
567
|
+
|
|
568
|
+
changes.flat().map(({ index, start, finish }) => {
|
|
569
|
+
stylings[index] = {
|
|
570
|
+
...stylings[index],
|
|
571
|
+
start: start,
|
|
572
|
+
finish: finish,
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
return stylings
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const handlePaste = (stylings, difference, start, end) => {
|
|
580
|
+
console.log(`Paste:`)
|
|
581
|
+
console.log([start, end])
|
|
582
|
+
console.log(stylings)
|
|
583
|
+
let changes = []
|
|
584
|
+
|
|
585
|
+
// Get stylings being encapsulated by pasting range
|
|
586
|
+
|
|
587
|
+
let encapsulating = handleScenario(
|
|
588
|
+
stylings,
|
|
589
|
+
(_start, _end) => start <= _start && end >= _end // This is needed because of conflict with addition/deletion
|
|
590
|
+
).map((styling) => {
|
|
591
|
+
// Effective removal
|
|
592
|
+
return {
|
|
593
|
+
...styling,
|
|
594
|
+
start: styling.start,
|
|
595
|
+
finish: styling.start,
|
|
596
|
+
}
|
|
597
|
+
})
|
|
598
|
+
changes.push(encapsulating)
|
|
599
|
+
|
|
600
|
+
// Get encapsulated stylings
|
|
601
|
+
let encapsulated = handleScenario(
|
|
602
|
+
stylings,
|
|
603
|
+
(_start, _end) => _start < start && _end > end
|
|
604
|
+
).map((styling) => {
|
|
605
|
+
// Right splice
|
|
606
|
+
changes.push({
|
|
607
|
+
...styling,
|
|
608
|
+
index: -1,
|
|
609
|
+
start: end,
|
|
610
|
+
finish: styling.finish,
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
// Left splice
|
|
614
|
+
changes.push({
|
|
615
|
+
...styling,
|
|
616
|
+
index: -1,
|
|
617
|
+
start: styling.start,
|
|
618
|
+
finish: start,
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
// Effective removal
|
|
622
|
+
return {
|
|
623
|
+
...styling,
|
|
624
|
+
start: styling.start,
|
|
625
|
+
finish: styling.start,
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
changes.push(encapsulated)
|
|
629
|
+
|
|
630
|
+
// Get intersecting stylings from the left
|
|
631
|
+
let left = handleScenario(
|
|
632
|
+
stylings,
|
|
633
|
+
(_start, _end) =>
|
|
634
|
+
_end > start && _end <= end - difference + 1 && _start < start
|
|
635
|
+
).map((styling) => {
|
|
636
|
+
return {
|
|
637
|
+
...styling,
|
|
638
|
+
finish: start,
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
changes.push(left)
|
|
643
|
+
|
|
644
|
+
// Get intersecting stylings from the right
|
|
645
|
+
let right = handleScenario(
|
|
646
|
+
stylings,
|
|
647
|
+
(_start, _end) =>
|
|
648
|
+
_end > end - difference && _start >= start && _start < end - difference
|
|
649
|
+
).map((styling) => {
|
|
650
|
+
return {
|
|
651
|
+
...styling,
|
|
652
|
+
start: end,
|
|
653
|
+
}
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
changes.push(right)
|
|
657
|
+
|
|
658
|
+
console.log(`Encapsulating: ${encapsulating.length}`)
|
|
659
|
+
console.log(`Encapsulated: ${encapsulated.length}`)
|
|
660
|
+
console.log(`Left Intersecting: ${left.length}`)
|
|
661
|
+
console.log(`Right Intersecting: ${right.length}`)
|
|
662
|
+
|
|
663
|
+
changes = changes.flat()
|
|
664
|
+
changes.map((styling) => {
|
|
665
|
+
let { index, start, finish } = styling
|
|
666
|
+
if (index == -1) {
|
|
667
|
+
stylings.push({
|
|
668
|
+
type: styling.type,
|
|
669
|
+
start: start,
|
|
670
|
+
finish: finish,
|
|
671
|
+
})
|
|
672
|
+
return
|
|
673
|
+
}
|
|
674
|
+
|
|
259
675
|
stylings[index] = {
|
|
260
676
|
...stylings[index],
|
|
261
677
|
start: start,
|
|
262
678
|
finish: finish,
|
|
263
679
|
}
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
console.log(stylings)
|
|
683
|
+
|
|
684
|
+
return stylings
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const onChange = (value) => {
|
|
688
|
+
setTimeout(function () {
|
|
689
|
+
let [selectionStart, selectionEnd] = getFieldSelection()
|
|
690
|
+
|
|
691
|
+
let difference = value.length - _text.current.content.length
|
|
692
|
+
|
|
693
|
+
let start = selectionStart - difference
|
|
694
|
+
let end = selectionEnd - difference
|
|
695
|
+
|
|
696
|
+
let stylings = _text.current.stylings.slice()
|
|
697
|
+
|
|
698
|
+
let succeeding = getSucceedStylings(start)
|
|
699
|
+
let changes = []
|
|
700
|
+
|
|
701
|
+
for (let succeed of succeeding) {
|
|
702
|
+
let index = getStylingIndex(succeed)
|
|
703
|
+
let styling = stylings[index]
|
|
704
|
+
|
|
705
|
+
changes.push([
|
|
706
|
+
index,
|
|
707
|
+
styling.start + difference,
|
|
708
|
+
styling.finish + difference,
|
|
709
|
+
])
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (difference < 0) {
|
|
713
|
+
stylings = handleDeletion(
|
|
714
|
+
Math.abs(difference),
|
|
715
|
+
selectionStart,
|
|
716
|
+
selectionEnd + Math.abs(difference)
|
|
717
|
+
)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (difference > 0) {
|
|
721
|
+
stylings = handleAddition(
|
|
722
|
+
Math.abs(difference),
|
|
723
|
+
selectionStart,
|
|
724
|
+
selectionEnd
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Remove empty stylings and invisible stylings
|
|
729
|
+
stylings = stylings.filter(
|
|
730
|
+
(styling) =>
|
|
731
|
+
!(
|
|
732
|
+
styling.start == styling.finish ||
|
|
733
|
+
(styling.start >= value.length && styling.finish >= value.length)
|
|
734
|
+
)
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
if (_text.current.pasted.status) {
|
|
738
|
+
stylings = handlePaste(
|
|
739
|
+
stylings,
|
|
740
|
+
difference,
|
|
741
|
+
selectionStart - _text.current.pasted.length,
|
|
742
|
+
selectionStart
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
for (let styling of _text.current.lastCopy) {
|
|
746
|
+
styling = {
|
|
747
|
+
...styling,
|
|
748
|
+
start: styling.start + selectionStart - _text.current.pasted.length,
|
|
749
|
+
finish:
|
|
750
|
+
styling.finish + selectionStart - _text.current.pasted.length,
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
stylings.push(styling)
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Remove empty stylings and invisible stylings
|
|
758
|
+
stylings = stylings.filter(
|
|
759
|
+
(styling) =>
|
|
760
|
+
!(
|
|
761
|
+
styling.start == styling.finish ||
|
|
762
|
+
(styling.start >= value.length && styling.finish >= value.length)
|
|
763
|
+
)
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
setText({
|
|
767
|
+
..._text.current,
|
|
768
|
+
content: value,
|
|
769
|
+
stylings: stylings,
|
|
770
|
+
revert: [selectionStart, selectionEnd],
|
|
771
|
+
pasted: { status: false, length: 0 },
|
|
772
|
+
})
|
|
773
|
+
}, 0)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const getContent = () => {
|
|
777
|
+
let content = _text.current.content
|
|
778
|
+
|
|
779
|
+
// Get all styling indices
|
|
780
|
+
let indices = _text.current.stylings
|
|
781
|
+
.map(({ start, finish }) => [start, finish])
|
|
782
|
+
.flat()
|
|
783
|
+
|
|
784
|
+
// Sort ascendingly
|
|
785
|
+
indices = indices.sort((a, b) => a - b)
|
|
786
|
+
|
|
787
|
+
// Remove duplicates
|
|
788
|
+
indices = indices.filter(
|
|
789
|
+
(element, index) => indices.indexOf(element) == index
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
// Add first index if not present
|
|
793
|
+
if (indices[0] != 0) indices.unshift(0)
|
|
264
794
|
|
|
265
|
-
|
|
795
|
+
// Add last index if not present
|
|
796
|
+
let last = content.length
|
|
797
|
+
if (indices[indices.length - 1] != last) indices.push(last)
|
|
798
|
+
|
|
799
|
+
let result = []
|
|
800
|
+
|
|
801
|
+
for (let i = 0; i < indices.length - 1; i++) {
|
|
802
|
+
result.push([indices[i], content.substring(indices[i], indices[i + 1])])
|
|
266
803
|
}
|
|
267
804
|
|
|
268
|
-
|
|
805
|
+
return result
|
|
806
|
+
}
|
|
269
807
|
|
|
270
|
-
|
|
808
|
+
const serializeStyleInRange = (start, end) => {
|
|
809
|
+
// Get all stylings intersecting
|
|
810
|
+
let stylings = []
|
|
811
|
+
for (let i = start; i < end; i++) {
|
|
812
|
+
stylings.push(getIntersectStylings(i))
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Remove duplicates
|
|
816
|
+
stylings = stylings.flat().filter((item) => item)
|
|
817
|
+
stylings = stylings.filter(
|
|
818
|
+
(item, index) =>
|
|
819
|
+
stylings.findIndex(
|
|
820
|
+
(_item) =>
|
|
821
|
+
_item.start == item.start &&
|
|
822
|
+
_item.finish == item.finish &&
|
|
823
|
+
_item.type == item.type
|
|
824
|
+
) == index
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
// Clamp start and finish values and offset by start index
|
|
828
|
+
stylings = stylings.map((styling) => {
|
|
829
|
+
return {
|
|
830
|
+
...styling,
|
|
831
|
+
start: getMaximum([styling.start, start]) - start,
|
|
832
|
+
finish: getMinimum([styling.finish, end]) - start,
|
|
833
|
+
}
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
return stylings
|
|
271
837
|
}
|
|
272
838
|
|
|
839
|
+
// dangerouslySetInnerHTML incorrectly renders when the entire text is highlighted, copied, and then pasted in succession
|
|
840
|
+
useEffect(() => {
|
|
841
|
+
let html = getContent()
|
|
842
|
+
.map((_data, index) => {
|
|
843
|
+
let [start, data] = _data
|
|
844
|
+
|
|
845
|
+
// Get stylings encompassing an index within it's range
|
|
846
|
+
let stylings = getIntersectStylings(start)
|
|
847
|
+
// console.log(data)
|
|
848
|
+
// console.log(stylings)
|
|
849
|
+
return `<span class="${stylings
|
|
850
|
+
.map((styling) => styleClasses[styling.type])
|
|
851
|
+
.join(" ")}" data-child-index="${index}">${data}</span>`
|
|
852
|
+
})
|
|
853
|
+
.join("")
|
|
854
|
+
|
|
855
|
+
field.current.innerHTML = html
|
|
856
|
+
}, [text.content, text.stylings, text.revert])
|
|
857
|
+
|
|
273
858
|
return (
|
|
274
|
-
<div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-
|
|
859
|
+
<div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-blue-300 shadow-md">
|
|
275
860
|
<div className={clsx("flex w-full flex-row justify-start p-2")}>
|
|
276
861
|
<Property
|
|
277
862
|
name="B"
|
|
278
|
-
|
|
863
|
+
onMouseDown={(event) => {
|
|
864
|
+
event.preventDefault() // This does not take focus away from field which allows the function to retrieve the current selection data
|
|
279
865
|
perform("bold")
|
|
280
866
|
}}
|
|
281
867
|
/>
|
|
282
868
|
<Property
|
|
283
869
|
name="I"
|
|
284
|
-
|
|
870
|
+
onMouseDown={(event) => {
|
|
871
|
+
event.preventDefault()
|
|
285
872
|
perform("italic")
|
|
286
873
|
}}
|
|
287
874
|
/>
|
|
288
875
|
<Property
|
|
289
876
|
name="U"
|
|
290
|
-
|
|
877
|
+
onMouseDown={(event) => {
|
|
878
|
+
event.preventDefault()
|
|
291
879
|
perform("under")
|
|
292
880
|
}}
|
|
293
881
|
/>
|
|
294
882
|
<Property
|
|
295
883
|
name="S"
|
|
296
|
-
|
|
884
|
+
onMouseDown={(event) => {
|
|
885
|
+
event.preventDefault()
|
|
297
886
|
perform("strike")
|
|
298
887
|
}}
|
|
299
888
|
/>
|
|
300
889
|
</div>
|
|
301
890
|
<div className="h-[1px] w-full bg-slate-600"> </div>
|
|
302
891
|
<div className="w-full">
|
|
303
|
-
<
|
|
892
|
+
<div
|
|
304
893
|
ref={field}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
894
|
+
contentEditable="true"
|
|
895
|
+
className="h-[150px] w-full resize-none overflow-auto overflow-x-hidden border-none p-2 outline-none"
|
|
896
|
+
onPaste={(event) => {
|
|
897
|
+
// pastes all copied text from the content editable as plain text
|
|
898
|
+
event.preventDefault()
|
|
899
|
+
const data = event.clipboardData.getData("text/plain")
|
|
900
|
+
document.execCommand("insertHTML", false, data)
|
|
901
|
+
|
|
902
|
+
console.log(data)
|
|
903
|
+
|
|
904
|
+
setText({
|
|
905
|
+
..._text.current,
|
|
906
|
+
pasted: { status: true, length: data.length },
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
// let [start, end] = getFieldSelection()
|
|
910
|
+
|
|
911
|
+
// console.log(index)
|
|
912
|
+
// console.log(start)
|
|
913
|
+
|
|
914
|
+
// let index = start - data.length
|
|
915
|
+
// let stylings = text.stylings.slice()
|
|
916
|
+
|
|
917
|
+
// FIXME:
|
|
918
|
+
// stylings.push({
|
|
919
|
+
// type: "bold",
|
|
920
|
+
// start: 5,
|
|
921
|
+
// finish: 7,
|
|
922
|
+
// })
|
|
923
|
+
|
|
924
|
+
// let copy = text.lastCopy
|
|
925
|
+
// if (copy.length != 0) {
|
|
926
|
+
// for (let styling of copy) {
|
|
927
|
+
// stylings.push({
|
|
928
|
+
// type: styling.type,
|
|
929
|
+
// start: styling.start + start,
|
|
930
|
+
// finish: styling.finish + start,
|
|
931
|
+
// })
|
|
932
|
+
// }
|
|
933
|
+
// }
|
|
934
|
+
|
|
935
|
+
// let content: any = text.content
|
|
936
|
+
// let original = content.length
|
|
937
|
+
|
|
938
|
+
// // If not collapsed, insert text
|
|
939
|
+
// if (start == end) {
|
|
940
|
+
// content = content.split("")
|
|
941
|
+
// content.splice(start, 0, data)
|
|
942
|
+
// content = content.join("")
|
|
943
|
+
// } else {
|
|
944
|
+
// console.log(content)
|
|
945
|
+
// // Otherwise, replace substring
|
|
946
|
+
// content =
|
|
947
|
+
// content.substring(0, start) +
|
|
948
|
+
// data +
|
|
949
|
+
// content.substring(end, content.length)
|
|
950
|
+
// }
|
|
951
|
+
|
|
952
|
+
// let difference = content.length - original
|
|
953
|
+
|
|
954
|
+
// stylings = handleDeletion()
|
|
955
|
+
|
|
956
|
+
// setText({
|
|
957
|
+
// ...text,
|
|
958
|
+
// content: content,
|
|
959
|
+
// stylings: stylings,
|
|
960
|
+
// revert: [start + data.length, start + data.length],
|
|
961
|
+
// })
|
|
962
|
+
}}
|
|
963
|
+
onInput={(event: any) => {
|
|
964
|
+
onChange(event.target.textContent)
|
|
310
965
|
}}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
let
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
indices = indices.sort((a, b) => a - b)
|
|
320
|
-
|
|
321
|
-
// Remove duplicates
|
|
322
|
-
indices = indices.filter(
|
|
323
|
-
(element, index) => indices.indexOf(element) == index
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
// Add first index if not present
|
|
327
|
-
if (indices[0] != 0) indices.unshift(0)
|
|
328
|
-
|
|
329
|
-
// Add last index if not present
|
|
330
|
-
let last = text.content.length
|
|
331
|
-
if (indices[indices.length - 1] != last) indices.push(last)
|
|
332
|
-
|
|
333
|
-
let result = []
|
|
334
|
-
|
|
335
|
-
for (let i = 0; i < indices.length - 1; i++) {
|
|
336
|
-
result.push([
|
|
337
|
-
indices[i],
|
|
338
|
-
value.substring(indices[i], indices[i + 1]),
|
|
339
|
-
])
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return result.map((_data, index) => {
|
|
343
|
-
let [start, data] = _data
|
|
344
|
-
|
|
345
|
-
// Get stylings encompassing an index within it's range
|
|
346
|
-
let stylings = getIntersectStylings(start)
|
|
347
|
-
|
|
348
|
-
return (
|
|
349
|
-
<span
|
|
350
|
-
key={index}
|
|
351
|
-
className={`${stylings
|
|
352
|
-
.map((styling) => styleClasses[styling.type])
|
|
353
|
-
.join(" ")}
|
|
354
|
-
`}
|
|
355
|
-
>
|
|
356
|
-
{data}
|
|
357
|
-
</span>
|
|
358
|
-
)
|
|
966
|
+
onCopy={(event) => {
|
|
967
|
+
let [start, end] = getFieldSelection()
|
|
968
|
+
|
|
969
|
+
let data = serializeStyleInRange(start, end)
|
|
970
|
+
|
|
971
|
+
setText({
|
|
972
|
+
...text,
|
|
973
|
+
lastCopy: data,
|
|
359
974
|
})
|
|
360
975
|
}}
|
|
361
|
-
|
|
976
|
+
// onKeyDown={(event: any) => {
|
|
977
|
+
// event.preventDefault()
|
|
978
|
+
// console.log(event)
|
|
979
|
+
// }}
|
|
980
|
+
></div>
|
|
362
981
|
</div>
|
|
363
982
|
<div className="h-[1px] w-full bg-slate-600"> </div>
|
|
364
983
|
<button className="my-1 rounded bg-cyan-800 p-2 py-1 text-white">
|