diff --git a/next.config.js b/next.config.js
index e023744..f95e6f7 100644
--- a/next.config.js
+++ b/next.config.js
@@ -3,5 +3,13 @@ const { i18n } = require('./next-i18next.config');
module.exports = {
reactStrictMode: true,
- i18n
+ i18n,
+ webpack(config) {
+ config.resolve.fallback = {
+ ...config.resolve.fallback,
+ fs: false,
+ };
+
+ return config;
+ },
}
diff --git a/package.json b/package.json
index d91f4a2..65c1081 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"@emotion/styled": "^11.3.0",
"arweave": "^1.10.17",
"axios": "^0.22.0",
+ "fast-csv": "^4.3.6",
"framer-motion": "^4.1.17",
"graphql": "^15.6.1",
"next": "11.1.2",
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 06c52d7..9cac895 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -1,4 +1,10 @@
{
+ "glossetaTitle": "Glosseta",
+ "glossetaNavbarButtonA11yText": "Takes you to the main search page",
+ "glossaryButton": "Glossary",
+ "searchButtonTitle": "Search",
+ "scrollToTheTopButton": "Click here to scroll to the top of the page",
+
"web3GlossaryHeading": "Web3 Glossary",
"glossetaDescription": "Glosseta is an open-source glossary meant to help people explore and learn the terminology behind web3",
@@ -10,10 +16,16 @@
"searchResultContentSourceDescription" : "The definition you see above is stored on the Arweave network which is a protocol for storing data permanently in a decentralized manner among network users who have storage to spare. As a result, this definition will live forever on the Arweave network.",
"searchResultContentSourceTransactionLinkText" : "Click here to view the Arweave transaction for this definition",
+ "glossaryPageHeading" : "Glossary",
+ "somethingMissingHeading" : "Something missing?",
+ "twitterTermRequestDescription" : "If there's a specific term you were looking for but didn't see above, please let us know and we'll get it added. Give us a shout on ",
+ "glossaryTermFetchErrorHeading": "Error",
+ "glossaryTermFetchErrorText" : "Something went wrong trying to retrieve the glossary terms. Please refresh the page or try searching for an individual term. If the issue persists please reach out to us on ",
+
"opensInANewWindow" : "Opens in a new window",
"twitter" : "Twitter",
"or" : "or",
- "gitHubIssueText": "open up a GitHub issue",
+ "gitHubIssueText" : "open up a GitHub issue",
"unavailableSearchResultDescription" : "This term isn't in our knowledge base at the moment. If you think this is something we should have, please reach out to us on",
"apiFetchErrorText" : "Something went wrong while trying to retrieve the definition. Please refresh the page or search for another term.",
diff --git a/src/pages/components/layout/page/index.tsx b/src/pages/components/layout/page/index.tsx
index f4804ff..c2c11fe 100644
--- a/src/pages/components/layout/page/index.tsx
+++ b/src/pages/components/layout/page/index.tsx
@@ -1,21 +1,25 @@
import React from "react";
import { Stack } from "@chakra-ui/react";
import Footer from "../../footer/footer";
+import Nav from "../../nav";
const PageLayout = ({ children }: { children?: object }): JSX.Element => {
return (
-
- {children}
+ <>
+
+
+ {children}
+
-
+ >
);
};
diff --git a/src/pages/components/nav/index.tsx b/src/pages/components/nav/index.tsx
new file mode 100644
index 0000000..47b5896
--- /dev/null
+++ b/src/pages/components/nav/index.tsx
@@ -0,0 +1,81 @@
+import React from "react";
+import { chakra, Flex, HStack, Avatar } from "@chakra-ui/react";
+import Link from "next/link";
+import { useTranslation } from "react-i18next";
+import MobileNav from "./mobile-nav";
+import NavItems from "./nav-items";
+import { useRouter } from "next/router";
+
+/**
+ * Implementation inspired from the developer dao website
+ *
+ * source: https://github.com/Developer-DAO/developerdao.com
+ * */
+export default function Nav() {
+ const { t } = useTranslation();
+ const router = useRouter();
+
+ const isHomePage = router.pathname === "/";
+ const isGlossaryPage = router.pathname === "/glossary";
+ const isSearchPage = router.pathname === "/search";
+
+ return (
+
+
+
+
+
+
+ {t("glossetaTitle")}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/components/nav/mobile-nav.tsx b/src/pages/components/nav/mobile-nav.tsx
new file mode 100644
index 0000000..d87de37
--- /dev/null
+++ b/src/pages/components/nav/mobile-nav.tsx
@@ -0,0 +1,59 @@
+import React from "react";
+import {
+ Box,
+ useColorModeValue,
+ useDisclosure,
+ VStack,
+ IconButton,
+ CloseButton,
+} from "@chakra-ui/react";
+import { AiOutlineMenu } from "react-icons/ai";
+import NavItems from "./nav-items";
+
+export default function MobileNav({
+ isHomePage,
+ isGlossaryPage,
+ isSearchPage,
+}: any) {
+ const bg = useColorModeValue("gray.800", "gray.800");
+ const { onClose, onOpen, isOpen } = useDisclosure();
+
+ return (
+
+ }
+ onClick={onOpen}
+ />
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/components/nav/nav-items.tsx b/src/pages/components/nav/nav-items.tsx
new file mode 100644
index 0000000..086ccdc
--- /dev/null
+++ b/src/pages/components/nav/nav-items.tsx
@@ -0,0 +1,41 @@
+import React from "react";
+
+import Link from "next/link";
+import { useTranslation } from "react-i18next";
+import styles from "../../../../styles/Home.module.css";
+import { Button } from "@chakra-ui/react";
+
+export default function NavItems({
+ isHomePage,
+ isGlossaryPage,
+ isSearchPage,
+}: any) {
+ const { t } = useTranslation();
+
+ /**
+ * The intent of the conditional checks is so that the respective pages link is not available to the user to click.
+ * So if the user is on the Glossary page, they will see every nav item except the Search (i.e. Home) item.
+ */
+ return (
+ <>
+ {isGlossaryPage && (
+
+
+
+ )}
+
+ {(isHomePage || isSearchPage) && (
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/pages/glossary/glossary-data-fetch-error.tsx b/src/pages/glossary/glossary-data-fetch-error.tsx
new file mode 100644
index 0000000..0d8089e
--- /dev/null
+++ b/src/pages/glossary/glossary-data-fetch-error.tsx
@@ -0,0 +1,46 @@
+import { Box, Text, Container, VStack, Heading, Link } from "@chakra-ui/react";
+import { ExternalLinkIcon } from "@chakra-ui/icons";
+import { useTranslation } from "next-i18next";
+import styles from "../../../styles/Home.module.css";
+
+export const GlossaryDataFetchError = (): JSX.Element => {
+ const twitter_href = `https://twitter.com/intent/tweet?screen_name=Glossetadotcom`;
+ const { t } = useTranslation();
+
+ return (
+ <>
+
+
+
+
+ {t("glossaryTermFetchErrorHeading")}
+
+
+ {t("glossaryTermFetchErrorText")}{" "}
+
+ {t("twitter")}
+
+ {t("opensInANewWindow")}
+
+
+
+
+
+
+ >
+ );
+};
+
+export default GlossaryDataFetchError;
diff --git a/src/pages/glossary/index.tsx b/src/pages/glossary/index.tsx
new file mode 100644
index 0000000..f43b338
--- /dev/null
+++ b/src/pages/glossary/index.tsx
@@ -0,0 +1,85 @@
+import PageLayout from "../components/layout/page";
+import { serverSideTranslations } from "next-i18next/serverSideTranslations";
+import getTermList from "../../utils/termListUtil";
+import {
+ SimpleGrid,
+ chakra,
+ Heading,
+ VStack,
+ Container,
+} from "@chakra-ui/react";
+import { ResultBox } from "../search/result-box";
+import { NewTermRequest } from "./new-term-request";
+import { ScrollToTopButton } from "./scroll-to-top-button";
+import { useTranslation } from "react-i18next";
+import { GlossaryDataFetchError } from "./glossary-data-fetch-error";
+
+const AllTerms = ({ terms }: any): JSX.Element => {
+ const { t } = useTranslation();
+
+ return (
+ <>
+
+
+
+ {t("glossaryPageHeading")}
+
+
+
+
+ {terms.length === 0 ? (
+
+ ) : (
+ <>
+ {terms.map((termItem: any) => {
+ return (
+
+ );
+ })}
+
+
+ >
+ )}
+
+
+
+
+
+ >
+ );
+};
+
+export async function getStaticProps({ locale }: any) {
+ const terms = await getTermList(locale);
+
+ return {
+ props: {
+ ...(await serverSideTranslations(locale, ["common"])),
+ terms: terms,
+ // Will be passed to the page component as props
+ },
+ };
+}
+
+export default AllTerms;
diff --git a/src/pages/glossary/new-term-request.tsx b/src/pages/glossary/new-term-request.tsx
new file mode 100644
index 0000000..02da74d
--- /dev/null
+++ b/src/pages/glossary/new-term-request.tsx
@@ -0,0 +1,39 @@
+import { Box, Text, Link, Container, VStack, Heading } from "@chakra-ui/react";
+import { ExternalLinkIcon } from "@chakra-ui/icons";
+import styles from "../../../styles/Home.module.css";
+import { useTranslation } from 'next-i18next';
+
+export const NewTermRequest = ({ term }: any): JSX.Element => {
+ const { t } = useTranslation();
+ const twitter_href = 'https://twitter.com/intent/tweet?screen_name=Glossetadotcom&text=Please%20add%20the%20following%20term%20to%20the%20knowledge%20base:';
+
+ return (
+ <>
+
+
+
+
+ {t('somethingMissingHeading')}
+
+
+ {t('twitterTermRequestDescription')}{" "}
+
+ {t('twitter')}
+
+ {t('opensInANewWindow')}
+
+
+
+
+
+
+ >
+ );
+};
+
+export default NewTermRequest;
diff --git a/src/pages/glossary/scroll-to-top-button.tsx b/src/pages/glossary/scroll-to-top-button.tsx
new file mode 100644
index 0000000..773fbfa
--- /dev/null
+++ b/src/pages/glossary/scroll-to-top-button.tsx
@@ -0,0 +1,46 @@
+import { Button } from "@chakra-ui/react";
+import { ArrowUpIcon } from "@chakra-ui/icons";
+import styles from "../../../styles/Home.module.css";
+import { useTranslation } from "next-i18next";
+
+export const ScrollToTopButton = ({
+ anchorIdToFocus = "",
+}: {
+ anchorIdToFocus: string;
+}): JSX.Element => {
+ const { t } = useTranslation();
+
+ const scrollToTheTop = () => {
+ if (anchorIdToFocus === "") {
+ document.body.scrollTop = 0;
+ document.documentElement.scrollTop = 0;
+ } else {
+ // @ts-ignore: Object is possibly 'null'.
+ document.getElementById(anchorIdToFocus).focus();
+ }
+ };
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default ScrollToTopButton;
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 359a3ed..efeb23d 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -7,6 +7,7 @@ import {
Heading,
Text,
Box,
+ Container,
} from "@chakra-ui/react";
import PageLayout from "./components/layout/page";
import SearchBar from "./search/search-bar";
@@ -18,33 +19,39 @@ const Home: NextPage = () => {
return (
-
-
+
+
-
- {t("web3GlossaryHeading")}
-
-
-
-
- {t("glossetaDescription")}
-
-
-
-
-
-
+
+
+
+
+
+ {t("web3GlossaryHeading")}
+
+
+
+
+
+
+
+ {t("glossetaDescription")}
+
+
+
+
+
);
diff --git a/src/pages/search/content-source-box.tsx b/src/pages/search/content-source-box.tsx
new file mode 100644
index 0000000..8f175f3
--- /dev/null
+++ b/src/pages/search/content-source-box.tsx
@@ -0,0 +1,49 @@
+import { Heading, Box, Text, Link, VStack } from "@chakra-ui/react";
+import { ExternalLinkIcon } from "@chakra-ui/icons";
+import { VIEWBLOCK_URL } from "../../utils/glosseta-constants";
+import styles from "../../../styles/Home.module.css";
+import { useTranslation } from "next-i18next";
+
+export const ContentSourceBox = ({ transactionId }: any): JSX.Element => {
+ const { t } = useTranslation();
+ const view_block_url = `${VIEWBLOCK_URL}/${transactionId}` as string;
+
+ return (
+ <>
+
+
+
+ {t("searchResultContentSourceHeading")}
+
+
+ {t("searchResultContentSourceDescription")}
+
+ {t("searchResultContentSourceTransactionLinkText")}
+
+
+ {t("opensInANewWindow")}
+
+
+
+
+
+ >
+ );
+};
+
+export default ContentSourceBox;
diff --git a/src/pages/search/result-box.tsx b/src/pages/search/result-box.tsx
new file mode 100644
index 0000000..a468701
--- /dev/null
+++ b/src/pages/search/result-box.tsx
@@ -0,0 +1,33 @@
+import { Heading, Box, Text, VStack, Tag, TagLabel } from "@chakra-ui/react";
+
+export const ResultBox = ({ definition, category, term }: any): JSX.Element => {
+ return (
+ <>
+
+
+
+ {term}
+
+
+ {category}
+
+
+ {definition}
+
+
+
+ >
+ );
+};
+
+export default ResultBox;
diff --git a/src/pages/search/result.tsx b/src/pages/search/result.tsx
index 4e26374..cb48eb5 100644
--- a/src/pages/search/result.tsx
+++ b/src/pages/search/result.tsx
@@ -1,17 +1,6 @@
-import {
- Heading,
- Box,
- Text,
- Link,
- Container,
- VStack,
- Tag,
- TagLabel,
-} from "@chakra-ui/react";
-import { ExternalLinkIcon } from "@chakra-ui/icons";
-import { VIEWBLOCK_URL } from "../../utils/glosseta-constants";
-import styles from "../../../styles/Home.module.css";
-import { useTranslation } from "next-i18next";
+import { Container, VStack } from "@chakra-ui/react";
+import { ResultBox } from "./result-box";
+import { ContentSourceBox } from "./content-source-box";
export const Result = ({
transactionId,
@@ -19,76 +8,12 @@ export const Result = ({
category,
term,
}: any): JSX.Element => {
- const { t } = useTranslation();
- const view_block_url = `${VIEWBLOCK_URL}/${transactionId}` as string;
-
return (
<>
-
-
-
- {term}
-
-
- {category}
-
-
- {definition}
-
-
-
-
-
-
- {t("searchResultContentSourceHeading")}
-
-
- {t("searchResultContentSourceDescription")}
-
- {t("searchResultContentSourceTransactionLinkText")}
-
-
- {t("opensInANewWindow")}
-
-
-
-
-
+
+
>
diff --git a/src/pages/search/search-bar.tsx b/src/pages/search/search-bar.tsx
index 351096c..50074a1 100644
--- a/src/pages/search/search-bar.tsx
+++ b/src/pages/search/search-bar.tsx
@@ -55,7 +55,7 @@ const SearchBar = ({
fontSize: { base: "sm", sm: "md" },
}}
onClick={(event) => {
- event.currentTarget.scrollIntoView();
+ event.currentTarget.scrollIntoView(false);
}}
onKeyPress={(event) => {
if (event.key === "Enter" && searchTerm !== "") {
diff --git a/src/types/glossary-item.ts b/src/types/glossary-item.ts
new file mode 100644
index 0000000..028bb15
--- /dev/null
+++ b/src/types/glossary-item.ts
@@ -0,0 +1,5 @@
+export type termItem = {
+ term: string,
+ category: string,
+ definition: string
+}
\ No newline at end of file
diff --git a/src/utils/termListUtil.ts b/src/utils/termListUtil.ts
new file mode 100644
index 0000000..168ba52
--- /dev/null
+++ b/src/utils/termListUtil.ts
@@ -0,0 +1,63 @@
+import { termItem } from "../types/glossary-item";
+import { parseStream } from "fast-csv";
+import { createReadStream } from "fs";
+
+const createTermItem = (row: any) => {
+ return {
+ term: row['term'],
+ category: row['category'],
+ definition: row['description']
+ } as termItem;
+};
+
+const readCSV = (locale: any) => {
+ return new Promise((resolve, reject) => {
+ let terms = [] as termItem[];
+
+ // We will default to the `en` locale for now until the other resource files are populated
+ const stream = createReadStream(`./resources/master_term_bank_en.csv`)
+ .on("error", (error) => {
+ return reject(error);
+ });
+
+ parseStream(stream, { headers: true, ignoreEmpty: true })
+ .on("error", (error) => {
+ console.log(`[Error reading CSV] error=${error}`);
+
+ return reject(error);
+ })
+ .on("data", (row) => {
+ const termItem = createTermItem(row);
+
+ terms.push(termItem);
+ terms.sort((a: termItem, b: termItem) => {
+ if (!a.term || a.term < b.term) {
+ return -1
+ } else if (!b.term || a.term > b.term) {
+ return 1
+ }
+ return 0
+ })
+
+ })
+ .on("end", () => {
+ resolve(terms);
+ });
+ });
+
+
+};
+
+const getTermList = async (locale: any) => {
+ try {
+ const data = await readCSV(locale);
+
+ return data;
+ } catch (error) {
+ console.log(`[Error processing terms from master list] error=${error}`);
+ }
+
+ return [];
+}
+
+export default getTermList;
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 58cbbc2..4ad1ca4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -802,6 +802,31 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
+"@fast-csv/format@4.3.5":
+ version "4.3.5"
+ resolved "https://registry.yarnpkg.com/@fast-csv/format/-/format-4.3.5.tgz#90d83d1b47b6aaf67be70d6118f84f3e12ee1ff3"
+ integrity sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==
+ dependencies:
+ "@types/node" "^14.0.1"
+ lodash.escaperegexp "^4.1.2"
+ lodash.isboolean "^3.0.3"
+ lodash.isequal "^4.5.0"
+ lodash.isfunction "^3.0.9"
+ lodash.isnil "^4.0.0"
+
+"@fast-csv/parse@4.3.6":
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/@fast-csv/parse/-/parse-4.3.6.tgz#ee47d0640ca0291034c7aa94039a744cfb019264"
+ integrity sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==
+ dependencies:
+ "@types/node" "^14.0.1"
+ lodash.escaperegexp "^4.1.2"
+ lodash.groupby "^4.6.0"
+ lodash.isfunction "^3.0.9"
+ lodash.isnil "^4.0.0"
+ lodash.isundefined "^3.0.1"
+ lodash.uniq "^4.5.0"
+
"@graphql-typed-document-node/core@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950"
@@ -1000,6 +1025,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42"
integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==
+"@types/node@^14.0.1":
+ version "14.17.34"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.34.tgz#fe4b38b3f07617c0fa31ae923fca9249641038f0"
+ integrity sha512-USUftMYpmuMzeWobskoPfzDi+vkpe0dvcOBRNOscFrGxVp4jomnRxWuVohgqBow2xyIPC0S3gjxV/5079jhmDg==
+
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@@ -2154,6 +2184,14 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
+fast-csv@^4.3.6:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-4.3.6.tgz#70349bdd8fe4d66b1130d8c91820b64a21bc4a63"
+ integrity sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==
+ dependencies:
+ "@fast-csv/format" "4.3.5"
+ "@fast-csv/parse" "4.3.6"
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -2861,6 +2899,41 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
+lodash.escaperegexp@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
+ integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
+
+lodash.groupby@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1"
+ integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=
+
+lodash.isboolean@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+ integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
+
+lodash.isequal@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+ integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+
+lodash.isfunction@^3.0.9:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
+ integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==
+
+lodash.isnil@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c"
+ integrity sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw=
+
+lodash.isundefined@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48"
+ integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=
+
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -2876,6 +2949,11 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
+lodash.uniq@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+ integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
+
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"