-
Notifications
You must be signed in to change notification settings - Fork 3.2k
/
search-highlighting.tsx
134 lines (126 loc) · 3.68 KB
/
search-highlighting.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import React, { useState, useCallback, useMemo } from 'react'
import { Slate, Editable, withReact } from 'slate-react'
import { Text, Descendant, createEditor } from 'slate'
import { css } from '@emotion/css'
import { withHistory } from 'slate-history'
import { Icon, Toolbar } from '../components'
const SearchHighlightingExample = () => {
const [search, setSearch] = useState<string | undefined>()
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
const decorate = useCallback(
([node, path]) => {
const ranges = []
if (
search &&
Array.isArray(node.children) &&
node.children.every(Text.isText)
) {
const texts = node.children.map(it => it.text)
const str = texts.join('')
const length = search.length
let start = str.indexOf(search)
let index = 0
let iterated = 0
while (start !== -1) {
// Skip already iterated strings
while (
index < texts.length &&
start >= iterated + texts[index].length
) {
iterated = iterated + texts[index].length
index++
}
// Find the index of array and relative position
let offset = start - iterated
let remaining = length
while (index < texts.length && remaining > 0) {
const currentText = texts[index]
const currentPath = [...path, index]
const taken = Math.min(remaining, currentText.length - offset)
ranges.push({
anchor: { path: currentPath, offset },
focus: { path: currentPath, offset: offset + taken },
highlight: true,
})
remaining = remaining - taken
if (remaining > 0) {
iterated = iterated + currentText.length
// Next block will be indexed from 0
offset = 0
index++
}
}
// Looking for next search block
start = str.indexOf(search, start + search.length)
}
}
return ranges
},
[search]
)
return (
<Slate editor={editor} initialValue={initialValue}>
<Toolbar>
<div
className={css`
position: relative;
`}
>
<Icon
className={css`
position: absolute;
top: 0.3em;
left: 0.4em;
color: #ccc;
`}
>
search
</Icon>
<input
type="search"
placeholder="Search the text..."
onChange={e => setSearch(e.target.value)}
className={css`
padding-left: 2.5em;
width: 100%;
`}
/>
</div>
</Toolbar>
<Editable decorate={decorate} renderLeaf={props => <Leaf {...props} />} />
</Slate>
)
}
const Leaf = ({ attributes, children, leaf }) => {
return (
<span
{...attributes}
{...(leaf.highlight && { 'data-cy': 'search-highlighted' })}
className={css`
font-weight: ${leaf.bold && 'bold'};
background-color: ${leaf.highlight && '#ffeeba'};
`}
>
{children}
</span>
)
}
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{
text: 'This is editable text that you can search. As you search, it looks for matching strings of text, and adds ',
},
{ text: 'decorations', bold: true },
{ text: ' to them in realtime.' },
],
},
{
type: 'paragraph',
children: [
{ text: 'Try it out for yourself by typing in the search box above!' },
],
},
]
export default SearchHighlightingExample