@sikka/hawa 0.0.234 → 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 +0 -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 +0 -3
- package/es/elements/FloatingCommentCE.d.ts +0 -6
- package/lib/elements/FloatingCommentCE.d.ts +0 -6
- package/src/elements/FloatingCommentCE.tsx +0 -852
|
@@ -1,852 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useState, useEffect } from "react"
|
|
2
|
-
import { RichTextarea } from "rich-textarea"
|
|
3
|
-
import clsx from "clsx"
|
|
4
|
-
|
|
5
|
-
const Property = (props) => {
|
|
6
|
-
return (
|
|
7
|
-
<div
|
|
8
|
-
className="border-box mr-[5px] flex h-[32px] w-[32px] items-center justify-center rounded bg-gray-400 p-2"
|
|
9
|
-
onMouseDown={props.onMouseDown}
|
|
10
|
-
>
|
|
11
|
-
{props.name}
|
|
12
|
-
</div>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type ComponentTypes = {
|
|
17
|
-
foo?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const styleClasses = {
|
|
21
|
-
bold: "font-bold",
|
|
22
|
-
italic: "italic",
|
|
23
|
-
under: "underline",
|
|
24
|
-
strike: "line-through",
|
|
25
|
-
}
|
|
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
|
-
|
|
36
|
-
export const FloatingCommentCE: React.FunctionComponent<ComponentTypes> = (
|
|
37
|
-
props
|
|
38
|
-
) => {
|
|
39
|
-
const [text, _setText] = useState({
|
|
40
|
-
content: "",
|
|
41
|
-
stylings: [], // A styling is an object with 2 indices specifying a substring of text, and the applied effect
|
|
42
|
-
revert: [0, 0],
|
|
43
|
-
lastCopy: [],
|
|
44
|
-
pasted: { status: false, length: 0 },
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const field = useRef(null)
|
|
48
|
-
const _text = useRef(text)
|
|
49
|
-
const setText = (data) => {
|
|
50
|
-
_text.current = data
|
|
51
|
-
_setText(data)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const getChildIndex = (child) => {
|
|
55
|
-
for (var i = 0; (child = child.previousSibling); i++);
|
|
56
|
-
return i
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Full reversion achieved !
|
|
60
|
-
const getFieldSelection = () => {
|
|
61
|
-
if (document.activeElement != field.current) return [0, 0]
|
|
62
|
-
|
|
63
|
-
let selection = window.getSelection()
|
|
64
|
-
let nodes = Array.from(field.current.childNodes)
|
|
65
|
-
|
|
66
|
-
nodes = nodes.filter(
|
|
67
|
-
(item: any) => !["#text", "BR"].includes(item.nodeName)
|
|
68
|
-
)
|
|
69
|
-
|
|
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
|
-
]
|
|
99
|
-
|
|
100
|
-
// Sort to make the minimum selection the start selection
|
|
101
|
-
return result.sort((a, b) => a - b)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
setTimeout(function () {
|
|
106
|
-
let [start, end] = _text.current.revert
|
|
107
|
-
|
|
108
|
-
if (start == 0 && end == 0) return
|
|
109
|
-
|
|
110
|
-
let startNode = null
|
|
111
|
-
let endNode = null
|
|
112
|
-
|
|
113
|
-
let total = 0
|
|
114
|
-
let nodes = Array.from(field.current.childNodes)
|
|
115
|
-
|
|
116
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
117
|
-
let node: any = nodes[i]
|
|
118
|
-
let sum = node.textContent.length + total
|
|
119
|
-
|
|
120
|
-
if (startNode == null && start >= total && start <= sum) {
|
|
121
|
-
startNode = nodes[i]
|
|
122
|
-
start -= total
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (endNode == null && end > total && end <= sum) {
|
|
126
|
-
endNode = nodes[i]
|
|
127
|
-
end -= total
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
total += node.textContent.length
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
var range = document.createRange()
|
|
134
|
-
var sel = window.getSelection()
|
|
135
|
-
|
|
136
|
-
range.setStart(startNode.firstChild, start)
|
|
137
|
-
range.setEnd(endNode.firstChild, end)
|
|
138
|
-
|
|
139
|
-
sel.removeAllRanges()
|
|
140
|
-
sel.addRange(range)
|
|
141
|
-
}, 1)
|
|
142
|
-
}, [text.revert])
|
|
143
|
-
|
|
144
|
-
// utility functions
|
|
145
|
-
const getRange = (start, end) => {
|
|
146
|
-
let result = []
|
|
147
|
-
for (let i = start; i <= end; i++) {
|
|
148
|
-
result.push(i)
|
|
149
|
-
}
|
|
150
|
-
return result
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const intersection = (setA, setB) => {
|
|
154
|
-
const _intersection = new Set()
|
|
155
|
-
for (const elem of setB) {
|
|
156
|
-
if (setA.has(elem)) {
|
|
157
|
-
_intersection.add(elem)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return _intersection
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const getMinimum = (array) => {
|
|
164
|
-
return array.sort((a, b) => a - b)[0]
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const getMaximum = (array) => {
|
|
168
|
-
return array.sort((a, b) => b - a)[0]
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// -1 - types dont match
|
|
172
|
-
// 0 - s1 is surrounded or on the edge of the s2
|
|
173
|
-
// 1 - s1 intersects with s2
|
|
174
|
-
// 2 - s1 does not intersect with s2
|
|
175
|
-
const getCorrelation = (styling1, styling2) => {
|
|
176
|
-
if (styling1.type != styling2.type) return -1
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
styling2.start <= styling1.start &&
|
|
180
|
-
styling2.finish >= styling1.finish
|
|
181
|
-
) {
|
|
182
|
-
return 0
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let indices1 = new Set(getRange(styling1.start - 1, styling1.finish - 1))
|
|
186
|
-
let indices2 = new Set(getRange(styling2.start - 1, styling2.finish - 1))
|
|
187
|
-
|
|
188
|
-
let result = intersection(indices1, indices2)
|
|
189
|
-
|
|
190
|
-
return result.size == 0 ? 2 : 1
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Correlation handler
|
|
194
|
-
const stylingSplice = (correlations, stylings, current, type) => {
|
|
195
|
-
// Only one surround correlation is possible at one time, so use .find to fetch it
|
|
196
|
-
|
|
197
|
-
let [_, index, styling] = correlations.find(([c, _, __]) => c == 0)
|
|
198
|
-
|
|
199
|
-
// Remove correlated styling
|
|
200
|
-
stylings = stylings.filter((_, _index) => _index != index)
|
|
201
|
-
|
|
202
|
-
// Get splices
|
|
203
|
-
let added = [
|
|
204
|
-
{
|
|
205
|
-
type: type,
|
|
206
|
-
start: getMinimum([styling.start, current.start]),
|
|
207
|
-
finish: getMaximum([styling.start, current.start]),
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
type: type,
|
|
211
|
-
start: getMinimum([styling.finish, current.finish]),
|
|
212
|
-
finish: getMaximum([styling.finish, current.finish]),
|
|
213
|
-
},
|
|
214
|
-
]
|
|
215
|
-
|
|
216
|
-
// Remove empty splices (edge cases)
|
|
217
|
-
added = added.filter((item) => item.start != item.finish)
|
|
218
|
-
|
|
219
|
-
// Add to current stylings
|
|
220
|
-
stylings = [...stylings, ...added]
|
|
221
|
-
|
|
222
|
-
return stylings
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Correlation handler
|
|
226
|
-
const stylingIntersect = (correlations, stylings, current, type) => {
|
|
227
|
-
// Filter out all intersected stylings
|
|
228
|
-
let intersections = correlations
|
|
229
|
-
.filter(([c, _, __]) => c == 1)
|
|
230
|
-
.map(([_, index, styling]) => {
|
|
231
|
-
return [index, styling]
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
// Add current styling with no index for the sake for endpoint indices
|
|
235
|
-
intersections.push([-1, current])
|
|
236
|
-
|
|
237
|
-
// Get minimum intersection start index
|
|
238
|
-
let start = intersections
|
|
239
|
-
.map(([_, styling]) => styling.start)
|
|
240
|
-
.sort((a, b) => a - b)[0]
|
|
241
|
-
|
|
242
|
-
// Get maximum intersection start index
|
|
243
|
-
let finish = intersections
|
|
244
|
-
.map(([_, styling]) => styling.finish)
|
|
245
|
-
.sort((a, b) => b - a)[0]
|
|
246
|
-
|
|
247
|
-
// Get indices of all intersection
|
|
248
|
-
let indices = intersections.map((e) => e[0])
|
|
249
|
-
|
|
250
|
-
// Remove all from resulting styling array
|
|
251
|
-
stylings = stylings.filter((_, index) => !indices.includes(index))
|
|
252
|
-
|
|
253
|
-
// Add widest styling which encompasses all intersections
|
|
254
|
-
stylings.push({
|
|
255
|
-
type: type,
|
|
256
|
-
start: start,
|
|
257
|
-
finish: finish,
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
return stylings
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const perform = (id) => {
|
|
264
|
-
let stylings = _text.current.stylings.slice()
|
|
265
|
-
let [selectionStart, selectionEnd] = getFieldSelection()
|
|
266
|
-
|
|
267
|
-
if (selectionStart == selectionEnd) return
|
|
268
|
-
|
|
269
|
-
let current = {
|
|
270
|
-
type: id,
|
|
271
|
-
start: selectionStart,
|
|
272
|
-
finish: selectionEnd,
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
let correlations = []
|
|
276
|
-
|
|
277
|
-
// Check the correlation between this requested styling and all other stylings
|
|
278
|
-
for (let i = 0; i < stylings.length; i++) {
|
|
279
|
-
let styling = stylings[i]
|
|
280
|
-
let correlation = getCorrelation(current, styling)
|
|
281
|
-
|
|
282
|
-
if (correlation != -1) correlations.push([correlation, i, styling])
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
let result
|
|
286
|
-
|
|
287
|
-
if (correlations.find(([c, _, __]) => c == 1)) {
|
|
288
|
-
result = stylingIntersect(correlations, stylings, current, id)
|
|
289
|
-
} else if (correlations.find(([c, _, __]) => c == 0)) {
|
|
290
|
-
result = stylingSplice(correlations, stylings, current, id)
|
|
291
|
-
} else if (
|
|
292
|
-
correlations.find(([c, _, __]) => c == 2) ||
|
|
293
|
-
correlations.length == 0
|
|
294
|
-
) {
|
|
295
|
-
result = [...stylings, current]
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
setText({
|
|
299
|
-
...text,
|
|
300
|
-
stylings: result,
|
|
301
|
-
revert: [selectionStart, selectionEnd],
|
|
302
|
-
})
|
|
303
|
-
}
|
|
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
|
-
|
|
335
|
-
// Get stylings encompassing an index within it's range
|
|
336
|
-
const getIntersectStylings = (index, startOffset = 0, finishOffset = 0) => {
|
|
337
|
-
// Find all stylings with encompassing range
|
|
338
|
-
let matches = text.stylings.filter(
|
|
339
|
-
({ start, finish }) =>
|
|
340
|
-
index >= start + startOffset && index < finish + finishOffset
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
return matches
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Get stylings after an index
|
|
347
|
-
const getSucceedStylings = (index) => {
|
|
348
|
-
// Find all stylings after the index
|
|
349
|
-
let matches = text.stylings.filter(({ start, finish }) => start >= index)
|
|
350
|
-
|
|
351
|
-
return matches
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const getStylingIndex = (styling) => {
|
|
355
|
-
return text.stylings.findIndex(
|
|
356
|
-
(item) =>
|
|
357
|
-
item.start == styling.start &&
|
|
358
|
-
item.finish == styling.finish &&
|
|
359
|
-
item.type == styling.type
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const handleScenario = (stylings, predicate) => {
|
|
364
|
-
let matches = stylings.filter(({ start: _start, finish: _finish }) =>
|
|
365
|
-
predicate(_start, _finish)
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
let indices = []
|
|
369
|
-
|
|
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()
|
|
385
|
-
let changes = []
|
|
386
|
-
|
|
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
|
-
})
|
|
419
|
-
|
|
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
|
-
})
|
|
435
|
-
|
|
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
|
-
})
|
|
451
|
-
|
|
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
|
-
})
|
|
467
|
-
|
|
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
|
-
})
|
|
484
|
-
|
|
485
|
-
// Apply changes
|
|
486
|
-
changes.map(({ index, start, finish }) => {
|
|
487
|
-
stylings[index] = {
|
|
488
|
-
...stylings[index],
|
|
489
|
-
start: start,
|
|
490
|
-
finish: finish,
|
|
491
|
-
}
|
|
492
|
-
})
|
|
493
|
-
|
|
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
|
-
// })
|
|
502
|
-
|
|
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
|
|
506
|
-
|
|
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)
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
const getContent = () => {
|
|
641
|
-
let content = _text.current.content
|
|
642
|
-
|
|
643
|
-
// Get all styling indices
|
|
644
|
-
let indices = _text.current.stylings
|
|
645
|
-
.map(({ start, finish }) => [start, finish])
|
|
646
|
-
.flat()
|
|
647
|
-
|
|
648
|
-
// Sort ascendingly
|
|
649
|
-
indices = indices.sort((a, b) => a - b)
|
|
650
|
-
|
|
651
|
-
// Remove duplicates
|
|
652
|
-
indices = indices.filter(
|
|
653
|
-
(element, index) => indices.indexOf(element) == index
|
|
654
|
-
)
|
|
655
|
-
|
|
656
|
-
// Add first index if not present
|
|
657
|
-
if (indices[0] != 0) indices.unshift(0)
|
|
658
|
-
|
|
659
|
-
// Add last index if not present
|
|
660
|
-
let last = content.length
|
|
661
|
-
if (indices[indices.length - 1] != last) indices.push(last)
|
|
662
|
-
|
|
663
|
-
let result = []
|
|
664
|
-
|
|
665
|
-
for (let i = 0; i < indices.length - 1; i++) {
|
|
666
|
-
result.push([indices[i], content.substring(indices[i], indices[i + 1])])
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
return result
|
|
670
|
-
}
|
|
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
|
-
|
|
722
|
-
return (
|
|
723
|
-
<div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-blue-300 shadow-md">
|
|
724
|
-
<div className={clsx("flex w-full flex-row justify-start p-2")}>
|
|
725
|
-
<Property
|
|
726
|
-
name="B"
|
|
727
|
-
onMouseDown={(event) => {
|
|
728
|
-
event.preventDefault() // This does not take focus away from field which allows the function to retrieve the current selection data
|
|
729
|
-
perform("bold")
|
|
730
|
-
}}
|
|
731
|
-
/>
|
|
732
|
-
<Property
|
|
733
|
-
name="I"
|
|
734
|
-
onMouseDown={(event) => {
|
|
735
|
-
event.preventDefault()
|
|
736
|
-
perform("italic")
|
|
737
|
-
}}
|
|
738
|
-
/>
|
|
739
|
-
<Property
|
|
740
|
-
name="U"
|
|
741
|
-
onMouseDown={(event) => {
|
|
742
|
-
event.preventDefault()
|
|
743
|
-
perform("under")
|
|
744
|
-
}}
|
|
745
|
-
/>
|
|
746
|
-
<Property
|
|
747
|
-
name="S"
|
|
748
|
-
onMouseDown={(event) => {
|
|
749
|
-
event.preventDefault()
|
|
750
|
-
perform("strike")
|
|
751
|
-
}}
|
|
752
|
-
/>
|
|
753
|
-
</div>
|
|
754
|
-
<div className="h-[1px] w-full bg-slate-600"> </div>
|
|
755
|
-
<div className="w-full">
|
|
756
|
-
<div
|
|
757
|
-
ref={field}
|
|
758
|
-
contentEditable="true"
|
|
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
|
-
})
|
|
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) => {
|
|
831
|
-
let [start, end] = getFieldSelection()
|
|
832
|
-
|
|
833
|
-
let data = serializeStyleInRange(start, end)
|
|
834
|
-
|
|
835
|
-
setText({
|
|
836
|
-
...text,
|
|
837
|
-
lastCopy: data,
|
|
838
|
-
})
|
|
839
|
-
}}
|
|
840
|
-
// onKeyDown={(event: any) => {
|
|
841
|
-
// event.preventDefault()
|
|
842
|
-
// console.log(event)
|
|
843
|
-
// }}
|
|
844
|
-
></div>
|
|
845
|
-
</div>
|
|
846
|
-
<div className="h-[1px] w-full bg-slate-600"> </div>
|
|
847
|
-
<button className="my-1 rounded bg-cyan-800 p-2 py-1 text-white">
|
|
848
|
-
Submit
|
|
849
|
-
</button>
|
|
850
|
-
</div>
|
|
851
|
-
)
|
|
852
|
-
}
|