diff --git a/package.json b/package.json
index 56ba793821cd..be2ecf9d3efa 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
"clean-webpack-plugin": "^0.1.17",
"copy-webpack-plugin": "^4.3.0",
"css-loader": "^0.28.5",
- "directory-tree-webpack-plugin": "^0.2.0",
+ "directory-tree-webpack-plugin": "^0.3.1",
"duplexer": "^0.1.1",
"eslint": "4.5.0",
"eslint-loader": "^1.9.0",
diff --git a/src/components/Page/Page.jsx b/src/components/Page/Page.jsx
index bbbc13d7288a..657d7c4c55ca 100644
--- a/src/components/Page/Page.jsx
+++ b/src/components/Page/Page.jsx
@@ -11,8 +11,28 @@ import Gitter from '../Gitter/Gitter';
import './Page.scss';
class Page extends React.Component {
- state = {
- content: this.props.content instanceof Promise ? 'Loading...' : this.props.content
+ constructor(props) {
+ super(props);
+
+ const { content } = props;
+
+ this.state = {
+ content: content instanceof Promise ? 'Loading...' : content.default || content
+ };
+ }
+
+ componentDidMount() {
+ const { content } = this.props;
+
+ if (content instanceof Promise) {
+ content
+ .then(module => this.setState({
+ content: module.default || module
+ }))
+ .catch(error => this.setState({
+ content: 'Error loading content.'
+ }));
+ }
}
render() {
@@ -66,18 +86,6 @@ class Page extends React.Component {
);
}
-
- componentDidMount() {
- if ( this.props.content instanceof Promise ) {
- this.props.content
- .then(module => this.setState({
- content: module.default || module
- }))
- .catch(error => this.setState({
- content: 'Error loading content.'
- }));
- }
- }
}
export default Page;
diff --git a/src/components/Site/Site.jsx b/src/components/Site/Site.jsx
index ea37bec2fe37..290e5bf6509f 100644
--- a/src/components/Site/Site.jsx
+++ b/src/components/Site/Site.jsx
@@ -3,6 +3,9 @@ import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { hot as Hot } from 'react-hot-loader';
+// Import Utilities
+import { ExtractPages, ExtractSections } from '../../utilities/content-utils';
+
// Import Components
import NotificationBar from '../NotificationBar/NotificationBar';
import Navigation from '../Navigation/Navigation';
@@ -20,27 +23,8 @@ import '../../styles/index';
import '../../styles/icon.font.js';
import './Site.scss';
-// Load & Clean Up JSON Representation of `src/content`
-// TODO: Consider moving all or parts of this cleaning to a `DirectoryTreePlugin` option(s)
+// Load Content Tree
import Content from '../../_content.json';
-Content.children = Content.children
- .filter(item => item.name !== 'images')
- .map(item => {
- if ( item.type === 'directory' ) {
- return {
- ...item,
- children: item.children.sort((a, b) => {
- let group1 = (a.group || '').toLowerCase();
- let group2 = (b.group || '').toLowerCase();
-
- if (group1 < group2) return -1;
- if (group1 > group2) return 1;
- return a.sort - b.sort;
- })
- };
-
- } else return item;
- });
class Site extends React.Component {
state = {
@@ -50,8 +34,9 @@ class Site extends React.Component {
render() {
let { location } = this.props;
let { mobileSidebarOpen } = this.state;
- let sections = this._sections;
+ let sections = ExtractSections(Content);
let section = sections.find(({ url }) => location.pathname.startsWith(url));
+ let pages = ExtractPages(Content);
return (
@@ -89,7 +74,7 @@ class Site extends React.Component {
render={ props => (
- { this._pages.map(page => (
+ { pages.map(page => (
{
- return array.reduce((flat, item) => {
- return flat.concat(
- Array.isArray(item.children) ? this._flatten(item.children) : item
- );
- }, []);
- }
-
/**
* Strip any non-applicable properties
*
@@ -173,28 +144,6 @@ class Site extends React.Component {
children: children ? this._strip(children) : []
}));
}
-
- /**
- * Get top-level sections
- *
- * @return {array} - ...
- */
- get _sections() {
- return Content.children.filter(item => (
- item.type === 'directory'
- ));
- }
-
- /**
- * Get all markdown pages
- *
- * @return {array} - ...
- */
- get _pages() {
- return this._flatten(Content.children).filter(item => {
- return item.extension === '.md';
- });
- }
}
export default Hot(module)(Site);
diff --git a/src/components/Splash/Splash.jsx b/src/components/Splash/Splash.jsx
index fcdeefbba718..a3720b571c0c 100644
--- a/src/components/Splash/Splash.jsx
+++ b/src/components/Splash/Splash.jsx
@@ -7,8 +7,8 @@ import SplashViz from '../SplashViz/SplashViz';
import Markdown from '../Markdown/Markdown';
import Support from '../Support/Support';
-// Import Content
-import Content from '../../content/index.md';
+// Import Demo Content
+import SplashContent from '../../content/index.md';
// Load Styling
import './Splash.scss';
@@ -21,7 +21,7 @@ const Splash = () => (
diff --git a/src/index.jsx b/src/index.jsx
index fc205186f3a7..abdb860d91cd 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -1,22 +1,45 @@
+// Import External Dependencies
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
+
+// Import Components
import Site from './components/Site/Site';
+// Import Utilities
+import { FindInContent } from './utilities/content-utils';
+
+// Import Content Tree
+import Content from './_content.json';
+
// TODO: Re-integrate
// Consider `react-g-analytics` package
+const render = process.NODE_ENV === 'production' ? ReactDOM.hydrate : ReactDOM.render;
+
// Client Side Rendering
if ( window.document !== undefined ) {
- ReactDOM.render((
-
- (
- import(`./content/${path}`) } />
- )} />
-
- ), document.getElementById('root'));
+ let { pathname } = window.location;
+ let trimmed = pathname.replace(/(.+)\/$/, '$1');
+ let entryPage = FindInContent(Content, item => item.url === trimmed);
+ let entryPath = entryPage.path.replace('src/content/', '');
+
+ import(`./content/${entryPath}`).then(entryModule => {
+ render((
+
+ (
+ {
+ if ( path === entryPath ) {
+ return entryModule.default || entryModule;
+
+ } else return import(`./content/${path}`);
+ }} />
+ )} />
+
+ ), document.getElementById('root'));
+ });
}
diff --git a/src/server.jsx b/src/server.jsx
index 582cc85ba114..035baa9e8b1d 100644
--- a/src/server.jsx
+++ b/src/server.jsx
@@ -16,11 +16,7 @@ const bundles = [
'/index.bundle.js'
];
-// Export method for `StaticSiteGeneratorPlugin`
-// CONSIDER: How high can we mount `Site` into the DOM hierarchy? If
-// we could start at ``, much of this could be moved to the `Site`
-// component itself (allowing easier utilization of page data for title,
-// description, etc).
+// Export method for `SSGPlugin`
export default locals => {
let { assets } = locals.webpackStats.compilation;
diff --git a/src/utilities/content-utils.js b/src/utilities/content-utils.js
new file mode 100644
index 000000000000..2bae423d4d22
--- /dev/null
+++ b/src/utilities/content-utils.js
@@ -0,0 +1,65 @@
+/**
+ * Walk the given tree of content
+ *
+ * @param {object} tree - Any node in the content tree
+ * @param {function} callback - Run on every descendant as well as the given `tree`
+ */
+export const WalkContent = (tree, callback) => {
+ callback(tree);
+
+ if ( tree.children ) {
+ tree.children.forEach(child => {
+ WalkContent(child, callback);
+ });
+ }
+};
+
+/**
+ * Deep flatten the given `tree`s child nodes
+ *
+ * @param {object} tree - Any node in the content tree
+ * @return {array} - A flattened list of leaf node descendants
+ */
+export const FlattenContent = tree => {
+ if ( tree.children ) {
+ return tree.children.reduce((flat, item) => {
+ return flat.concat(
+ Array.isArray(item.children) ? FlattenContent(item) : item
+ );
+ }, []);
+
+ } else return [];
+};
+
+/**
+ * Find an item within the given `tree`
+ *
+ * @param {object} tree - Any node in the content tree
+ * @param {function} test - A callback to find any leaf node in the given `tree`
+ * @return {object} - The first leaf node that passes the `test`
+ */
+export const FindInContent = (tree, test) => {
+ let list = FlattenContent(tree);
+
+ return list.find(test);
+};
+
+/**
+ * Get top-level sections
+ *
+ * @param {object} tree - Any node in the content tree
+ * @return {array} - Immediate children of the given `tree` that are directories
+ */
+export const ExtractSections = tree => {
+ return tree.children.filter(item => item.type === 'directory');
+};
+
+/**
+ * Get all markdown pages
+ *
+ * @param {object} tree - Any node in the content tree
+ * @return {array} - All markdown descendants of the given `tree`
+ */
+export const ExtractPages = tree => {
+ return FlattenContent(tree).filter(item => item.extension === '.md');
+};
diff --git a/webpack.common.js b/webpack.common.js
index 10e06cc6bd2a..a1bf3263581e 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -109,7 +109,17 @@ module.exports = (env = {}) => ({
dir: 'src/content',
path: 'src/_content.json',
extensions: /\.md/,
- enhance: treePluginEnhacer
+ enhance: treePluginEnhacer,
+ filter: item => item.name !== 'images',
+ sort: (a, b) => {
+ let group1 = (a.group || '').toLowerCase();
+ let group2 = (b.group || '').toLowerCase();
+
+ if (group1 < group2) return -1;
+ if (group1 > group2) return 1;
+ if (a.sort && b.sort) return a.sort - b.sort;
+ else return 0;
+ }
})
],
stats: {