on master help sign in
Merge pull request #1850 from urbit/mp/redo-glossary
d7fa942matildepark
components/Search.js+216−228
@@ -14,26 +14,21 @@ class Search extends Component {
14 this.searchEndpoint = this.searchEndpoint.bind(this);
14 this.searchEndpoint = this.searchEndpoint.bind(this);
15 this.onInputValueChange = this.onInputValueChange.bind(this);
15 this.onInputValueChange = this.onInputValueChange.bind(this);
16 this.onSelect = this.onSelect.bind(this);
16 this.onSelect = this.onSelect.bind(this);
17− this.glossarySearch = this.glossarySearch.bind(this);
18 this.patpSearch = this.patpSearch.bind(this);
17 this.patpSearch = this.patpSearch.bind(this);
19 this.devSearch = this.devSearch.bind(this);
18 this.devSearch = this.devSearch.bind(this);
20 this.opsSearch = this.opsSearch.bind(this);
19 this.opsSearch = this.opsSearch.bind(this);
21 }
20 }
22
21
23 searchEndpoint(query) {
22 searchEndpoint(query) {
24− return `/api/search?q=${query}`;
23+ return `/api/search?q=${encodeURIComponent(query)}`;
25 }
24 }
26
25
27 devSearch(query) {
26 devSearch(query) {
28− return `/api/dev-search?q=${query}`;
27+ return `/api/dev-search?q=${encodeURIComponent(query)}`;
29− }
30−
31− glossarySearch(query) {
32− return `/api/glossary?q=${encodeURIComponent(query)}`;
33 }
28 }
34
29
35 opsSearch(query) {
30 opsSearch(query) {
36− return `/api/ops-search?q=${query}`;
31+ return `/api/ops-search?q=${encodeURIComponent(query)}`;
37 }
32 }
38
33
39 patpSearch(query) {
34 patpSearch(query) {
@@ -45,16 +40,19 @@ class Search extends Component {
45 }
40 }
46
41
47 onSelect(item) {
42 onSelect(item) {
48− if (item.slug) {
49− this.props.router.push(item.slug);
50− }
51−
52 this.setState({
43 this.setState({
53 query: "",
44 query: "",
54 results: [],
45 results: [],
55 });
46 });
56
47
48+ if (item.url) {
49+ this.props.router.push(`${item.url}${item?.slug ? item.slug : ""}`);
57− this.props.closeSearch();
50+ return this.props.closeSearch();
51+ }
52+ if (item.slug) {
53+ this.props.router.push(item.slug);
54+ return this.props.closeSearch();
55+ }
58 }
56 }
59
57
60 onInputValueChange = debounce(async (query) => {
58 onInputValueChange = debounce(async (query) => {
@@ -62,10 +60,13 @@ class Search extends Component {
62 const search = fetch(this.searchEndpoint(query))
60 const search = fetch(this.searchEndpoint(query))
63 .then((res) => res.json())
61 .then((res) => res.json())
64 .then(async (res) => {
62 .then(async (res) => {
63+ return [...res.glossary.map((item) => ({
64+ type: "GLOSSARY_RESULT",
65+ content: item
65− return res.results.map((item) => ({
66+ })), ...res.results.map((item) => ({
66 type: "RESULT",
67 type: "RESULT",
67 content: item,
68 content: item,
68− }));
69+ }))];
69 });
70 });
70
71
71 const patp = this.patpSearch(query)
72 const patp = this.patpSearch(query)
@@ -76,53 +77,54 @@ class Search extends Component {
76
77
77 const patpResult = this.patpSearch(query)
78 const patpResult = this.patpSearch(query)
78 ? [
79 ? [
79− {
80+ {
80− type: "PATP",
81+ type: "PATP",
81− content: {
82+ content: {
82− patp: patp,
83+ patp: patp,
83− slug: `/ids/${patp}`,
84+ slug: `/ids/${patp}`,
84− },
85 },
85 },
86+ },
86− ]
87+ ]
87 : [];
88 : [];
88
89
89− const glossarySearch = fetch(this.glossarySearch(query))
90− .then((res) => res.json())
91− .then((res) => {
92− return res.results.map((item) => ({
93− type: "GLOSSARY_RESULT",
94− content: item,
95− }));
96− });
97−
98 const devSearch = fetch(this.devSearch(query))
90 const devSearch = fetch(this.devSearch(query))
99 .then((res) => res.json())
91 .then((res) => res.json())
100 .then((res) => {
92 .then((res) => {
93+ return [...res.glossary.map((item) => ({
94+ type: "GLOSSARY_RESULT",
95+ content: item
101− return res.results.map((item) => ({
96+ })), ...res.results.map((item) => ({
102 type: "DEV_RESULT",
97 type: "DEV_RESULT",
103 content: item,
98 content: item,
104− }));
99+ }))];
105 });
100 });
106
101
107 const opsSearch = fetch(this.opsSearch(query))
102 const opsSearch = fetch(this.opsSearch(query))
108 .then((res) => res.json())
103 .then((res) => res.json())
109 .then((res) => {
104 .then((res) => {
105+ return [...res.glossary.map((item) => ({
106+ type: "GLOSSARY_RESULT",
107+ content: item
110− return res.results.map((item) => ({
108+ })), ...res.results.map((item) => ({
111 type: "OPS_RESULT",
109 type: "OPS_RESULT",
112 content: item,
110 content: item,
113− }));
111+ }))];
114 });
112 });
115
113
116− const [results, glossaryResults, devResults, opsResults] =
114+ const [results, devResults, opsResults] =
117− await Promise.all([search, glossarySearch, devSearch, opsSearch]);
115+ await Promise.all([search, devSearch, opsSearch]);
118
116
119 const list = [
117 const list = [
120− ...glossaryResults,
121 ...patpResult,
118 ...patpResult,
122 ...results,
119 ...results,
123 ...devResults,
120 ...devResults,
124 ...opsResults,
121 ...opsResults,
125− ];
122+ ]
123+ .sort((a, b) => {
124+ const aNum = a?.type === "GLOSSARY_RESULT" ? 1 : -1;
125+ const bNum = b?.type === "GLOSSARY_RESULT" ? 1 : -1;
126+ return bNum - aNum;
127+ });;
126
128
127 this.setState({ results: list });
129 this.setState({ results: list });
128 } else {
130 } else {
@@ -182,209 +184,195 @@ class Search extends Component {
182 <ul {...getMenuProps()} className="overflow-y-scroll">
184 <ul {...getMenuProps()} className="overflow-y-scroll">
183 {isOpen
185 {isOpen
184 ? state.results.map((item, index) => {
186 ? state.results.map((item, index) => {
185− const selected = highlightedIndex === index;
187+ const selected = highlightedIndex === index;
186− if (item.type === "PATP") {
188+ if (item.type === "PATP") {
187− return (
189+ return (
188− <li
190+ <li
189− className={`cursor-pointer p-2 flex space-x-2 items-center text-left w-full ${
191+ className={`cursor-pointer p-2 flex space-x-2 items-center text-left w-full ${selected ? "bg-green-400" : ""
190− selected ? "bg-green-400" : ""
191 }`}
192 }`}
192− {...getItemProps({
193+ {...getItemProps({
193− key: item.content.slug + "-" + index,
194+ key: item.content.slug + "-" + index,
194− index,
195+ index,
195− item: item.content,
196+ item: item.content,
196− selected: highlightedIndex === index,
197+ selected: highlightedIndex === index,
197− })}
198+ })}
198− >
199+ >
199− <div className="rounded-md overflow-hidden">
200+ <div className="rounded-md overflow-hidden">
200− <Sigil
201+ <Sigil
201− patp={item.content.patp}
202+ patp={item.content.patp}
202− size={25}
203+ size={25}
203− icon
204+ icon
204− />
205+ />
205− </div>
206+ </div>
206− <p className="font-mono">{item.content.patp}</p>
207+ <p className="font-mono">{item.content.patp}</p>
207− </li>
208+ </li>
208− );
209+ );
209− }
210+ }
210− if (item.type === "GLOSSARY_RESULT") {
211+ if (item.type === "GLOSSARY_RESULT") {
211− return (
212+ return (
212− <li
213+ <li
213− className={`cursor-pointer flex text-left w-full ${
214+ className={`cursor-pointer flex text-left w-full ${selected ? "bg-green-400" : ""
214− selected ? "bg-green-400" : ""
215 }`}
215 }`}
216− {...getItemProps({
216+ {...getItemProps({
217− key: item.content.slug + "-" + index,
217+ key: item.content.slug + "-" + index,
218− index,
218+ index,
219− item: item.content,
219+ item: item.content,
220− selected: highlightedIndex === index,
220+ selected: highlightedIndex === index,
221− })}
221+ })}
222− >
222+ >
223− <div className="font-semibold p-3">
223+ <div className="font-semibold p-3">
224− <p
224+ <p
225− className={`text-base ${
226− selected ? "text-white" : "text-wall-600"
225+ className={`text-base ${selected ? "text-white" : "text-wall-600"
227 }`}
226 }`}
228− >
227+ >
229− {item.content.symbol.length > 0 && (
228+ {item.content.symbol.length > 0 && (
230− <code
229+ <code
231− className={`mr-1 rounded px-1 py-0.5 ${
230+ className={`mr-1 rounded px-1 py-0.5 ${selected
232− selected
233− ? "bg-washedWhite"
231+ ? "bg-washedWhite"
234− : "bg-wall-100"
232+ : "bg-wall-100"
235 }`}
233 }`}
236− >
234+ >
237− {item.content.symbol}
235+ {item.content.symbol}
238− </code>
236+ </code>
239− )}
237+ )}
240− {item.content.name}
238+ {item.content.name}
241− </p>
239+ </p>
242− <p
240+ <p
243− className={`font-normal text-base mt-1 ${
241+ className={`font-normal text-base mt-1 ${selected ? "text-white" : "text-wall-600"
244− selected ? "text-white" : "text-wall-600"
245 }`}
242 }`}
246− dangerouslySetInnerHTML={{
243+ dangerouslySetInnerHTML={{
247− __html: item.content.desc,
244+ __html: item.content.desc,
248− }}
245+ }}
249− ></p>
246+ ></p>
250− </div>
247+ </div>
251− </li>
248+ </li>
252− );
249+ );
253− }
250+ }
254− if (item.type === "RESULT") {
251+ if (item.type === "RESULT") {
255− return (
252+ return (
256− <li
253+ <li
257− className={`cursor-pointer flex text-left w-full ${
254+ className={`cursor-pointer flex text-left w-full ${selected ? "bg-green-400" : ""
258− selected ? "bg-green-400" : ""
259 }`}
255 }`}
260− {...getItemProps({
256+ {...getItemProps({
261− key: item.content.link + "-" + index,
257+ key: item.content.link + "-" + index,
262− index,
258+ index,
263− item: item.content,
259+ item: item.content,
264− selected,
260+ selected,
265− })}
261+ })}
266− >
262+ >
267− <div className="p-3">
263+ <div className="p-3">
268− <p
264+ <p
269− className={`font-medium text-base ${
270− selected ? "text-white" : "text-wall-600"
265+ className={`font-medium text-base ${selected ? "text-white" : "text-wall-600"
271 }`}
266 }`}
272− >
267+ >
273− {item.content.parent !== "Content"
268+ {item.content.parent !== "Content"
274− ? `${item.content.parent} /`
269+ ? `${item.content.parent} /`
275− : ""}{" "}
270+ : ""}{" "}
276− {item.content.title}
271+ {item.content.title}
277− </p>
272+ </p>
278− <p
273+ <p
279− className={`text-base font-regular text-small ${
274+ className={`text-base font-regular text-small ${selected ? "text-midWhite" : "text-wall-500"
280− selected ? "text-midWhite" : "text-wall-500"
281 }`}
275 }`}
282− >
276+ >
283− {item.content.content}
277+ {item.content.content}
284− {item?.content?.foundOnPage && (
278+ {item?.content?.foundOnPage && (
285− <span className="italic block">
279+ <span className="italic block">
286− Found in page content
280+ Found in page content
287− </span>
281+ </span>
288− )}
282+ )}
289− </p>
283+ </p>
290− </div>
284+ </div>
291− </li>
285+ </li>
292− );
286+ );
293− }
287+ }
294− if (item.type === "DEV_RESULT") {
288+ if (item.type === "DEV_RESULT") {
295− const devItem = Object.assign({}, item.content);
289+ const devItem = Object.assign({}, item.content);
296− devItem[
290+ devItem[
297− "slug"
291+ "slug"
298− ] = `https://developers.urbit.org${item.content.slug}`;
292+ ] = `https://developers.urbit.org${item.content.slug}`;
299− return (
293+ return (
300− <li
294+ <li
301− className={`cursor-pointer flex text-left w-full ${
295+ className={`cursor-pointer flex text-left w-full ${selected ? "bg-green-400" : ""
302− selected ? "bg-green-400" : ""
303 }`}
296 }`}
304− {...getItemProps({
297+ {...getItemProps({
305− key: item.content.link + "-" + index,
298+ key: item.content.link + "-" + index,
306− index,
299+ index,
307− item: devItem,
300+ item: devItem,
308− selected,
301+ selected,
309− })}
302+ })}
310− >
303+ >
311− <div className="p-3">
304+ <div className="p-3">
312− <p
305+ <p
313− className={`font-medium text-base ${
314− selected ? "text-white" : "text-wall-600"
306+ className={`font-medium text-base ${selected ? "text-white" : "text-wall-600"
315 }`}
307 }`}
316− >
308+ >
317− <span className="text-wall-400">
309+ <span className="text-wall-400">
318− {item.content.parent !== "Content"
310+ {item.content.parent !== "Content"
319− ? `developers.urbit.org / ${item.content.parent} /`
311+ ? `developers.urbit.org / ${item.content.parent} /`
320− : "developers.urbit.org /"}{" "}
312+ : "developers.urbit.org /"}{" "}
321− </span>
313+ </span>
322− {item.content.title}
314+ {item.content.title}
323− </p>
315+ </p>
324− <p
316+ <p
325− className={`text-base font-regular text-small ${
317+ className={`text-base font-regular text-small ${selected ? "text-midWhite" : "text-wall-500"
326− selected ? "text-midWhite" : "text-wall-500"
327 }`}
318 }`}
328− >
319+ >
329− {item.content.content}
320+ {item.content.content}
330− {item?.content?.foundOnPage && (
321+ {item?.content?.foundOnPage && (
331− <span className="italic block">
322+ <span className="italic block">
332− Found in page content
323+ Found in page content
333− </span>
324+ </span>
334− )}
325+ )}
335− </p>
326+ </p>
336− </div>
327+ </div>
337− </li>
328+ </li>
338− );
329+ );
339− }
330+ }
340− if (item.type === "OPS_RESULT") {
331+ if (item.type === "OPS_RESULT") {
341− const opsItem = Object.assign({}, item.content);
332+ const opsItem = Object.assign({}, item.content);
342− opsItem[
333+ opsItem[
343− "slug"
334+ "slug"
344− ] = `https://operators.urbit.org${item.content.slug}`;
335+ ] = `https://operators.urbit.org${item.content.slug}`;
345− return (
336+ return (
346− <li
337+ <li
347− className={`cursor-pointer flex text-left w-full ${
338+ className={`cursor-pointer flex text-left w-full ${selected ? "bg-green-400" : ""
348− selected ? "bg-green-400" : ""
349 }`}
339 }`}
350− {...getItemProps({
340+ {...getItemProps({
351− key: item.content.link + "-" + index,
341+ key: item.content.link + "-" + index,
352− index,
342+ index,
353− item: opsItem,
343+ item: opsItem,
354− selected,
344+ selected,
355− })}
345+ })}
356− >
346+ >
357− <div className="p-3">
347+ <div className="p-3">
358− <p
348+ <p
359− className={`font-medium text-base ${
360− selected ? "text-white" : "text-wall-600"
349+ className={`font-medium text-base ${selected ? "text-white" : "text-wall-600"
361 }`}
350 }`}
362− >
351+ >
363− <span className="text-wall-400">
352+ <span className="text-wall-400">
364− {item.content.parent !== "Content"
353+ {item.content.parent !== "Content"
365− ? `operators.urbit.org / ${item.content.parent} /`
354+ ? `operators.urbit.org / ${item.content.parent} /`
366− : "operators.urbit.org /"}{" "}
355+ : "operators.urbit.org /"}{" "}
367− </span>
356+ </span>
368− {item.content.title}
357+ {item.content.title}
369− </p>
358+ </p>
370− <p
359+ <p
371− className={`text-base font-regular text-small ${
360+ className={`text-base font-regular text-small ${selected ? "text-midWhite" : "text-wall-500"
372− selected ? "text-midWhite" : "text-wall-500"
373 }`}
361 }`}
374− >
362+ >
375− {item.content.content}
363+ {item.content.content}
376− {item?.content?.foundOnPage && (
364+ {item?.content?.foundOnPage && (
377− <span className="italic block">
365+ <span className="italic block">
378− Found in page content
366+ Found in page content
379− </span>
367+ </span>
380− )}
368+ )}
381− </p>
369+ </p>
382− </div>
370+ </div>
383− </li>
371+ </li>
384− );
372+ );
385− }
373+ }
386− return null;
374+ return null;
387− })
375+ })
388 : null}
376 : null}
389 </ul>
377 </ul>
390 </div>
378 </div>
lib/buildGlossary.jsnew file+59
@@ -0,0 +1,59 @@
1+const fs = require("fs");
2+const path = require("path");
3+const matter = require("gray-matter");
4+const toml = require("@iarna/toml");
5+
6+const options = {
7+ engines: {
8+ toml: toml.parse.bind(toml),
9+ },
10+ language: "toml",
11+ delimiters: "+++",
12+};
13+
14+const glossary = [];
15+
16+function buildGlossary(dir) {
17+ const children = fs.readdirSync(dir, { withFileTypes: true });
18+ const pages = children.filter((f) => f.isFile() && f.name !== "_index.md");
19+ const subdirs = children.filter(
20+ (f) => f.isDirectory()
21+ );
22+ pages.map((page) => {
23+ const { data } = matter(fs.readFileSync(path.join(dir, page.name)), options);
24+ const glossaryEntry = data.glossaryEntry
25+ ? Object.values(data.glossaryEntry)
26+ .map((e) => ({
27+ ...e, ...{
28+ url: "https://urbit.org" + path.join(
29+ dir.substr(dir.indexOf("content") + 7),
30+ "/",
31+ page.name.replace(/.md$/, "")
32+ )
33+ }
34+ }))
35+ : [];
36+ glossaryEntry.forEach((entry) => glossary.push(entry));
37+ });
38+
39+ Object.fromEntries(
40+ subdirs.map((subdir) => [
41+ subdir.name,
42+ buildGlossary(path.join(dir, subdir.name)),
43+ ])
44+ );
45+}
46+
47+buildGlossary(path.join(process.cwd(), "content"));
48+const fileContents = `export const glossary = ${JSON.stringify(glossary)}`;
49+
50+try {
51+ fs.readdirSync("cache");
52+} catch (err) {
53+ fs.mkdirSync("cache");
54+}
55+
56+fs.writeFile("cache/glossary.js", fileContents, function (err) {
57+ if (err) return console.error(err);
58+ console.log("Site glossary created.");
59+});
package.json+2−1
@@ -7,11 +7,12 @@
7 "build": "next build",
7 "build": "next build",
8 "export": "npm run prebuild && next build && next export",
8 "export": "npm run prebuild && next build && next export",
9 "start": "next start",
9 "start": "next start",
10− "prebuild": "npm run cache-posts && npm run rss && npm run community-tree && npm run getting-started-tree && npm run overview-tree",
10+ "prebuild": "npm run cache-posts && npm run rss && npm run glossary && npm run community-tree && npm run getting-started-tree && npm run overview-tree",
11 "community-tree": "node lib/buildPageTree community weight",
11 "community-tree": "node lib/buildPageTree community weight",
12 "getting-started-tree": "node lib/buildPageTree getting-started weight",
12 "getting-started-tree": "node lib/buildPageTree getting-started weight",
13 "overview-tree": "node lib/buildPageTree overview weight",
13 "overview-tree": "node lib/buildPageTree overview weight",
14 "cache-posts": "node lib/cache.js",
14 "cache-posts": "node lib/cache.js",
15+ "glossary": "node lib/buildGlossary.js",
15 "rss": "node lib/rss.js",
16 "rss": "node lib/rss.js",
16 "vercel-build": "yum install libuuid-devel libmount-devel && cp /lib64/{libuuid,libmount,libblkid}.so.1 node_modules/canvas/build/Release/ && npm run build"
17 "vercel-build": "yum install libuuid-devel libmount-devel && cp /lib64/{libuuid,libmount,libblkid}.so.1 node_modules/canvas/build/Release/ && npm run build"
17 },
18 },
pages/api/dev-search.js+1−1
@@ -1,6 +1,6 @@
1export default async (req, res) => {
1export default async (req, res) => {
2 const results = await fetch(
2 const results = await fetch(
3− `https://developers.urbit.org/api/search?q=${req.query.q}`
3+ `https://developers.urbit.org/api/search?q=${encodeURIComponent(req.query.q)}`
4 ).then((res) => res.json());
4 ).then((res) => res.json());
5 res.statusCode = 200;
5 res.statusCode = 200;
6 res.setHeader("Content-Type", "application/json");
6 res.setHeader("Content-Type", "application/json");
pages/api/glossary.jsdeleted−10
@@ -1,10 +0,0 @@
1−export default async (req, res) => {
2− const results = await fetch(
3− `https://developers.urbit.org/api/glossary?q=${encodeURIComponent(
4− req.query.q
5− )}`
6− ).then((res) => res.json());
7− res.statusCode = 200;
8− res.setHeader("Content-Type", "application/json");
9− res.end(JSON.stringify(results));
10−};
pages/api/search.js+10−1
@@ -1,4 +1,5 @@
1import { index } from "../../cache/data";
1import { index } from "../../cache/data";
2+import { glossary } from "../../cache/glossary";
2const levenSort = require("leven-sort");
3const levenSort = require("leven-sort");
3
4
4export default (req, res) => {
5export default (req, res) => {
@@ -9,6 +10,13 @@ export default (req, res) => {
9 e?.parent.toLowerCase().includes(req.query.q.toLowerCase())
10 e?.parent.toLowerCase().includes(req.query.q.toLowerCase())
10 );
11 );
11
12
13+ const entries = glossary.filter((entry) => {
14+ return (
15+ entry.name.includes(req.query.q.toLowerCase()) ||
16+ entry.symbol.includes(req.query.q)
17+ );
18+ });
19+
12 results.push(
20 results.push(
13 ...index
21 ...index
14 .filter((e) => !results.includes(e))
22 .filter((e) => !results.includes(e))
@@ -30,7 +38,8 @@ export default (req, res) => {
30 "parent",
38 "parent",
31 "headings",
39 "headings",
32 ]);
40 ]);
41+ const sortedEntries = levenSort(entries, req.query.q, ["symbol"]);
33 res.statusCode = 200;
42 res.statusCode = 200;
34 res.setHeader("Content-Type", "application/json");
43 res.setHeader("Content-Type", "application/json");
35− res.end(JSON.stringify({ results: sorted }));
44+ res.end(JSON.stringify({ results: sorted, glossary: sortedEntries }));
36};
45};