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
This commit is contained in:
Kasper Fabricius Kristensen
2021-08-27 14:38:35 +02:00
committed by GitHub
parent 15161bb0e9
commit d89332b91a
8 changed files with 295 additions and 49 deletions

View File

@@ -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",

View File

@@ -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 <Link to={hit.url}>{children}</Link>
let url = replaceUrl(hit)
return <Link to={url}>{children}</Link>
}
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

View File

@@ -20,9 +20,11 @@ const Content = ({ data, api }) => {
},
}}
>
{data.sections.map((s, i) => {
return <Section key={i} data={s} />
})}
<main className="DocSearch-content">
{data.sections.map((s, i) => {
return <Section key={i} data={s} />
})}
</main>
</Box>
</Flex>
)

View File

@@ -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 (
<main
ref={sectionRef}
id={convertToKebabCase(section.section_name)}
className="DocSearch-content"
>
<section ref={sectionRef} id={convertToKebabCase(section.section_name)}>
<Box
sx={{
borderBottom: "hairline",
@@ -151,7 +143,7 @@ const Section = ({ data }) => {
) : null}
</Flex>
</ResponsiveContainer>
{!isExpanded ? (
{!isExpanded && (
<Flex
sx={{
justifyContent: "center",
@@ -173,34 +165,39 @@ const Section = ({ data }) => {
SHOW <ChevronDown fill={"dark"} styles={{ mr: "-10px" }} />
</Button>
</Flex>
) : (
<Box mt={4}>
{section.paths.map((p, i) => {
return (
<Flex
key={i}
sx={{
flexDirection: "column",
}}
>
{p.methods.map((m, i) => {
return (
<Method
key={i}
data={m}
section={convertToKebabCase(section.section_name)}
pathname={p.name}
/>
)
})}
</Flex>
)
})}
</Box>
)}
<Box
id="method-container"
mt={4}
sx={{
display: isExpanded ? "block" : "none",
}}
>
{section.paths.map((p, i) => {
return (
<Flex
key={i}
sx={{
flexDirection: "column",
}}
>
{p.methods.map((m, i) => {
return (
<Method
key={i}
data={m}
section={convertToKebabCase(section.section_name)}
pathname={p.name}
/>
)
})}
</Flex>
)
})}
</Box>
</Flex>
</Box>
</main>
</section>
)
}

View File

@@ -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 (
<a href={url} onClick={goToHierarchy}>
{children}
</a>
)
}
/**
* If the result is NOT part of the currently viewed API
*/
if (url.includes(`api/${getOtherAPI()}`)) {
formatURL()
return (
<a href={url} onClick={navigateHierarchy}>
{children}
</a>
)
}
/**
* If the result is part of the Docasaurus docs
*/
return <a href={url}>{children}</a>
}
export default HitComponent

View File

@@ -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 <DocSearch apiKey={algoliaApiKey} indexName="medusa-commerce" />
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 (
<DocSearch
apiKey={algoliaApiKey}
indexName="medusa-commerce"
hitComponent={HitComponent}
navigator={{
navigate({ item }) {
if (item.url.includes(`api/${api}`)) {
goToHierarchy(item)
} else if (item.url.includes(`api/${getOtherAPI()}`)) {
navigateHierarchy(item)
} else {
window.location = item.url
}
},
navigateNewTab({ item }) {
const 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")
},
}}
/>
)
}
export default Search

View File

@@ -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)

View File

@@ -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
}
}