diff --git a/gatsby-config.js b/gatsby-config.js index 7f4982aec..ed0799f34 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -33,14 +33,14 @@ module.exports = { { resolve: 'gatsby-source-filesystem', options: { - path: `${__dirname}/src/pages`, name: 'pages', + path: `${__dirname}/src/pages`, }, }, { resolve: 'gatsby-source-filesystem', options: { - name: 'packages', + name: 'content', path: `${__dirname}/content/`, }, }, diff --git a/plugins/gatsby-transformer-home-example-code/gatsby-node.js b/plugins/gatsby-transformer-home-example-code/gatsby-node.js index e8d813313..5965a3ba7 100644 --- a/plugins/gatsby-transformer-home-example-code/gatsby-node.js +++ b/plugins/gatsby-transformer-home-example-code/gatsby-node.js @@ -1,28 +1,33 @@ -const {readdirSync, readFileSync} = require('fs'); -const {join, resolve} = require('path'); +const crypto = require(`crypto`); + +const createContentDigest = obj => + crypto + .createHash(`md5`) + .update(obj) + .digest(`hex`); // Store code snippets in GraphQL for the home page examples. // Snippets will be matched with markdown templates of the same name. -exports.sourceNodes = ({graphql, actions}) => { +exports.onCreateNode = async ({actions, node, loadNodeContent}) => { const {createNode} = actions; + const {absolutePath, ext, name, relativeDirectory, sourceInstanceName} = node; - const path = resolve(__dirname, '../../content/home/examples'); - const files = readdirSync(path); - - files.forEach(file => { - if (file.match(/\.js$/)) { - const code = readFileSync(join(path, file), 'utf8'); - const id = file.replace(/\.js$/, ''); - - createNode({ - id, - children: [], - parent: 'EXAMPLES', - internal: { - type: 'ExampleCode', - contentDigest: JSON.stringify(code), - }, - }); - } - }); + if ( + sourceInstanceName === 'content' && + relativeDirectory === 'home/examples' && + ext === '.js' + ) { + const code = await loadNodeContent(node); + createNode({ + id: name, + children: [], + parent: node.id, + code, + mdAbsolutePath: absolutePath.replace(/\.js$/, '.md'), + internal: { + type: 'ExampleCode', + contentDigest: createContentDigest(JSON.stringify(code)), + }, + }); + } }; diff --git a/src/components/CodeEditor/CodeEditor.js b/src/components/CodeEditor/CodeEditor.js index f5ef723a7..9c1aab34e 100644 --- a/src/components/CodeEditor/CodeEditor.js +++ b/src/components/CodeEditor/CodeEditor.js @@ -38,6 +38,12 @@ class CodeEditor extends Component { } } + UNSAFE_componentWillReceiveProps(nextProps) { + if (this.props.code !== nextProps.code) { + this.setState(this._updateState(nextProps.code)); + } + } + render() { const {children} = this.props; const { @@ -68,211 +74,169 @@ class CodeEditor extends Component {
- {children && ( -
- {children} -
- )} -
+ + Live JSX Editor + + +
+
+ +
+
+ {error && ( +
- - Live JSX Editor - - -
-
- -
-
- {error && ( -
-
- - Error - -
-
-                  {errorMessage}
-                
+ Error +
- )} - {!error && ( +
+                {errorMessage}
+              
+
+ )} + {!error && ( +
-
- Result -
-
+ Result
- )} -
+
+
+ )}
); diff --git a/src/components/CodeExample/CodeExample.js b/src/components/CodeExample/CodeExample.js new file mode 100644 index 000000000..31fe7e2c6 --- /dev/null +++ b/src/components/CodeExample/CodeExample.js @@ -0,0 +1,73 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +import {colors, media} from 'theme'; +import CodeEditor from '../CodeEditor/CodeEditor'; + +class CodeExample extends Component { + render() { + const {children, code, id, loaded} = this.props; + return ( +
+ {children && ( +
+ {children} +
+ )} + {loaded ? :

Loading code example...

} +
+ ); + } +} + +CodeExample.propTypes = { + children: PropTypes.node, + code: PropTypes.string.isRequired, + loaded: PropTypes.bool.isRequired, +}; + +export default CodeExample; diff --git a/src/components/CodeExample/index.js b/src/components/CodeExample/index.js new file mode 100644 index 000000000..6bff868c1 --- /dev/null +++ b/src/components/CodeExample/index.js @@ -0,0 +1,3 @@ +import CodeExample from './CodeExample'; + +export default CodeExample; diff --git a/src/pages/index.js b/src/pages/index.js index 480d8b0a9..786e37336 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -7,72 +7,46 @@ import ButtonLink from 'components/ButtonLink'; import Container from 'components/Container'; import Flex from 'components/Flex'; -import mountCodeExample from 'utils/mountCodeExample'; +import CodeExample from 'components/CodeExample'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {graphql} from 'gatsby'; import TitleAndMetaTags from 'components/TitleAndMetaTags'; import Layout from 'components/Layout'; import {colors, media, sharedStyles} from 'theme'; -import createOgUrl from 'utils/createOgUrl'; import loadScript from 'utils/loadScript'; +import createOgUrl from 'utils/createOgUrl'; import {babelURL} from 'site-constants'; import ReactDOM from 'react-dom'; import logoWhiteSvg from 'icons/logo-white.svg'; class Home extends Component { - constructor(props, context) { - super(props, context); - - const {data} = props; - - const code = data.code.edges.reduce((map, {node}) => { - map[node.id] = JSON.parse(node.internal.contentDigest); - - return map; - }, {}); - - const examples = data.examples.edges.map(({node}) => ({ - content: node.html, - id: node.fields.slug.replace(/^.+\//, '').replace('.html', ''), - title: node.frontmatter.title, - })); - - const marketing = data.marketing.edges.map(({node}) => ({ - title: node.frontmatter.title, - content: node.html, - })); - - this.state = { - code, - examples, - marketing, - }; - } + state = { + babelLoaded: false, + }; componentDidMount() { - const {code, examples} = this.state; - - examples.forEach(({id}) => { - renderExamplePlaceholder(id); - }); - - function mountCodeExamples() { - examples.forEach(({id}) => { - mountCodeExample(id, code[id]); - }); - } - - loadScript(babelURL).then(mountCodeExamples, error => { - console.error('Babel failed to load.'); - - mountCodeExamples(); - }); + loadScript(babelURL).then( + () => { + this.setState({ + babelLoaded: true, + }); + }, + error => { + console.error('Babel failed to load.'); + }, + ); } render() { - const {examples, marketing} = this.state; - const {location} = this.props; + const {babelLoaded} = this.state; + const {data, location} = this.props; + const {codeExamples, examples, marketing} = data; + + const code = codeExamples.edges.reduce((lookup, {node}) => { + lookup[node.mdAbsolutePath] = node; + return lookup; + }, {}); return ( @@ -217,7 +191,7 @@ class Home extends Component { whiteSpace: 'nowrap', }, }}> - {marketing.map((column, index) => ( + {marketing.edges.map(({node: column}, index) => (
- {column.title} + {column.frontmatter.title} -
+
))}
@@ -283,27 +257,19 @@ class Home extends Component { />
- {examples.map((example, index) => ( -
-

{example.title}

-
-
-
- ))} + {examples.edges.map(({node}, index) => { + const snippet = code[node.fileAbsolutePath]; + return ( + +

{node.frontmatter.title}

+
+ + ); + })}
@@ -339,7 +305,6 @@ class Home extends Component { Home.propTypes = { data: PropTypes.shape({ - code: PropTypes.object.isRequired, examples: PropTypes.object.isRequired, marketing: PropTypes.object.isRequired, }).isRequired, @@ -382,22 +347,23 @@ const CtaItem = ({children, primary = false}) => ( export const pageQuery = graphql` query IndexMarkdown { - code: allExampleCode { + codeExamples: allExampleCode { edges { node { id - internal { - contentDigest - } + code + mdAbsolutePath } } } + examples: allMarkdownRemark( filter: {fileAbsolutePath: {regex: "//home/examples//"}} sort: {fields: [frontmatter___order], order: ASC} ) { edges { node { + fileAbsolutePath fields { slug } diff --git a/src/utils/mountCodeExample.js b/src/utils/mountCodeExample.js deleted file mode 100644 index 41c35eef8..000000000 --- a/src/utils/mountCodeExample.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * @emails react-core - */ - -import CodeEditor from '../components/CodeEditor'; -import React from 'react'; -import ReactDOM from 'react-dom'; - -// TODO This is a huge hack. -// Remark transform this template to split code examples and their targets apart. -const mountCodeExample = (containerId, code) => { - const container = document.getElementById(containerId); - const parent = container.parentElement; - - const children = Array.prototype.filter.call( - parent.children, - child => child !== container, - ); - children.forEach(child => parent.removeChild(child)); - - const description = children - .map(child => child.outerHTML) - .join('') - .replace(/`([^`]+)`/g, '$1'); - - ReactDOM.render( - - {
} - , - container, - ); -}; - -export default mountCodeExample;