on dffd855 help sign in
273 lines 10 kB history raw
1import { Component, createRef } from "react";
2import { glossary } from "../lib/glossary";
3import { withRouter } from "next/router";
4import debounce from "lodash.debounce";
5import Downshift from "downshift";
6import ob from "urbit-ob";
7import Sigil from "../components/Sigil";
9class Search extends Component {
10 constructor(props) {
11 super(props);
12 this.state = {
13 results: [],
14 };
15 this.searchEndpoint = this.searchEndpoint.bind(this);
16 this.onInputValueChange = this.onInputValueChange.bind(this);
17 this.onSelect = this.onSelect.bind(this);
18 this.glossarySearch = this.glossarySearch.bind(this);
19 this.patpSearch = this.patpSearch.bind(this);
20 }
22 searchEndpoint(query) {
23 return `/api/search?q=${query}`;
24 }
26 glossarySearch(query) {
27 return glossary.filter((entry) => {
28 return (
29 entry.name.includes(query.toLowerCase()) || entry.symbol.includes(query)
30 );
31 });
32 }
34 patpSearch(query) {
35 return (
36 (ob.isValidPatp(`~${deSig(query.toLowerCase())}`) &&
37 `~${deSig(query.toLowerCase())}`.length < 15) ||
38 (!isNaN(query) && query <= 4294967295)
39 );
40 }
42 onSelect(item) {
43 if (item.slug) {
44 this.props.router.push(item.slug);
45 }
47 this.setState({
48 query: "",
49 results: [],
50 });
52 this.props.closeSearch();
53 }
55 onInputValueChange = debounce((query) => {
56 if (query.length) {
57 fetch(this.searchEndpoint(query))
58 .then((res) => res.json())
59 .then((res) => {
60 // Wrap results in an object which will tell React what component to use to render results.
61 const results = res.results.map((item) => ({
62 type: "RESULT",
63 content: item,
64 }));
66 const patp = this.patpSearch(query)
67 ? !isNaN(query)
68 ? ob.patp(query)
69 : ob.patp(ob.patp2dec(`~${deSig(query)}`))
70 : null;
72 const patpResult = this.patpSearch(query)
73 ? [
74 {
75 type: "PATP",
76 content: {
77 patp: patp,
78 slug: `/ids/${patp}`,
79 },
80 },
81 ]
82 : [];
84 const glossaryResults = this.glossarySearch(query).map((item) => ({
85 type: "GLOSSARY_RESULT",
86 content: item,
87 }));
89 const list = [...glossaryResults, ...patpResult, ...results];
91 this.setState({ results: list });
92 });
93 } else {
94 this.setState({ results: [] });
95 }
96 }, 250);
98 render() {
99 const { state, props } = this;
101 if (props.showSearch) {
102 return (
103 <Downshift
104 onSelect={(selection) => this.onSelect(selection)}
105 onInputValueChange={(event) => this.onInputValueChange(event)}
106 itemToString={(item) => (item ? item.slug : "")}
107 defaultHighlightedIndex={0}
108 >
109 {({
110 getInputProps,
111 getItemProps,
112 getLabelProps,
113 getMenuProps,
114 isOpen,
115 inputValue,
116 highlightedIndex,
117 selectedItem,
118 getRootProps,
119 }) => (
120 <div className="fixed w-screen h-screen z-50 flex flex-col items-center p-4">
121 <div
122 onClick={(event) => props.closeSearch(event)}
123 className="top-0 left-0 fixed w-screen h-screen bg-washedWall"
124 />
125 <div className="relative flex flex-col max-w-screen-lg md:my-32 w-full md:w-10/12 lg:w-8/12 xl:w-6/12 rounded-xl bg-white min-h-0 overflow-hidden">
126 <div
127 style={{ display: "inline-block" }}
128 {...getRootProps({}, { suppressRefError: true })}
129 >
130 <input
131 autoFocus
132 className="text-lg md:text-xl lg:text-2xl font-medium text-green-400 bg-transparent py-2 px-4 outline-none relative w-full"
133 placeholder="Search..."
134 type="text"
135 onClick={(e) => e.stopPropagation()}
136 {...getInputProps({
137 onKeyDown: (event) => {
138 if (event.key === "Escape") {
139 // Prevent Downshift's default 'Escape' behavior.
140 event.nativeEvent.preventDownshiftDefault = true;
141 this.props.closeSearch(event);
142 }
143 },
144 })}
145 />
146 </div>
147 <ul {...getMenuProps()} className="overflow-y-scroll">
148 {isOpen
149 ? state.results.map((item, index) => {
150 const selected = highlightedIndex === index;
151 if (item.type === "PATP") {
152 return (
153 <li
154 className={`cursor-pointer p-2 flex space-x-2 items-center text-left w-full ${
155 selected ? "bg-green-400" : ""
156 }`}
157 {...getItemProps({
158 key: item.content.slug + "-" + index,
159 index,
160 item: item.content,
161 selected: highlightedIndex === index,
162 })}
163 >
164 <div className="rounded-md overflow-hidden">
165 <Sigil
166 patp={item.content.patp}
167 size={25}
168 icon
169 />
170 </div>
171 <p className="font-mono">{item.content.patp}</p>
172 </li>
173 );
174 }
175 if (item.type === "GLOSSARY_RESULT") {
176 return (
177 <li
178 className={`cursor-pointer flex text-left w-full ${
179 selected ? "bg-green-400" : ""
180 }`}
181 {...getItemProps({
182 key: item.content.slug + "-" + index,
183 index,
184 item: item.content,
185 selected: highlightedIndex === index,
186 })}
187 >
188 <div className="font-semibold p-3">
189 <p
190 className={`text-base ${
191 selected ? "text-white" : "text-wall-600"
192 }`}
193 >
194 {item.content.symbol.length > 0 && (
195 <code
196 className={`mr-1 rounded px-1 py-0.5 ${
197 selected
198 ? "bg-washedWhite"
199 : "bg-wall-100"
200 }`}
201 >
202 {item.content.symbol}
203 </code>
204 )}
205 {item.content.name}
206 </p>
207 <p
208 className={`font-normal text-base mt-1 ${
209 selected ? "text-white" : "text-wall-600"
210 }`}
211 dangerouslySetInnerHTML={{
212 __html: item.content.desc,
213 }}
214 ></p>
215 </div>
216 </li>
217 );
218 }
219 if (item.type === "RESULT") {
220 return (
221 <li
222 className={`cursor-pointer flex text-left w-full ${
223 selected ? "bg-green-400" : ""
224 }`}
225 {...getItemProps({
226 key: item.content.link + "-" + index,
227 index,
228 item: item.content,
229 selected,
230 })}
231 >
232 <div className="p-3">
233 <p
234 className={`font-medium text-base ${
235 selected ? "text-white" : "text-wall-600"
236 }`}
237 >
238 {item.content.parent !== "Content"
239 ? `${item.content.parent} /`
240 : ""}{" "}
241 {item.content.title}
242 </p>
243 <p
244 className={`text-base font-regular text-small ${
245 selected ? "text-midWhite" : "text-wall-500"
246 }`}
247 >
248 {item.content.content}
249 </p>
250 </div>
251 </li>
252 );
253 }
254 return null;
255 })
256 : null}
257 </ul>
258 </div>
259 </div>
260 )}
261 </Downshift>
262 );
263 } else {
264 return null;
265 }
266 }
269function deSig(string) {
270 return string.startsWith("~") ? string.substring(1) : string;
273export default withRouter(Search);