From 0eb7e75a6cf16b7ff0cceb877324c90c91d96a33 Mon Sep 17 00:00:00 2001 From: Dmitry Matiouchenko Date: Mon, 30 Jan 2023 15:52:37 -0800 Subject: [PATCH] Swap theme search to iframe search-frame page (#1342) * chore: wip + saving indexing change with new index map * feat: local search seems to work but indexPrefix error * feat: iframed search works * chore(pre-release): publish@4.7.11-search1 * fix: flip pathname check * chore(pre-release): publish@4.7.11-search2 * fix: indexPrefix undefined, aioSearch missing, link target no links check * chore(pre-release): publish@4.7.11-search3 * chore: cleanup * chore: update yarn.lock --- .../gatsby-theme-aio/algolia/index-records.js | 20 +- packages/gatsby-theme-aio/package.json | 2 +- .../src/components/Layout/index.js | 205 +++++++++++++++--- .../src/components/Search/index.js | 36 ++- yarn.lock | 98 +-------- 5 files changed, 213 insertions(+), 148 deletions(-) diff --git a/packages/gatsby-theme-aio/algolia/index-records.js b/packages/gatsby-theme-aio/algolia/index-records.js index dfbab3a6fa..02db9b2a7c 100644 --- a/packages/gatsby-theme-aio/algolia/index-records.js +++ b/packages/gatsby-theme-aio/algolia/index-records.js @@ -14,10 +14,10 @@ const getImportedContent = require('./helpers/get-imported-content'); const getIFrameContent = require('./helpers/get-iframe-content'); const getOpenApiContent = require('./helpers/get-openapi-content'); const mdxQuery = require('./mdx-query'); +const axios = require('axios').default; const parseHtml = require('./helpers/parse-html'); const parseMdx = require('./helpers/parse-mdx'); const createAlgoliaRecord = require('./create-record'); -const { getProductFromIndex } = require('./helpers/get-products-indexes'); function indexRecords() { return [ @@ -30,6 +30,22 @@ function indexRecords() { allFile: { nodes }, }, }) { + + let productIndexMap; + try { + const result = await axios.get("https://raw.githubusercontent.com/AdobeDocs/search-indices/main/product-index-map.json"); + productIndexMap = result.data; + } catch (error) { + console.error(`AIO: Failed fetching search index.\n${error}`) + process.exit(); + } + + const productFromPath = productIndexMap.find(prod => { + return prod.productIndices.some(index => { + return index.indexPathPrefix.includes(pathPrefix) + }) + }); + const markdownFiles = []; for (const node of nodes) { markdownFiles.push({ @@ -51,7 +67,7 @@ function indexRecords() { objectID: node.id, openAPISpec: node.childMdx.frontmatter.openAPISpec, pathPrefix: `${pathPrefix}/`, - product: getProductFromIndex(repository), + product: productFromPath.productName, size: node.size, slug: node.childMdx.slug, title: node.childMdx.frontmatter.title, diff --git a/packages/gatsby-theme-aio/package.json b/packages/gatsby-theme-aio/package.json index 8180529b9e..f0c6b0ef9b 100644 --- a/packages/gatsby-theme-aio/package.json +++ b/packages/gatsby-theme-aio/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/gatsby-theme-aio", - "version": "4.7.11-rc2", + "version": "4.7.11", "description": "The Adobe I/O theme for building markdown powered sites", "main": "index.js", "license": "Apache-2.0", diff --git a/packages/gatsby-theme-aio/src/components/Layout/index.js b/packages/gatsby-theme-aio/src/components/Layout/index.js index a7e5b9ce5f..addb88ec2c 100644 --- a/packages/gatsby-theme-aio/src/components/Layout/index.js +++ b/packages/gatsby-theme-aio/src/components/Layout/index.js @@ -117,6 +117,65 @@ const updatePageSrc = (type, frontMatter, setIsLoading) => { } }; +// Used to update the url in the browser +const setQueryStringParameter = (name, value) => { + const params = new URLSearchParams(window.location.search); + params.set(name, value); + window.history.replaceState({}, '', `${window.location.pathname}?${params}`); +}; + +/** + * @returns The query string from the URL + */ +export const getQueryString = () => { + const params = new URLSearchParams(window.location.search); + return params.toString(); +}; + +const searchIFrameSource = () => { + /** + * Returns expected origin based on the host + * @param {*} host The host + * @param {*} suffix A suffix to append + * @returns The expected origin + */ + const setExpectedOrigin = (host, suffix = '') => { + if (isDevEnvironment(host)) { + return `http://localhost:8000`; + } + else if (isStageEnvironment(host)) { + return `https://developer-stage.adobe.com${suffix}`; + } else { + return `https://developer.adobe.com${suffix}`; + } + }; + + /** + * Checks whether the current URL is a dev environment based on host value + * @param {*} host The host + * @returns True if the current URL is a dev environment, false otherwise + */ + function isDevEnvironment(host) { + return host.indexOf('localhost') >= 0; + } + + /** + * Checks whether the current URL is a stage environment based on host value + * @param {*} host The host + * @returns True if the current URL is a stage environment, false otherwise + */ + function isStageEnvironment(host) { + return host.indexOf('developer-stage') >= 0; + } + + + const src = isDevEnvironment(window.location.host) ? setExpectedOrigin(window.location.host) : `${setExpectedOrigin(window.location.host, '/search-frame')}`; + const queryString = new URLSearchParams(window.location.search); + return queryString && queryString.toString().length > 0 + ? `${src}?${queryString.toString()}` + : src; +}; + export default ({ children, pageContext, location }) => { const [ims, setIms] = useState(null); const [isLoadingIms, setIsLoadingIms] = useState(true); @@ -151,24 +210,6 @@ export default ({ children, pageContext, location }) => { } }, []); - // Set Search indexAll - useEffect(() => { - if (hasSearch) { - Axios.get("https://raw.githubusercontent.com/AdobeDocs/search-indices/main/product-index-map.json") - .then(result => { - const productIndexMap = result.data; - if (typeof productIndexMap === 'string') { - setIndexAll(JSON.parse(productIndexMap)); - } else if (Object.prototype.toString.call(productIndexMap) == '[object Array]') { // https://stackoverflow.com/a/12996879/15028986 - setIndexAll(productIndexMap); - } - }) - .catch(err => { - console.error(`AIO: Failed fetching search index.\n${err}`) - }) - } - }, []); - // Load all data once and pass it to the Provider const data = useStaticQuery( graphql` @@ -266,6 +307,7 @@ export default ({ children, pageContext, location }) => { const [showSearch, setShowSearch] = useState(false); const [showSideNav, setShowSideNav] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [loadSearchFrame, setLoadSearchFrame] = useState(false); // Show search if search param is set useEffect(() => { @@ -340,7 +382,27 @@ export default ({ children, pageContext, location }) => { updatePageSrc('openAPI', frontMatter, setIsLoading); updatePageSrc('frame', frontMatter, setIsLoading); + // Set Search indexAll + useEffect(() => { + if (hasSearch) { + Axios.get("https://raw.githubusercontent.com/AdobeDocs/search-indices/main/product-index-map.json") + .then(result => { + const productIndexMap = result.data; + if (typeof productIndexMap === 'string') { + setIndexAll(JSON.parse(productIndexMap)); + } else if (Object.prototype.toString.call(productIndexMap) == '[object Array]') { // https://stackoverflow.com/a/12996879/15028986 + setIndexAll(productIndexMap); + } + }) + .catch(err => { + console.error(`AIO: Failed fetching search index.\n${err}`) + }) + } + }, []); + + if (pathPrefix === "/search-frame") { + return ( <> @@ -469,6 +531,89 @@ export default ({ children, pageContext, location }) => { ); } + let searchPathNameCheck = ""; + + const searchFrameOnLoad = (counter = 0, loaded) => { + const renderedFrame = document.getElementById('searchIframe'); + + renderedFrame.contentWindow.postMessage(JSON.stringify({ localPathName: window.location.pathname }), '*'); + if (window.location.pathname !== '/') { + if (searchPathNameCheck !== window.location.pathname) { + // attempt to establish connection for 3 seconds then time out + if (counter > 30) { + // eslint-disable-next-line no-console + console.warn('Loading Search iFrame timed out'); + return; + } + window.setTimeout(() => { searchFrameOnLoad(renderedFrame, counter + 1, loaded); }, 100); + } + } + // Past this point we successfully passed the local pathname + // and received a confirmation from the iframe + if (!loaded) { + const queryString = getQueryString(); + if (queryString) { + // let searchIframeContainer = document.querySelector('div.nav-console-search-frame'); + // if (searchIframeContainer.length > 0) { + // searchIframeContainer.style.visibility = 'visible'; + // } + setShowSearch(true); + } + } + + loaded = true; + }; + + // Referenced https://stackoverflow.com/a/10444444/15028986 + const checkIframeLoaded = () => { + const renderedFrame = document.getElementById('searchIframe'); + // Get a handle to the iframe element + let iframeDoc; + try { + iframeDoc = renderedFrame.contentDocument; + // Check if loading is complete + if (iframeDoc.readyState === 'complete') { + renderedFrame.onload = () => { + searchFrameOnLoad(); + }; + // The loading is complete, call the function we want executed once the iframe is loaded + return; + } + } catch (error) { + window.setTimeout(checkIframeLoaded, 100); + } + + }; + + const onMessageReceivedFromIframe = (evt) => { + // const expectedOrigin = setExpectedOrigin(window.location.host); + // if (evt.origin !== expectedOrigin) return; + try { + const message = typeof evt.data === 'string' ? JSON.parse(evt.data) : evt.data; + if (message.query) { + setQueryStringParameter(SEARCH_PARAMS.query, message.query); + setQueryStringParameter(SEARCH_PARAMS.keywords, message.keywords); + setQueryStringParameter(SEARCH_PARAMS.index, message.index); + } else if (message.received) { + searchPathNameCheck = message.received; + } + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + + useEffect(() => { + window.addEventListener("message", onMessageReceivedFromIframe); + if (hasSearch) { + setLoadSearchFrame(true); + } + }, []); + + useEffect(() => { + checkIframeLoaded(); + }, [loadSearchFrame]) + return ( <> @@ -693,15 +838,21 @@ export default ({ children, pageContext, location }) => { - {hasSearch && showSearch && indexAll && ( - + {hasSearch && loadSearchFrame && ( + )}
{ window.history.replaceState({}, '', `${window.location.pathname}`); }; -const searchSuggestions = async (algolia, query, searchIndex, indexAll, existingIndices, setExistingIndices) => { +const searchSuggestions = async (algolia, query, indexPrefix, searchIndex, indexAll, existingIndices, setExistingIndices) => { const queries = []; let indexes; if (!existingIndices.length) { @@ -95,7 +95,7 @@ const searchSuggestions = async (algolia, query, searchIndex, indexAll, existing return await algolia.multipleQueries(queries); }; -const searchIndexes = async (algolia, query, selectedIndex, indexAll, existingIndices, setExistingIndices, keywords) => { +const searchIndexes = async (algolia, query, indexPrefix, selectedIndex, indexAll, existingIndices, setExistingIndices, keywords) => { let indexes; if (!existingIndices.length) { @@ -230,9 +230,9 @@ const Search = ({ algolia, indexAll, indexPrefix, showSearch, setShowSearch, sea if (searchQuery !== oldSearchQuery) { setIsLoading(true); - search = await searchIndexes(algolia, searchQuery, ['all'], indexAll, existingIndices, setExistingIndices, selectedKeywords); + search = await searchIndexes(algolia, searchQuery, indexPrefix, ['all'], indexAll, existingIndices, setExistingIndices, selectedKeywords); } else { - search = await searchIndexes(algolia, searchQuery, selectedIndex, indexAll, existingIndices, setExistingIndices, selectedKeywords); + search = await searchIndexes(algolia, searchQuery, indexPrefix, selectedIndex, indexAll, existingIndices, setExistingIndices, selectedKeywords); } const localProduct = searchIndex.filter((product) => product !== SEARCH_INDEX_ALL)[0]; @@ -295,7 +295,7 @@ const Search = ({ algolia, indexAll, indexPrefix, showSearch, setShowSearch, sea if (!localPathName.startsWith('/')) { localPathName = `/${localPathName}` } if (!localPathName.endsWith('/')) { localPathName = `${localPathName}/` } const localProduct = indexAll.find(product => product.productIndices.some(idx => { - return idx.indexPathPrefix.startsWith(message.localPathName); + return localPathName.startsWith(idx.indexPathPrefix); })); if (localProduct?.productName) { @@ -388,7 +388,8 @@ const Search = ({ algolia, indexAll, indexPrefix, showSearch, setShowSearch, sea if (key === 'Escape') { setShowSearch(false); clearQueryStringParameters(); - document.getElementById("aio-Search-close").focus() + const searchClose = document.getElementById("aio-Search-close"); + searchClose ? searchClose.focus() : ""; } }; @@ -405,7 +406,7 @@ const Search = ({ algolia, indexAll, indexPrefix, showSearch, setShowSearch, sea if (searchQuery.length && !searchResults.length) { setShowClear(true); - const suggestions = await searchSuggestions(algolia, searchQuery, searchIndex, indexAll, existingIndices, setExistingIndices); + const suggestions = await searchSuggestions(algolia, searchQuery, indexPrefix, searchIndex, indexAll, existingIndices, setExistingIndices); setSearchQueryCounter(searchQueryCounter + 1); console.log('Total search queries counted is:', searchQueryCounter); @@ -438,22 +439,15 @@ const Search = ({ algolia, indexAll, indexPrefix, showSearch, setShowSearch, sea useEffect(() => { if (suggestionsRef) { if (searchSuggestionResults.length > 0) { - suggestionsRef.current.querySelectorAll("a").forEach(link => { - link.target = "_top"; - }); - } - } - }, [searchSuggestionResults]) - - useEffect(() => { - if (searchResultsRef) { - if (searchResults.length > 0) { - searchResultsRef.current.querySelectorAll("a").forEach(link => { - link.target = "_top"; - }); + const allLinks = suggestionsRef.current.querySelectorAll("a"); + if (allLinks.length > 0) { + allLinks.forEach(link => { + link.target = "_top"; + }); + } } } - }, [searchResults]) + }, [searchSuggestionResults, searchResults]) } return ( diff --git a/yarn.lock b/yarn.lock index 4297bd6e21..1618491367 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,103 +33,7 @@ __metadata: languageName: node linkType: hard -"@adobe/gatsby-theme-aio@npm:*": - version: 4.7.10 - resolution: "@adobe/gatsby-theme-aio@npm:4.7.10" - dependencies: - "@adobe/focus-ring-polyfill": ^0.1.5 - "@adobe/gatsby-source-github-file-contributors": ^0.3.1 - "@adobe/prism-adobe": ^1.0.3 - "@emotion/react": ^11.10.4 - "@loadable/component": ^5.15.2 - "@mdx-js/mdx": 1.6.22 - "@mdx-js/react": 1.6.22 - "@spectrum-css/accordion": 3.0.24 - "@spectrum-css/actionbutton": 2.1.7 - "@spectrum-css/actiongroup": ^2.0.0 - "@spectrum-css/assetlist": 3.0.24 - "@spectrum-css/badge": ^1.0.20 - "@spectrum-css/breadcrumb": 5.0.0 - "@spectrum-css/button": 6.0.13 - "@spectrum-css/card": 5.0.0 - "@spectrum-css/checkbox": 3.1.3 - "@spectrum-css/dialog": 6.0.15 - "@spectrum-css/divider": 1.0.27 - "@spectrum-css/icon": 3.0.23 - "@spectrum-css/inlinealert": 4.0.13 - "@spectrum-css/link": 3.1.23 - "@spectrum-css/menu": 4.0.4 - "@spectrum-css/modal": 3.0.23 - "@spectrum-css/picker": 1.2.12 - "@spectrum-css/popover": 5.0.18 - "@spectrum-css/progresscircle": 1.0.23 - "@spectrum-css/search": 4.2.12 - "@spectrum-css/sidenav": 3.0.24 - "@spectrum-css/table": 4.0.19 - "@spectrum-css/tabs": 3.2.19 - "@spectrum-css/textfield": 3.2.4 - "@spectrum-css/tooltip": 3.1.18 - "@spectrum-css/typography": 4.0.20 - "@spectrum-css/underlay": 2.0.31 - "@spectrum-css/vars": 8.0.0 - "@spectrum-css/well": 3.0.22 - algolia-html-extractor: ^0.0.1 - algoliasearch: ^4.14.2 - await-exec: ^0.1.2 - builtin-status-codes: ^3.0.0 - classnames: ^2.3.2 - core-js: ^3.25.1 - gatsby-plugin-algolia: ^0.26.0 - gatsby-plugin-emotion: ^7.23.0 - gatsby-plugin-layout: ^3.23.0 - gatsby-plugin-mdx: ^3.20.0 - gatsby-plugin-mdx-embed: ^1.0.0 - gatsby-plugin-preact: ^6.23.0 - gatsby-plugin-react-helmet: ^5.23.0 - gatsby-plugin-sharp: ^4.23.0 - gatsby-remark-autolink-headers: ^5.23.0 - gatsby-remark-copy-linked-files: ^5.23.0 - gatsby-remark-images-remote: ^2.1.1 - gatsby-source-filesystem: ^4.23.0 - gatsby-transformer-remark: ^5.23.0 - gatsby-transformer-sharp: 4.23.0 - https-browserify: ^1.0.0 - jsdom: ^20.0.0 - lottie-web: ^5.9.6 - mdx-embed: ^1.0.0 - mobx: ^6.6.2 - normalize-path: ^3.0.0 - os-browserify: ^0.3.0 - path-browserify: ^1.0.1 - penpal: ^6.2.2 - postcss: ^8.4.16 - preact: ^10.11.0 - preact-render-to-string: ^5.2.4 - prism-react-renderer: 1.3.5 - prop-types: ^15.8.1 - react-helmet: ^6.1.0 - react-id-generator: ^3.0.2 - redoc: 2.0.0 - redoc-cli: ^0.13.20 - rehype-slug-custom-id: ^1.1.0 - request: ^2.88.2 - sharp: ^0.31.0 - stream-http: ^3.2.0 - styled-components: ^5.3.5 - swiper: ^8.3.2 - to-arraybuffer: ^1.0.1 - tty-browserify: ^0.0.1 - unist-util-select: 3.0.4 - uuid: ^9.0.0 - peerDependencies: - gatsby: ^4.22.0 - react: ^17.0.2 - react-dom: ^17.0.2 - checksum: 4b6c6a1413b20ab32262d1c71b756a2873cddb9969edc784ab0f9e51a16c8372dd608185549523019a83b4e5c8f2f299905598e29c0a48448434d497bced6cfd - languageName: node - linkType: hard - -"@adobe/gatsby-theme-aio@workspace:packages/gatsby-theme-aio": +"@adobe/gatsby-theme-aio@*, @adobe/gatsby-theme-aio@workspace:packages/gatsby-theme-aio": version: 0.0.0-use.local resolution: "@adobe/gatsby-theme-aio@workspace:packages/gatsby-theme-aio" dependencies: