-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathuseHighlightNavigation.ts
More file actions
105 lines (94 loc) · 3.09 KB
/
Copy pathuseHighlightNavigation.ts
File metadata and controls
105 lines (94 loc) · 3.09 KB
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
import { useCallback, useMemo, useState } from "react";
import { HighlightForSearchNavigation, HighlightsState } from "./model";
import { removeLastUnmatchedQuote } from "../../util/stringUtils";
import authFetch from "../../util/auth/authFetch";
/**
* - "find": on-demand find-in-document queries typed by the user.
* - "search": the workspace-level search query, fixed for the lifetime of the
* page viewer. Parses chip syntax and returns highlights with a distinct prefix
* so both sets can coexist without colliding.
*/
type HighlightEndpoint = "search" | "find";
export type HighlightNavigationState = {
query: string;
highlightsState: HighlightsState;
focusedHighlight: HighlightForSearchNavigation | null;
isPending: boolean;
fetchHighlights: (query: string) => Promise<void> | undefined;
jumpToNext: () => void;
jumpToPrevious: () => void;
};
export function useHighlightNavigation(
uri: string,
endpoint: HighlightEndpoint,
): HighlightNavigationState {
const [query, setQuery] = useState("");
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
const [highlights, setHighlights] = useState<HighlightForSearchNavigation[]>(
[],
);
const [isPending, setIsPending] = useState(false);
const fetchHighlights = useCallback(
(q: string) => {
if (!q) {
setFocusedIndex(null);
setHighlights([]);
setQuery("");
return;
}
const params = new URLSearchParams();
// The backend will respect quotes and do an exact search,
// but if quotes are unbalanced elasticsearch will error
params.set("q", removeLastUnmatchedQuote(q));
setQuery(q);
setIsPending(true);
return authFetch(`/api/pages2/${uri}/${endpoint}?${params.toString()}`)
.then((res) => res.json())
.then((results: HighlightForSearchNavigation[]) => {
setIsPending(false);
setHighlights(results);
setFocusedIndex(results.length > 0 ? 0 : null);
})
.catch(() => {
// e.g. the document has no page index; treat as no highlights rather
// than letting the rejection surface as an uncaught runtime error.
setIsPending(false);
setHighlights([]);
setFocusedIndex(null);
});
},
[uri, endpoint],
);
const jumpToNext = useCallback(() => {
if (highlights.length > 0) {
setFocusedIndex((prev) =>
prev !== null && prev < highlights.length - 1 ? prev + 1 : 0,
);
}
}, [highlights.length]);
const jumpToPrevious = useCallback(() => {
if (highlights.length > 0) {
setFocusedIndex((prev) =>
prev !== null && prev > 0 ? prev - 1 : highlights.length - 1,
);
}
}, [highlights.length]);
const highlightsState = useMemo<HighlightsState>(
() => ({
focusedIndex,
highlights,
}),
[focusedIndex, highlights],
);
const focusedHighlight =
focusedIndex !== null ? (highlights[focusedIndex] ?? null) : null;
return {
query,
highlightsState,
focusedHighlight,
isPending,
fetchHighlights,
jumpToNext,
jumpToPrevious,
};
}