From d89332b91acaf72ab2e60563ef3871be0dc44318 Mon Sep 17 00:00:00 2001
From: Kasper Fabricius Kristensen
<45367945+kasperkristensen@users.noreply.github.com>
Date: Fri, 27 Aug 2021 14:38:35 +0200
Subject: [PATCH] fix: improvements to Algolia DocSearch (#357)
What
Changes selectors of API references to improve DocSearch results
Implements custom navigation for DocSearch in both API ref and docs. (Uses goTo method for in page navigation, Gatsby navigate for navigation within Gatsby site, changes URL's to API reference to make use of methods implemented to provide better metadata etc.)
Implements a new way to make all HTML available in API reference without need the crawler to have Javascript enabled which should allow Algolia to crawl our site faster.
Why
To provide users with a good search experience within our docs and API reference
Testing
Tested using a local setup of the Algolia crawler
---
www/docs/docusaurus.config.js | 3 +-
www/docs/src/theme/SearchBar/index.js | 45 ++++++++-
www/reference/src/components/content/index.js | 8 +-
.../src/components/content/section.js | 69 +++++++------
.../src/components/search/hit-component.js | 96 +++++++++++++++++++
www/reference/src/components/search/index.js | 95 +++++++++++++++++-
.../src/context/navigation-context.js | 15 ++-
www/reference/src/utils/check-display.js | 13 +++
8 files changed, 295 insertions(+), 49 deletions(-)
create mode 100644 www/reference/src/components/search/hit-component.js
create mode 100644 www/reference/src/utils/check-display.js
diff --git a/www/docs/docusaurus.config.js b/www/docs/docusaurus.config.js
index b0b0704c2a..3fd94dbece 100644
--- a/www/docs/docusaurus.config.js
+++ b/www/docs/docusaurus.config.js
@@ -83,7 +83,8 @@ module.exports = {
items: [
{
label: "Stack Overflow",
- href: "https://stackoverflow.com/questions/tagged/medusa-commerce",
+ href:
+ "https://stackoverflow.com/questions/tagged/medusa-commerce",
},
{
label: "Discord",
diff --git a/www/docs/src/theme/SearchBar/index.js b/www/docs/src/theme/SearchBar/index.js
index 058c6afd02..80b95c845e 100644
--- a/www/docs/src/theme/SearchBar/index.js
+++ b/www/docs/src/theme/SearchBar/index.js
@@ -20,8 +20,33 @@ import styles from "./styles.module.css"
let DocSearchModal = null
+const convertToKebabCase = (string) => {
+ return string
+ .replace(/\s+/g, "-")
+ .replace("'", "")
+ .replace(".", "")
+ .replace('"', "")
+ .toLowerCase()
+}
+
+const replaceUrl = (item) => {
+ let { url, hierarchy } = item
+ if (url.includes("api/store") || url.includes("/api/admin")) {
+ url = url.replace("#", "/")
+ if (hierarchy.lvl2) {
+ const index = url.lastIndexOf("/")
+ url =
+ url.substring(0, index) +
+ `/${convertToKebabCase(hierarchy.lvl1)}` +
+ url.substring(index)
+ }
+ }
+ return url
+}
+
function Hit({ hit, children }) {
- return {children}
+ let url = replaceUrl(hit)
+ return {children}
}
function ResultsFooter({ state, onClose }) {
@@ -101,8 +126,22 @@ function DocSearch({ contextualSearch, ...props }) {
)
const navigator = useRef({
- navigate({ itemUrl }) {
- history.push(itemUrl)
+ navigate({ item }) {
+ let url = replaceUrl(item)
+ history.push(url)
+ },
+ navigateNewTab({ item }) {
+ let url = replaceUrl(item)
+ const windowReference = window.open(url, "_blank", "noopener")
+
+ if (windowReference) {
+ windowReference.focus()
+ }
+ },
+ navigateNewWindow({ item }) {
+ const url = replaceUrl(item)
+
+ window.open(url, "_blank", "noopener")
},
}).current
diff --git a/www/reference/src/components/content/index.js b/www/reference/src/components/content/index.js
index 1df622a02a..ccb8f39ff2 100644
--- a/www/reference/src/components/content/index.js
+++ b/www/reference/src/components/content/index.js
@@ -20,9 +20,11 @@ const Content = ({ data, api }) => {
},
}}
>
- {data.sections.map((s, i) => {
- return
- })}
+
+ {data.sections.map((s, i) => {
+ return
+ })}
+
)
diff --git a/www/reference/src/components/content/section.js b/www/reference/src/components/content/section.js
index d06f8338b4..770300f97a 100644
--- a/www/reference/src/components/content/section.js
+++ b/www/reference/src/components/content/section.js
@@ -14,7 +14,7 @@ import useInView from "../../hooks/use-in-view"
const Section = ({ data }) => {
const { section } = data
- const [isExpanded, setIsExpanded] = useState(true)
+ const [isExpanded, setIsExpanded] = useState(false)
const { openSections, updateSection, updateMetadata } = useContext(
NavigationContext
)
@@ -51,10 +51,6 @@ const Section = ({ data }) => {
scrollIntoView()
}
- useEffect(() => {
- setIsExpanded(false)
- }, [])
-
useEffect(() => {
const shouldOpen = openSections.includes(
convertToKebabCase(section.section_name)
@@ -82,11 +78,7 @@ const Section = ({ data }) => {
}, [isInView])
return (
-
+
{
) : null}
- {!isExpanded ? (
+ {!isExpanded && (
{
SHOW
- ) : (
-
- {section.paths.map((p, i) => {
- return (
-
- {p.methods.map((m, i) => {
- return (
-
- )
- })}
-
- )
- })}
-
)}
+
+ {section.paths.map((p, i) => {
+ return (
+
+ {p.methods.map((m, i) => {
+ return (
+
+ )
+ })}
+
+ )
+ })}
+
-
+
)
}
diff --git a/www/reference/src/components/search/hit-component.js b/www/reference/src/components/search/hit-component.js
new file mode 100644
index 0000000000..250d872206
--- /dev/null
+++ b/www/reference/src/components/search/hit-component.js
@@ -0,0 +1,96 @@
+import React, { useContext } from "react"
+import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
+import NavigationContext from "../../context/navigation-context"
+import { navigate } from "gatsby"
+
+const HitComponent = ({ hit, children }) => {
+ const { goTo, api } = useContext(NavigationContext)
+ let { url, type, hierarchy } = hit
+
+ /** Get the API that is not currenty being viewed, so we can create
+ * an URL that goes to the other API.
+ */
+ const getOtherAPI = () => {
+ if (api === "store") return "admin"
+ if (api === "admin") return "store"
+ }
+
+ /** If result is part of currently viewed API then we scroll to the
+ * concept/method, and update the pages metadata.
+ */
+ const goToHierarchy = e => {
+ e.preventDefault()
+ if (hierarchy.lvl2) {
+ goTo({
+ section: convertToKebabCase(hierarchy.lvl1),
+ method: convertToKebabCase(hierarchy.lvl2),
+ })
+ } else {
+ goTo({ section: convertToKebabCase(hierarchy.lvl1) })
+ }
+ }
+
+ /** If result is NOT part of currently viewed API, but still a part of
+ * the Gatsby site, then we use Gatsby's navigate function for improved
+ * linking.
+ */
+ const navigateHierarchy = e => {
+ e.preventDefault()
+ if (hierarchy.lvl2) {
+ navigate(
+ `/api/${getOtherAPI()}/${convertToKebabCase(
+ hierarchy.lvl1
+ )}/${convertToKebabCase(hierarchy.lvl2)}`
+ )
+ } else {
+ navigate(`/api/${getOtherAPI()}/${convertToKebabCase(hierarchy.lvl1)}`)
+ }
+ }
+
+ /**
+ * If result is part of the API reference then we want to strip
+ * the #'s from the URL's. If the result is of LVL2 then we want
+ * to add the LVL1 to the URL: '/store/#create-cart' -> '/store/cart/create-cart'
+ */
+ const formatURL = () => {
+ url = url.replace("#", "/")
+ if (type === "lvl2") {
+ const index = url.lastIndexOf("/")
+ url =
+ url.substring(0, index) +
+ `/${convertToKebabCase(hierarchy.lvl1)}` +
+ url.substring(index)
+ }
+ }
+
+ /**
+ * If the result is part of the currently viewed API
+ */
+ if (url.includes(`api/${api}`)) {
+ formatURL()
+ return (
+
+ {children}
+
+ )
+ }
+
+ /**
+ * If the result is NOT part of the currently viewed API
+ */
+ if (url.includes(`api/${getOtherAPI()}`)) {
+ formatURL()
+ return (
+
+ {children}
+
+ )
+ }
+
+ /**
+ * If the result is part of the Docasaurus docs
+ */
+ return {children}
+}
+
+export default HitComponent
diff --git a/www/reference/src/components/search/index.js b/www/reference/src/components/search/index.js
index 78d969a3ce..8af62c0057 100644
--- a/www/reference/src/components/search/index.js
+++ b/www/reference/src/components/search/index.js
@@ -1,11 +1,102 @@
-import React from "react"
+import React, { useContext } from "react"
import { DocSearch } from "@docsearch/react"
import "../../medusa-plugin-themes/docsearch/theme.css"
+import HitComponent from "./hit-component"
+import NavigationContext from "../../context/navigation-context"
+import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
+import { navigate } from "gatsby-link"
const algoliaApiKey = process.env.ALGOLIA_API_KEY || "temp"
const Search = () => {
- return
+ const { goTo, api } = useContext(NavigationContext)
+
+ const getOtherAPI = () => {
+ if (api === "store") return "admin"
+ if (api === "admin") return "store"
+ }
+
+ const replaceUrl = item => {
+ let { url, hierarchy } = item
+ if (url.includes("api/store") || url.includes("/api/admin")) {
+ url = url.replace("#", "/")
+ if (hierarchy.lvl2) {
+ const index = url.lastIndexOf("/")
+ url =
+ url.substring(0, index) +
+ `/${convertToKebabCase(hierarchy.lvl1)}` +
+ url.substring(index)
+ }
+ }
+ return url
+ }
+
+ /** If result is part of currently viewed API then we scroll to the
+ * concept/method, and update the pages metadata.
+ */
+ const goToHierarchy = item => {
+ const { hierarchy } = item
+ if (hierarchy.lvl2) {
+ goTo({
+ section: convertToKebabCase(hierarchy.lvl1),
+ method: convertToKebabCase(hierarchy.lvl2),
+ })
+ } else {
+ goTo({ section: convertToKebabCase(hierarchy.lvl1) })
+ }
+ }
+
+ /** If result is NOT part of currently viewed API, but still a part of
+ * the Gatsby site, then we use Gatsby's navigate function for improved
+ * linking.
+ */
+ const navigateHierarchy = item => {
+ const { hierarchy } = item
+ if (hierarchy.lvl2) {
+ navigate(
+ `/api/${getOtherAPI()}/${convertToKebabCase(
+ hierarchy.lvl1
+ )}/${convertToKebabCase(hierarchy.lvl2)}`
+ )
+ } else {
+ navigate(`/api/${getOtherAPI()}/${convertToKebabCase(hierarchy.lvl1)}`)
+ }
+ }
+
+ return (
+
+ )
}
export default Search
diff --git a/www/reference/src/context/navigation-context.js b/www/reference/src/context/navigation-context.js
index a25f1200bf..93a8c3e404 100644
--- a/www/reference/src/context/navigation-context.js
+++ b/www/reference/src/context/navigation-context.js
@@ -1,4 +1,5 @@
import React, { useReducer } from "react"
+import { checkDisplay } from "../utils/check-display"
import scrollParent from "../utils/scroll-parent"
import types from "./types"
@@ -79,10 +80,16 @@ const scrollNav = id => {
const scrollToElement = async id => {
const element = document.querySelector(`#${id}`)
if (element) {
- element.scrollIntoView({
- block: "start",
- inline: "nearest",
- })
+ if (checkDisplay(element)) {
+ element.scrollIntoView({
+ block: "start",
+ inline: "nearest",
+ })
+ } else {
+ setTimeout(() => {
+ scrollToElement(id)
+ }, 100)
+ }
} else {
setTimeout(() => {
scrollToElement(id)
diff --git a/www/reference/src/utils/check-display.js b/www/reference/src/utils/check-display.js
new file mode 100644
index 0000000000..e0b2113359
--- /dev/null
+++ b/www/reference/src/utils/check-display.js
@@ -0,0 +1,13 @@
+export const checkDisplay = element => {
+ const mc = element.closest("#method-container")
+
+ //if no closest method container exists then it is a section and we can scroll
+ if (!mc) return true
+
+ const style = getComputedStyle(mc)
+ if (style.display === "none") {
+ return false
+ } else {
+ return true
+ }
+}