From c8680159892366ab1dfd2a0e196db9d15b9c9cfd Mon Sep 17 00:00:00 2001 From: Piotr Janosz Date: Thu, 8 Aug 2019 21:55:16 +0200 Subject: [PATCH] Made docs scrollable to section when selecting a link --- packages/website/mdx/core-concepts/index.mdx | 2 - packages/website/package.json | 12 ++-- .../components/docs/search/autocomplete.tsx | 1 + packages/website/ts/pages/docs/page.tsx | 55 ++++++++++++++----- packages/website/ts/utils/algolia_helpers.ts | 10 ++-- 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/packages/website/mdx/core-concepts/index.mdx b/packages/website/mdx/core-concepts/index.mdx index c2f982e890..a19c8f31a2 100644 --- a/packages/website/mdx/core-concepts/index.mdx +++ b/packages/website/mdx/core-concepts/index.mdx @@ -8,8 +8,6 @@ export const meta = { 0x is a protocol that facilitates the peer-to-peer exchange of [Ethereum](TODO-Link-to-Ethereum-primer-section)-based assets. The protocol serves as an open standard and common building block for any developer needing exchange functionality. 0x provides secure [smart contracts](TODO-Link-to-smart-contract-section) that are externally audited; [developer tools](TODO-Link-to-Tools-explorer) tailored to the 0x ecosystem; and open access to a [pool of shared liquidity](TODO-Link-to-shared-liquidity-section). Developers can integrate with 0x at the smart contract or application layer. - - ### What can I build on 0x? Some examples of the types of things that can be built on 0x include: diff --git a/packages/website/package.json b/packages/website/package.json index 0a1760d9cd..d34913437e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -37,12 +37,6 @@ "@babel/preset-react": "^7.0.0", "@mdx-js/react": "^1.0.21", "@reach/tabs": "^0.1.6", - "@types/algoliasearch": "^3.30.12", - "@types/body-scroll-lock": "^2.6.0", - "@types/react-lazyload": "^2.3.1", - "@types/react-loadable": "^5.4.2", - "@types/react-syntax-highlighter": "^0.0.8", - "@types/styled-components": "4.1.1", "accounting": "^0.4.1", "algoliasearch": "^3.33.0", "babel-plugin-syntax-object-rest-spread": "^6.13.0", @@ -109,7 +103,9 @@ "@0x/tslint-config": "^3.0.1", "@mdx-js/loader": "^1.0.0-rc.4", "@types/accounting": "^0.4.1", + "@types/algoliasearch": "^3.30.12", "@types/blockies": "^0.0.0", + "@types/body-scroll-lock": "^2.6.0", "@types/deep-equal": "^1.0.0", "@types/find-versions": "^2.0.0", "@types/is-mobile": "0.3.0", @@ -124,12 +120,16 @@ "@types/react-copy-to-clipboard": "^4.2.0", "@types/react-dom": "^16.0.6", "@types/react-helmet": "^5.0.6", + "@types/react-lazyload": "^2.3.1", + "@types/react-loadable": "^5.4.2", "@types/react-redux": "^4.4.37", + "@types/react-router-dom": "^4.3.4", "@types/react-scroll": "1.5.3", "@types/react-syntax-highlighter": "^0.0.8", "@types/react-tap-event-plugin": "0.0.30", "@types/react-transition-group": "^4.2.0", "@types/redux": "^3.6.0", + "@types/styled-components": "4.1.1", "@types/valid-url": "^1.0.2", "@types/web3-provider-engine": "^14.0.0", "awesome-typescript-loader": "^5.2.1", diff --git a/packages/website/ts/components/docs/search/autocomplete.tsx b/packages/website/ts/components/docs/search/autocomplete.tsx index 3a73423fe7..75562f3c91 100644 --- a/packages/website/ts/components/docs/search/autocomplete.tsx +++ b/packages/website/ts/components/docs/search/autocomplete.tsx @@ -17,6 +17,7 @@ interface IHit { isCommunity?: boolean; isFeatured?: boolean; objectID: string; + sectionUrl: string; tags?: string[]; textContent: string; title: string; diff --git a/packages/website/ts/pages/docs/page.tsx b/packages/website/ts/pages/docs/page.tsx index b3830987a4..c43b4ea5d6 100644 --- a/packages/website/ts/pages/docs/page.tsx +++ b/packages/website/ts/pages/docs/page.tsx @@ -1,5 +1,6 @@ +import * as _ from 'lodash'; import * as React from 'react'; -import { match } from 'react-router-dom'; +import { RouteComponentProps } from 'react-router-dom'; import { MDXProvider } from '@mdx-js/react'; @@ -29,11 +30,9 @@ import { FullscreenMessage } from 'ts/pages/fullscreen_message'; import { Paragraph } from 'ts/components/text'; import { colors } from 'ts/style/colors'; +import { docs } from 'ts/style/docs'; -interface IDocsPageProps { - match: match; -} - +interface IDocsPageProps extends RouteComponentProps {} interface IDocsPageState { Component: React.ReactNode; contents: IContents[]; @@ -42,7 +41,7 @@ interface IDocsPageState { wasNotFound: boolean; } -export const DocsPage: React.FC = ({ match }) => { +export const DocsPage: React.FC = props => { const [state, setState] = React.useState({ Component: null, contents: [], @@ -53,14 +52,12 @@ export const DocsPage: React.FC = ({ match }) => { const { Component, contents, title, subtitle, wasNotFound } = state; const isLoading = !Component && !wasNotFound; - const { page, type } = match.params; + const { page, type } = props.match.params; + const { hash } = props.location; - React.useEffect( - () => { - void loadPageAsync(page, type); - }, - [page, type], - ); + React.useEffect(() => { + void loadPageAsync(page, type); + }, [page, type]); const loadPageAsync = async (fileName: string, dirName: string) => { try { @@ -73,11 +70,43 @@ export const DocsPage: React.FC = ({ match }) => { subtitle: component.meta.subtitle, title: component.meta.title, }); + + if (hash) { + await waitForImages(); // images will push down content when loading, so we wait... + scrollToHash(hash); // ...and then scroll to hash when ready not to push the content down + } } catch (error) { setState({ ...state, title: '404', wasNotFound: true }); } }; + const waitForImages = async () => { + const images = document.querySelectorAll('img'); + return Promise.all( + _.compact( + _.map(images, (img: HTMLImageElement) => { + if (!img.complete) { + return new Promise(resolve => { + img.addEventListener('load', () => resolve()); + }); + } + return false; + }), + ), + ); + }; + + const scrollToHash = (hash: string): void => { + const element = document.getElementById(hash.substring(1)); + if (element) { + const bodyRect = document.body.getBoundingClientRect(); + const elemRect = element.getBoundingClientRect(); + const elemOffset = elemRect.top - bodyRect.top; + const totalOffset = elemOffset - docs.headerOffset; + window.scrollTo(0, totalOffset); + } + }; + return ( {wasNotFound ? ( diff --git a/packages/website/ts/utils/algolia_helpers.ts b/packages/website/ts/utils/algolia_helpers.ts index 44bc28fe0f..7897f0d4f2 100644 --- a/packages/website/ts/utils/algolia_helpers.ts +++ b/packages/website/ts/utils/algolia_helpers.ts @@ -22,8 +22,8 @@ function processContentTree(tree: Node[], url: string, meta: Meta, index: any, s const formattedTextNodes = formatTextNodes(textNodes); const content = getContent(meta, url, formattedTextNodes); - // setIndexSettings(index, settings); - // pushObjectsToAlgolia(index, content); + setIndexSettings(index, settings); + pushObjectsToAlgolia(index, content); } } @@ -84,12 +84,11 @@ function getContent(meta: Meta, url: string, formattedTextNodes: FormattedNode[] formattedTextNodes.forEach((node: FormattedNode, index: number) => { const titleSlug = slugify(meta.title, { lower: true }); - const sectionUrl = node.hash ? `${url}#${node.hash}` : url; // If we have the hash, append it to url, if not - use the base url + const formattedUrl = node.hash ? `${url}#${node.hash}` : url; // If we have the hash, append it to url, if not - use the base url content.push({ ...meta, - url, - sectionUrl, + url: formattedUrl, textContent: node.textContent, id: titleSlug, objectID: `${titleSlug}_${index}`, @@ -168,7 +167,6 @@ interface Meta { interface Content extends Meta { url: string; - sectionUrl: string; textContent: string; id: string; objectID: string;