Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/search/hook.tsx
1698 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Support for global full text search of our slate.js document.
8
*/
9
10
import React from "react";
11
import { delay } from "awaiting";
12
import { Input } from "antd";
13
const { useCallback, useMemo, useRef, useState } = React;
14
import { Editor, Point, Range, Transforms } from "slate";
15
import {
16
nextMatch,
17
previousMatch,
18
SearchControlButtons,
19
} from "./search-control";
20
import { ReactEditor } from "../slate-react";
21
import { IS_MACOS, IS_TOUCH } from "@cocalc/frontend/feature";
22
import { createSearchDecorate } from "./decorate";
23
import { Replace } from "./replace";
24
25
const modKey = IS_MACOS ? "⌘" : "ctrl";
26
const keyboardMessage = `Find Next (${modKey}-G) and Prev (Shift-${modKey}-G).`;
27
28
const EXTRA_INFO_STYLE = {
29
position: "absolute",
30
opacity: 0.95,
31
marginTop: "2px",
32
zIndex: 1,
33
background: "white",
34
width: "100%",
35
color: "rgb(102,102,102)",
36
borderLeft: "1px solid lightgrey",
37
borderBottom: "1px solid lightgrey",
38
boxShadow: "-3px 5px 2px lightgrey",
39
} as React.CSSProperties;
40
41
interface Options {
42
editor: Editor;
43
}
44
45
export interface SearchHook {
46
decorate: ([node, path]) => { anchor: Point; focus: Point; search: true }[];
47
Search: React.JSX.Element;
48
search: string;
49
previous: () => void;
50
next: () => void;
51
focus: (search?: string) => void;
52
}
53
54
export const useSearch: (Options) => SearchHook = (options) => {
55
const { editor } = options;
56
const [search, setSearch] = useState<string>("");
57
const inputRef = useRef<any>(null);
58
59
const decorate = useMemo(() => {
60
return createSearchDecorate(search);
61
}, [search]);
62
63
const cancel = useCallback(async () => {
64
setSearch("");
65
inputRef.current?.blur();
66
await delay(100); // todo: there must be a better way.
67
ReactEditor.focus(editor);
68
return;
69
}, []);
70
71
const Search = useMemo(
72
() => (
73
<div
74
style={{
75
border: 0,
76
width: "100%",
77
position: "relative",
78
}}
79
>
80
<div style={{ display: "flex" }}>
81
<Input
82
ref={inputRef}
83
allowClear={true}
84
size="small"
85
placeholder="Search..."
86
value={search}
87
onChange={(e) => setSearch(e.target.value)}
88
style={{
89
border: 0,
90
flex: 1,
91
}}
92
onKeyDown={async (event) => {
93
if (event.metaKey || event.ctrlKey) {
94
if (event.key == "f") {
95
event.preventDefault();
96
return;
97
}
98
if (event.key == "g") {
99
event.preventDefault();
100
if (event.shiftKey) {
101
previousMatch(editor, decorate);
102
} else {
103
nextMatch(editor, decorate);
104
}
105
return;
106
}
107
}
108
if (event.key == "Enter") {
109
event.preventDefault();
110
inputRef.current?.blur();
111
await delay(100);
112
const { selection } = editor;
113
if (selection != null) {
114
const focus = Range.edges(selection)[0];
115
Transforms.setSelection(editor, { focus, anchor: focus });
116
}
117
nextMatch(editor, decorate);
118
return;
119
}
120
if (event.key == "Escape") {
121
event.preventDefault();
122
cancel();
123
return;
124
}
125
}}
126
/>
127
{search.trim() && (
128
<SearchControlButtons
129
editor={editor}
130
decorate={decorate}
131
disabled={!search.trim()}
132
/>
133
)}
134
</div>
135
{search.trim() && (
136
<div style={EXTRA_INFO_STYLE}>
137
<Replace
138
editor={editor}
139
decorate={decorate}
140
cancel={cancel}
141
search={search}
142
/>
143
{!IS_TOUCH && (
144
<div style={{ marginLeft: "7px" }}>{keyboardMessage}</div>
145
)}
146
</div>
147
)}
148
</div>
149
),
150
[search, decorate]
151
);
152
153
return {
154
decorate,
155
Search,
156
search,
157
inputRef,
158
focus: async (search) => {
159
if (search?.trim()) {
160
setSearch(search);
161
await delay(0); // so that the "all" below selects this search.
162
}
163
inputRef.current?.focus({ cursor: "all" });
164
},
165
next: () => {
166
nextMatch(editor, decorate);
167
},
168
previous: () => {
169
previousMatch(editor, decorate);
170
},
171
};
172
};
173
174