WIP: Add userhelp to new site (#216)
* Docs/userguide switching * Initial commit of userdocs merge * Remove service worker, fix rendering * Remove limited sources * UX improvements per Paul design * Tweak version select for FF * Fix mobile view * Final tweaks to UI * Tweaks to search bar, clean up conflicts * Fix icons * Hide search if no API key
This commit is contained in:
parent
fd6dcf27be
commit
8ffc3eeb5b
30
README.md
30
README.md
|
@ -2,8 +2,9 @@
|
||||||
|
|
||||||
# doc.silverstripe.org
|
# doc.silverstripe.org
|
||||||
|
|
||||||
This repository contains the source code powering [SilverStripe's
|
This repository contains the source code powering [the Silverstripe CMS
|
||||||
developer documentation website](https://docs.silverstripe.org).
|
developer documentation website](https://docs.silverstripe.org) and
|
||||||
|
[userhelp website](https://userhelp.silverstripe.org).
|
||||||
|
|
||||||
This application is build on [Gatsby](https://gatsbyjs.com), a static
|
This application is build on [Gatsby](https://gatsbyjs.com), a static
|
||||||
site generator based on [React](https://reactjs.org). It sources content
|
site generator based on [React](https://reactjs.org). It sources content
|
||||||
|
@ -61,6 +62,20 @@ gatsby serve
|
||||||
These commands will give you an exact representation of how the site will run on a production server, with
|
These commands will give you an exact representation of how the site will run on a production server, with
|
||||||
statically generated html files and server-side rendering.
|
statically generated html files and server-side rendering.
|
||||||
|
|
||||||
|
## Toggling between docs and userhelp
|
||||||
|
|
||||||
|
Whether the application uses the `docs.silverstripe.org` content or `userhelp.silverstripe.org` is determined
|
||||||
|
by the environment variable, `DOCS_CONTEXT`. You can set this in the `.env.development` file, or use one of
|
||||||
|
the script shortcuts:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn dev-docs
|
||||||
|
yarn dev-user
|
||||||
|
yarn build-docs
|
||||||
|
yarn build-user
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
From within `path/to/ssdocs`, run the command
|
From within `path/to/ssdocs`, run the command
|
||||||
|
@ -85,7 +100,7 @@ your content changes, since the remote repositories are the source of truth.
|
||||||
## Deploying content changes
|
## Deploying content changes
|
||||||
|
|
||||||
Once your contribution has been merged into the master branch, it will be deployed to production via a
|
Once your contribution has been merged into the master branch, it will be deployed to production via a
|
||||||
Github action in the `silverstripe-framework` repository.
|
Github action in the repository that holds the markdown files (e.g. `silverstripe/silverstripe-framework` for docs).
|
||||||
|
|
||||||
## Deploying app changes
|
## Deploying app changes
|
||||||
|
|
||||||
|
@ -93,11 +108,12 @@ Once your change is merged in to the `master` branch of this repository, it will
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
To contribute an improvement to the https://docs.silverstripe.org functionality or
|
To contribute an improvement to the https://docs.silverstripe.org or https://userhelp.silverstripe.org functionality or
|
||||||
theme, submit a pull request on the [GitHub project](https://github.com/silverstripe/doc.silverstripe.org). Any approved pull requests will make
|
theme, submit a pull request on the [GitHub project](https://github.com/silverstripe/doc.silverstripe.org). Any approved pull requests will make
|
||||||
their way onto the https://docs.silverstripe.org site in the next release.
|
their way onto the https://docs.silverstripe.org or https://userhelp.silverstripe.org sites in the next release.
|
||||||
|
|
||||||
If you wish to edit the documentation content, submit a pull request
|
If you wish to edit the documentation content, submit a pull request
|
||||||
on the
|
on the
|
||||||
[framework Github project](https://github.com/silverstripe/silverstripe-framework). Updated
|
[framework Github project](https://github.com/silverstripe/silverstripe-framework) or the
|
||||||
documentation content is uploaded daily to [doc.silverstripe.org](https://docs.silverstripe.org) via a build hook.
|
[userhelp documentation](https://github.com/silverstripe/silverstripe-userhelp-content) repository
|
||||||
|
or corresponding module.
|
|
@ -2,22 +2,39 @@ require("prismjs/themes/prism-okaidia.css");
|
||||||
require("./src/theme/assets/scss/theme.scss");
|
require("./src/theme/assets/scss/theme.scss");
|
||||||
require('./src/theme/assets/fontawesome/css/all.css');
|
require('./src/theme/assets/fontawesome/css/all.css');
|
||||||
require('./src/theme/assets/search/algolia.css');
|
require('./src/theme/assets/search/algolia.css');
|
||||||
const smoothScroll = require('smooth-scroll');
|
|
||||||
const Layout = require('./src/components/Layout').default;
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const Layout = require('./src/components/Layout').default;
|
||||||
|
const NodeProvider = require('./src/components/NodeProvider').default;
|
||||||
|
const smoothScroll = require('smooth-scroll');
|
||||||
|
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
smoothScroll('a[href*="#"]')
|
smoothScroll('a[href*="#"]')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the chrome doesn't rerender every page load, which makes the sidebar reset its scroll.
|
||||||
|
*/
|
||||||
exports.wrapPageElement = ({ element, props }) => {
|
exports.wrapPageElement = ({ element, props }) => {
|
||||||
return <Layout {...props}>{element}</Layout>
|
return (
|
||||||
|
<NodeProvider {...props}>
|
||||||
|
<Layout {...props}>
|
||||||
|
{element}
|
||||||
|
</Layout>
|
||||||
|
</NodeProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.onRouteUpdate = ({location}) => {
|
exports.onRouteUpdate = ({location}) => {
|
||||||
|
if (window.ga && process.env.NODE_ENV === 'production') {
|
||||||
|
window.ga('send', 'pageview');
|
||||||
|
}
|
||||||
anchorScroll(location);
|
anchorScroll(location);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.shouldUpdateScroll = ({
|
exports.shouldUpdateScroll = ({
|
||||||
routerProps: { location },
|
routerProps: { location },
|
||||||
}) => {
|
}) => {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const sources = process.env.DOCS_CONTEXT === 'user'
|
||||||
|
? require('./sources-user')
|
||||||
|
: require('./sources-docs');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
siteMetadata: {
|
siteMetadata: {
|
||||||
|
@ -14,36 +17,14 @@ module.exports = {
|
||||||
`gatsby-plugin-sharp`,
|
`gatsby-plugin-sharp`,
|
||||||
`gatsby-plugin-sitemap`,
|
`gatsby-plugin-sitemap`,
|
||||||
`gatsby-plugin-netlify`,
|
`gatsby-plugin-netlify`,
|
||||||
{
|
|
||||||
resolve: `gatsby-source-git`,
|
...sources,
|
||||||
options: {
|
|
||||||
name: `4`,
|
|
||||||
remote: `https://github.com/silverstripe/silverstripe-framework.git`,
|
|
||||||
branch: `4`,
|
|
||||||
patterns: `docs/en/**`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resolve: `gatsby-source-git`,
|
|
||||||
options: {
|
|
||||||
name: `3`,
|
|
||||||
remote: `https://github.com/silverstripe/silverstripe-framework.git`,
|
|
||||||
branch: `3.7`,
|
|
||||||
patterns: `docs/en/**`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
resolve: `gatsby-source-filesystem`,
|
resolve: `gatsby-source-filesystem`,
|
||||||
options: {
|
options: {
|
||||||
name: `watcher--ss3`,
|
name: `watcher`,
|
||||||
path: `${__dirname}/.cache/gatsby-source-git/3/docs/en`
|
path: `${__dirname}/.cache/gatsby-source-git/`
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resolve: `gatsby-source-filesystem`,
|
|
||||||
options: {
|
|
||||||
name: `watcher--ss4`,
|
|
||||||
path: `${__dirname}/.cache/gatsby-source-git/4/docs/en`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -108,7 +89,7 @@ module.exports = {
|
||||||
// allowed selectors defined in FontAwesome. Everything else in FA should be removed.
|
// allowed selectors defined in FontAwesome. Everything else in FA should be removed.
|
||||||
extractor: class {
|
extractor: class {
|
||||||
static extract(content) {
|
static extract(content) {
|
||||||
const selectors = [`file-alt`]
|
const selectors = [`fa-file-alt`]
|
||||||
const matches = content.match(/icon(Brand)?: ([a-zA-Z0-9_-]+)/);
|
const matches = content.match(/icon(Brand)?: ([a-zA-Z0-9_-]+)/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const isBrand = typeof matches[1] !== 'undefined';
|
const isBrand = typeof matches[1] !== 'undefined';
|
||||||
|
|
|
@ -3,31 +3,41 @@ const fs = require('fs');
|
||||||
const { createFilePath } = require(`gatsby-source-filesystem`);
|
const { createFilePath } = require(`gatsby-source-filesystem`);
|
||||||
const fileToTitle = require('./src/utils/fileToTitle');
|
const fileToTitle = require('./src/utils/fileToTitle');
|
||||||
|
|
||||||
const createSlug = (filePath, version) => {
|
const createSlug = ({path, version, thirdparty}) => {
|
||||||
const parts = filePath.split('/');
|
const rest = path.split('/');
|
||||||
const langIndex = parts.indexOf('en');
|
const parts = [
|
||||||
parts.splice(langIndex + 1, 0, version);
|
'en',
|
||||||
return parts
|
version,
|
||||||
.map(part => part.replace(/^\d+_/, ''))
|
// thirdparty modules are explicitly pathed
|
||||||
.join('/')
|
thirdparty,
|
||||||
.toLowerCase()
|
...rest,
|
||||||
|
].filter(p => p);
|
||||||
|
|
||||||
|
const slug = parts
|
||||||
|
.map(part => part.replace(/^\d+_/, ''))
|
||||||
|
.join('/')
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
return `/${slug}/`;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.onCreateNode = async ({ node, getNode, getNodesByType, actions, createNodeId, createContentDigest }) => {
|
const parseName = name => name.split('--');
|
||||||
|
|
||||||
|
exports.onCreateNode = async ({ node, getNode, getNodesByType, actions, createNodeId, createContentDigest }) => {
|
||||||
if (node.internal.type !== 'MarkdownRemark') {
|
if (node.internal.type !== 'MarkdownRemark') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { createNode } = actions;
|
const { createNode } = actions;
|
||||||
const fileNode = getNode(node.parent);
|
const fileNode = getNode(node.parent);
|
||||||
const version = fileNode.sourceInstanceName;
|
const [category, version, thirdparty] = parseName(fileNode.sourceInstanceName);
|
||||||
|
|
||||||
// The gatsby-source-filesystem plugins are registered to collect from the same path
|
// The gatsby-sgatsbource-filesystem plugins are registered to collect from the same path
|
||||||
// that the git source writes to, so we get the watch task (hot reload on content changes)
|
// that the git source writes to, so we get the watch task (hot reload on content changes)
|
||||||
// But we don't want duplicate document pages for each source plugin, so
|
// But we don't want duplicate document pages for each source plugin, so
|
||||||
// we bail out if we already have the file. However, we need to ensure
|
// we bail out if we already have the file. However, we need to ensure
|
||||||
// the file is injected into the template as a dependency, so when the content changes,
|
// the file is injected into the template as a dependency, so when the content changes,
|
||||||
// the pages get refreshed on the fly.
|
// the pages get refreshed on the fly.
|
||||||
if (version.match(/^watcher--/)) {
|
if (category === 'watcher') {
|
||||||
const existing = getNodesByType('SilverstripeDocument')
|
const existing = getNodesByType('SilverstripeDocument')
|
||||||
.find(n => n.fileAbsolutePath === node.fileAbsolutePath);
|
.find(n => n.fileAbsolutePath === node.fileAbsolutePath);
|
||||||
|
|
||||||
|
@ -35,14 +45,14 @@ exports.onCreateNode = async ({ node, getNode, getNodesByType, actions, createNo
|
||||||
// Pair the document with its watched file so we can inject it into the template
|
// Pair the document with its watched file so we can inject it into the template
|
||||||
// as a dependency.
|
// as a dependency.
|
||||||
existing.watchFile___NODE = node.id;
|
existing.watchFile___NODE = node.id;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
const basePath = category === 'user' ? `docs/en/userguide` : `docs/en`;
|
||||||
const filePath = createFilePath({
|
const filePath = createFilePath({
|
||||||
node,
|
node,
|
||||||
getNode,
|
getNode,
|
||||||
basePath: `docs`
|
basePath,
|
||||||
});
|
});
|
||||||
let fileTitle = path.basename(node.fileAbsolutePath, '.md');
|
let fileTitle = path.basename(node.fileAbsolutePath, '.md');
|
||||||
const isIndex = fileTitle === 'index';
|
const isIndex = fileTitle === 'index';
|
||||||
|
@ -50,10 +60,24 @@ exports.onCreateNode = async ({ node, getNode, getNodesByType, actions, createNo
|
||||||
fileTitle = path.basename(path.dirname(node.fileAbsolutePath));
|
fileTitle = path.basename(path.dirname(node.fileAbsolutePath));
|
||||||
}
|
}
|
||||||
const docTitle = fileToTitle(fileTitle);
|
const docTitle = fileToTitle(fileTitle);
|
||||||
const slug = createSlug(filePath, version);
|
const slug = createSlug({
|
||||||
|
path: filePath,
|
||||||
|
version,
|
||||||
|
thirdparty,
|
||||||
|
});
|
||||||
|
|
||||||
const parentSlug = `${path.resolve(slug, '../')}/`;
|
const parentSlug = `${path.resolve(slug, '../')}/`;
|
||||||
const unhideSelf = false;
|
const unhideSelf = false;
|
||||||
|
|
||||||
|
// Most of these don't exist in userhelp, so force them into the schema by un-nulling them.
|
||||||
|
const frontmatter = {
|
||||||
|
introduction: ``,
|
||||||
|
icon: `file-alt`,
|
||||||
|
iconBrand: ``,
|
||||||
|
hideChildren: false,
|
||||||
|
...node.frontmatter,
|
||||||
|
};
|
||||||
|
|
||||||
const docData = {
|
const docData = {
|
||||||
isIndex,
|
isIndex,
|
||||||
filePath,
|
filePath,
|
||||||
|
@ -61,7 +85,8 @@ exports.onCreateNode = async ({ node, getNode, getNodesByType, actions, createNo
|
||||||
slug,
|
slug,
|
||||||
parentSlug,
|
parentSlug,
|
||||||
unhideSelf,
|
unhideSelf,
|
||||||
...node.frontmatter,
|
category,
|
||||||
|
...frontmatter,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!docData.title || docData.title === '') {
|
if (!docData.title || docData.title === '') {
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
const Layout = require('./src/components/Layout').default;
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const Layout = require('./src/components/Layout').default;
|
||||||
|
const NodeProvider = require('./src/components/NodeProvider').default;
|
||||||
|
const { setCurrentPath } = require('./src/utils/nodes');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the node provider (static query of all documents)
|
||||||
|
* Ensures the chrome doesn't rerender every page load, which makes the sidebar reset its scroll.
|
||||||
|
*/
|
||||||
exports.wrapPageElement = ({ element, props }) => {
|
exports.wrapPageElement = ({ element, props }) => {
|
||||||
return <Layout {...props}>{element}</Layout>
|
return (
|
||||||
|
<NodeProvider {...props}>
|
||||||
|
<Layout {...props}>
|
||||||
|
{element}
|
||||||
|
</Layout>
|
||||||
|
</NodeProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.onRenderBody = ({ setPostBodyComponents, setHeadComponents }) => {
|
exports.onRenderBody = ({ setPostBodyComponents, setHeadComponents, pathname }) => {
|
||||||
// Rules that cannot be touched by purgecss because they come in from client side rendering
|
// Rules that cannot be touched by purgecss because they come in from client side rendering
|
||||||
setHeadComponents([
|
setHeadComponents([
|
||||||
<style key='prism-css' type="text/css" dangerouslySetInnerHTML={{
|
<style key='prism-css' type="text/css" dangerouslySetInnerHTML={{
|
||||||
__html: `
|
__html: `
|
||||||
:not(pre) > code[class*="language-"] {
|
:not(pre) > code[class*="language-"] {
|
||||||
|
|
|
@ -74,7 +74,11 @@
|
||||||
"start": "npm run develop",
|
"start": "npm run develop",
|
||||||
"serve": "gatsby serve",
|
"serve": "gatsby serve",
|
||||||
"type-check": "tsc --noEmit",
|
"type-check": "tsc --noEmit",
|
||||||
"lint": "tslint --project ."
|
"lint": "tslint --project .",
|
||||||
|
"dev-docs": "DOCS_CONTEXT=docs gatsby develop",
|
||||||
|
"dev-user": "DOCS_CONTEXT=user gatsby develop",
|
||||||
|
"build-docs": "DOCS_CONTEXT=docs gatsby build",
|
||||||
|
"build-user": "DOCS_CONTEXT=user gatsby build"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `docs--4`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-framework.git`,
|
||||||
|
branch: `4`,
|
||||||
|
patterns: `docs/en/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `docs--3`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-framework.git`,
|
||||||
|
branch: `3.7`,
|
||||||
|
patterns: `docs/en/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,447 @@
|
||||||
|
module.exports = [
|
||||||
|
/******* main content *********/
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-userhelp-content.git`,
|
||||||
|
//branch: `4`,
|
||||||
|
branch: `pulls/4/new-docs`,
|
||||||
|
patterns: `docs/en/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-userhelp-content.git`,
|
||||||
|
//branch: `3`,
|
||||||
|
branch: `pulls/3/new-docs`,
|
||||||
|
patterns: `docs/en/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* versionedfiles ********/
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
// Running a fork. Switch remote back once merged.
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/versionedfiles`,
|
||||||
|
//remote: `https://github.com/silverstripe-australia/silverstripe-versionedfiles`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-versionedfiles`,
|
||||||
|
// branch: `master`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* advancedworkflow ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
// Running a fork. Switch remote back once merged.
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/setting_up_advancedworkflow`,
|
||||||
|
//remote: `https://github.com/symbiote/silverstripe-advancedworkflow`,
|
||||||
|
//branch: `4`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-advancedworkflow`,
|
||||||
|
branch: `pulls/4/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/setting_up_advancedworkflow`,
|
||||||
|
// remote: `https://github.com/symbiote/silverstripe-advancedworkflow`,
|
||||||
|
// branch: `master`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-advancedworkflow`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* registry ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/online_databases_and_registries`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-registry`,
|
||||||
|
//branch: `1.0`,
|
||||||
|
branch: `pulls/1.0/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/online_databases_and_registries`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-registry`,
|
||||||
|
// branch: `master`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* forum ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
// Fork. Remove once PR is merged.
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/forums`,
|
||||||
|
remote: `https://github.com/unclecheese/silverstripe-forum`,
|
||||||
|
//branch: `0.8`,
|
||||||
|
branch: `pulls/0.8/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* contentreview ********/
|
||||||
|
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/content_review`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-contentreview`,
|
||||||
|
// branch: `master`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* blog ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/blogs`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-blog`,
|
||||||
|
//branch: `2`,
|
||||||
|
branch: `pulls/2/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/blogs`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-blog`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* userforms ********/
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/forms`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-userforms`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* translatable ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/working_with_translations`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-translatable`,
|
||||||
|
//branch: `2.1`,
|
||||||
|
branch: `pulls/2.1/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* subsites ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/working_with_multiple_websites`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-subsites`,
|
||||||
|
//branch: `1.1`,
|
||||||
|
branch: `pulls/1.1/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/working_with_multiple_websites`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-subsites`,
|
||||||
|
//branch: `1.1`,
|
||||||
|
branch: `pulls/1.1/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* secureassets ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/securing_files`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-secureassets`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* taxonomy ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/taxonomies`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-taxonomy`,
|
||||||
|
branch: `pulls/1/new-docs`,
|
||||||
|
// branch: `1`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/taxonomies`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-taxonomy`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* iframe ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/iframe`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-iframe`,
|
||||||
|
//branch: `1.0`,
|
||||||
|
branch: `pulls/1.0/new-docs`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/iframe`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-iframe`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* versionfeed ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/site_wide_rss_feeds`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-versionfeed`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/site_wide_rss_feeds`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-versionfeed`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* dms ********/
|
||||||
|
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--optional_features/document_management_system`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-dms`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* elemental ********/
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
// Fork. Replace when PR is merged.
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/content_blocks`,
|
||||||
|
remote: `https://github.com/dnadesign/silverstripe-elemental`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* maintenance ********/
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
// Fork. Replace when PR is merged.
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/modules_report`,
|
||||||
|
//remote: `https://github.com/bringyourownideas/silverstripe-maintenance`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-maintenance`,
|
||||||
|
branch: `pulls/1/new-docs`,
|
||||||
|
// branch: `1`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* sharedraftcontent ********/
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/share_draft_content`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-sharedraftcontent`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* documentconverter ********/
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/document_converter`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-documentconverter`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* ckan-registry ********/
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/ckan_registry`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-ckan-registry`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******* mfa ********/
|
||||||
|
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--optional_features/multi-factor_authentication`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-mfa`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
// branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******** securityreport ********/
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--managing_your_website/reports/security_report`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-securityreport`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
//branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--managing_your_website/reports/security_report`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-securityreport`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
//branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/******** sitewide-content-report ********/
|
||||||
|
// v4
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--4--managing_your_website/reports/sitewide_content_report`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-sitewidecontent-report`,
|
||||||
|
branch: `pulls/master/new-docs`,
|
||||||
|
//branch: `master`,
|
||||||
|
patterns: `docs/en/userguide/**`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// v3
|
||||||
|
{
|
||||||
|
resolve: `gatsby-source-git`,
|
||||||
|
options: {
|
||||||
|
name: `user--3--managing_your_website/reports/sitewide_content_report`,
|
||||||
|
remote: `https://github.com/silverstripe/silverstripe-sitewidecontent-report`,
|
||||||
|
branch: `pulls/2.0/new-docs`,
|
||||||
|
//branch: `2.0`,
|
||||||
|
patterns: `docs/en/userguide/**`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React, { StatelessComponent } from 'react';
|
||||||
|
import SEO from './SEO';
|
||||||
|
import parseHTML from '../utils/parseHTML';
|
||||||
|
|
||||||
|
interface DocsPageProps {
|
||||||
|
title: string;
|
||||||
|
html: string;
|
||||||
|
relPath: string;
|
||||||
|
branch: string;
|
||||||
|
gitURL: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DocsPage: StatelessComponent<DocsPageProps> = ({ title, html, branch, relPath, gitURL }): ReactElement => {
|
||||||
|
const editLink = `${gitURL.replace(/\.git$/, '')}/edit/${branch}/${relPath}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO title={title} />
|
||||||
|
{parseHTML(html)}
|
||||||
|
{editLink &&
|
||||||
|
<div className="github-edit">
|
||||||
|
<a target="_blank" href={editLink} title="Edit on Github">
|
||||||
|
<i className="fas fa-pen fa-fw" />{` `}
|
||||||
|
Edit on Github
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocsPage;
|
|
@ -2,60 +2,68 @@ import React, { StatelessComponent, ReactElement } from 'react';
|
||||||
import SearchBox from './SearchBox';
|
import SearchBox from './SearchBox';
|
||||||
import { Link, navigate } from 'gatsby';
|
import { Link, navigate } from 'gatsby';
|
||||||
import logo from '../images/silverstripe-logo.svg';
|
import logo from '../images/silverstripe-logo.svg';
|
||||||
import { getNodes, getHomePage, getCurrentNode, getCurrentVersion } from '../utils/nodes';
|
import useDocContext from '../hooks/useDocContext';
|
||||||
|
import useHierarchy from '../hooks/useHierarchy';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
handleSidebarToggle(e: EventTarget): void
|
handleSidebarToggle(e: EventTarget): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNavigate = (e: any): void => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentNode = getCurrentNode();
|
|
||||||
const ver = e.target.value;
|
|
||||||
|
|
||||||
if (currentNode) {
|
|
||||||
const newPath = currentNode.slug.replace(/^\/en\/[0-9]+\//, `/en/${ver}/`);
|
|
||||||
const otherNode = getNodes().find(n => n.slug === newPath);
|
|
||||||
// This has to be a hard refresh, because the sidebar needs to unmount
|
|
||||||
if (otherNode) {
|
|
||||||
window.location.href = otherNode.slug;
|
|
||||||
} else {
|
|
||||||
window.location.href = `/en/${ver}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Header: StatelessComponent<HeaderProps> = ({ handleSidebarToggle }): ReactElement => {
|
const Header: StatelessComponent<HeaderProps> = ({ handleSidebarToggle }): ReactElement => {
|
||||||
|
const { getNodes, getHomePage, getCurrentNode, getCurrentVersion } = useHierarchy();
|
||||||
const home = getHomePage();
|
const home = getHomePage();
|
||||||
|
const currentNode = getCurrentNode() || home;
|
||||||
|
const context = useDocContext();
|
||||||
|
|
||||||
|
const handleNavigate = (e: any): void => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ver = e.target.value;
|
||||||
|
|
||||||
|
if (currentNode) {
|
||||||
|
const newPath = currentNode.slug.replace(/^\/en\/[0-9]+\//, `/en/${ver}/`);
|
||||||
|
const otherNode = getNodes().find(n => n.slug === newPath);
|
||||||
|
// This has to be a hard refresh, because the sidebar needs to unmount
|
||||||
|
if (otherNode) {
|
||||||
|
navigate(otherNode.slug);
|
||||||
|
} else {
|
||||||
|
navigate(`/en/${ver}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = context === 'user' ? 'CMS Help' : 'CMS Docs';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header role="banner" className="header fixed-top">
|
<header role="banner" className="header fixed-top">
|
||||||
<div className="branding docs-branding">
|
<div className="branding docs-branding">
|
||||||
<div className="container position-relative py-2 d-flex">
|
<div className="container position-relative py-2 d-flex">
|
||||||
<div className="docs-logo-wrapper">
|
<div className="docs-logo-wrapper">
|
||||||
<div className="site-logo">
|
<div className="site-logo">
|
||||||
<Link className="navbar-brand" to={ home ? home.slug : '/'} title="Go to the home page">
|
<Link style={{ backgroundImage: `url(${logo})`}} className="navbar-brand" to={ home ? home.slug : '/'} title="Go to the home page">
|
||||||
<img src={logo} alt="Silverstripe CMS documentation" />
|
Silverstripe CMS Documentation
|
||||||
<span>Documentation</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
<span />
|
||||||
|
<span>{title}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="docs-top-utilities d-flex justify-content-between justify-content-lg-end align-items-center">
|
<div className="docs-top-utilities d-flex justify-content-between justify-content-lg-end align-items-center">
|
||||||
<div className="top-search-box d-none d-lg-flex">
|
<div className="top-search-box d-none d-lg-flex">
|
||||||
|
{process.env.GATSBY_DOCSEARCH_API_KEY && (
|
||||||
<form className="search-form">
|
<form className="search-form">
|
||||||
<SearchBox identifier="header-search" />
|
<SearchBox identifier="header-search" />
|
||||||
</form>
|
</form>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ul className="social-list list-inline d-flex flex-grow-1 flex-lg-grow-0 justify-content-between justify-content-lg-around align-items-center">
|
<ul className="social-list list-inline d-flex flex-grow-1 flex-lg-grow-0 align-items-center justify-content-lg-center justify-content-end justify-content-lg-end">
|
||||||
<li className="list-inline-item version-select">
|
<li className="list-inline-item version-select">
|
||||||
<label htmlFor="version-select">Version: </label>
|
|
||||||
<select id="version-select" value={getCurrentVersion() || '4'} onChange={handleNavigate}>
|
<select id="version-select" value={getCurrentVersion() || '4'} onChange={handleNavigate}>
|
||||||
<option value='4'>4.x</option>
|
<option value='4'>V4</option>
|
||||||
<option value='3'>3.x</option>
|
<option value='3'>V3</option>
|
||||||
</select>
|
</select>
|
||||||
<i className="fas fa-chevron-down"></i>
|
<i className="fas fa-chevron-down"></i>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { StatelessComponent, useState, ReactNode } from "react";
|
import React, { StatelessComponent, useState, ReactNode } from "react";
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
import { setCurrentNode } from '../utils/nodes';
|
import useHierarchy from '../hooks/useHierarchy';
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
|
@ -9,15 +9,13 @@ interface LayoutProps {
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Layout: StatelessComponent<LayoutProps> = ({ children, pathContext: { slug } }) => {
|
const Layout: StatelessComponent<LayoutProps> = ({ children, pageContext: { slug } }) => {
|
||||||
|
const { setCurrentPath } = useHierarchy();
|
||||||
const [isToggled, setSidebarOpen] = useState(false);
|
const [isToggled, setSidebarOpen] = useState(false);
|
||||||
const handleNavigate = () => setSidebarOpen(false);
|
const handleNavigate = () => setSidebarOpen(false);
|
||||||
|
|
||||||
// This is a wrapper component that only gets mounted once.
|
|
||||||
// These two method calls set the initial state for SSR. From here on,
|
|
||||||
// the state is updated each time the docs template renders
|
|
||||||
setCurrentNode(slug);
|
|
||||||
|
|
||||||
|
setCurrentPath(slug);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header handleSidebarToggle={() => setSidebarOpen(!isToggled)} />
|
<Header handleSidebarToggle={() => setSidebarOpen(!isToggled)} />
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { StatelessComponent, ReactElement } from 'react';
|
import React, { StatelessComponent, ReactElement } from 'react';
|
||||||
import { getNavChildren, getHomePage, getCurrentNode } from '../utils/nodes';
|
|
||||||
import { SilverstripeDocument } from '../types';
|
import { SilverstripeDocument } from '../types';
|
||||||
import { Link } from 'gatsby';
|
import { Link } from 'gatsby';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { LinkGetProps } from '@reach/router';
|
import { LinkGetProps } from '@reach/router';
|
||||||
|
import useHierarchy from '../hooks/useHierarchy';
|
||||||
|
|
||||||
interface NavProps {
|
interface NavProps {
|
||||||
onNavigate?(e: React.MouseEvent): void;
|
onNavigate?(e: React.MouseEvent): void;
|
||||||
|
@ -21,16 +21,23 @@ const getLinkProps = (props: LinkGetProps): {} => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Nav:StatelessComponent<NavProps> = ({ onNavigate }): ReactElement => {
|
const Nav:StatelessComponent<NavProps> = ({ onNavigate }): ReactElement => {
|
||||||
|
const {
|
||||||
|
getNavChildren,
|
||||||
|
getHomePage,
|
||||||
|
getCurrentNode,
|
||||||
|
} = useHierarchy();
|
||||||
|
|
||||||
const currentNode = getCurrentNode();
|
const currentNode = getCurrentNode();
|
||||||
const top = getHomePage();
|
const top = getHomePage();
|
||||||
|
|
||||||
if (!top) {
|
if (!top) {
|
||||||
return <nav />;
|
return <nav />;
|
||||||
}
|
}
|
||||||
|
const topLevel = getNavChildren(top);
|
||||||
return (
|
return (
|
||||||
<nav role="navigation" id="docs-nav" className="docs-nav navbar">
|
<nav role="navigation" id="docs-nav" className="docs-nav navbar">
|
||||||
<ul className="section-items list-unstyled nav flex-column pb-3">
|
<ul className="section-items list-unstyled nav flex-column pb-3">
|
||||||
{getNavChildren(top).map((node: SilverstripeDocument) => {
|
{topLevel.map((node: SilverstripeDocument) => {
|
||||||
const { slug, title } = node;
|
const { slug, title } = node;
|
||||||
const childItems = getNavChildren(node);
|
const childItems = getNavChildren(node);
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import React, { StatelessComponent, ReactElement } from 'react';
|
||||||
|
import NodeContext from '../contexts/NodeContext';
|
||||||
|
import { useStaticQuery, graphql } from 'gatsby';
|
||||||
|
import { HierarchyQuery } from '../types';
|
||||||
|
import {
|
||||||
|
getChildren,
|
||||||
|
getCurrentNode,
|
||||||
|
getCurrentVersion,
|
||||||
|
getHomePage,
|
||||||
|
getNavChildren,
|
||||||
|
getNodes,
|
||||||
|
getParent,
|
||||||
|
getSiblings,
|
||||||
|
initialise,
|
||||||
|
setCurrentPath,
|
||||||
|
} from '../utils/nodes';
|
||||||
|
|
||||||
|
const NodeProvider: StatelessComponent<{}> = ({ children, pageContext: { slug } }): ReactElement => {
|
||||||
|
const result:HierarchyQuery = useStaticQuery(graphql`
|
||||||
|
{
|
||||||
|
allSilverstripeDocument {
|
||||||
|
nodes {
|
||||||
|
title
|
||||||
|
summary
|
||||||
|
isIndex
|
||||||
|
introduction
|
||||||
|
icon
|
||||||
|
iconBrand
|
||||||
|
hideChildren
|
||||||
|
slug
|
||||||
|
parentSlug
|
||||||
|
fileTitle
|
||||||
|
fileAbsolutePath
|
||||||
|
category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
if (!getNodes()) {
|
||||||
|
const nodes = result.allSilverstripeDocument.nodes.map(node => ({
|
||||||
|
...node,
|
||||||
|
}));
|
||||||
|
initialise(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentPath(slug);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeContext.Provider value={{
|
||||||
|
getChildren,
|
||||||
|
getCurrentNode,
|
||||||
|
getCurrentVersion,
|
||||||
|
getHomePage,
|
||||||
|
getNavChildren,
|
||||||
|
getNodes,
|
||||||
|
getParent,
|
||||||
|
getSiblings,
|
||||||
|
setCurrentPath,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</NodeContext.Provider>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NodeProvider;
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StatelessComponent, ReactElement, useEffect } from 'react';
|
import { StatelessComponent, ReactElement, useEffect, useState } from 'react';
|
||||||
import { navigateTo } from "gatsby-link"
|
import { navigateTo } from "gatsby-link"
|
||||||
import { getCurrentVersion } from '../utils/nodes';
|
import useHierarchy from '../hooks/useHierarchy';
|
||||||
|
import useDocContext from '../hooks/useDocContext';
|
||||||
|
import { useStaticQuery, graphql } from 'gatsby';
|
||||||
|
|
||||||
interface SearchBoxProps {
|
interface SearchBoxProps {
|
||||||
identifier: string;
|
identifier: string;
|
||||||
|
@ -17,6 +19,10 @@ const autocompleteSelected = (e) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const SearchBox: StatelessComponent<SearchBoxProps> = ({ identifier }): ReactElement|null => {
|
const SearchBox: StatelessComponent<SearchBoxProps> = ({ identifier }): ReactElement|null => {
|
||||||
|
const { getCurrentVersion } = useHierarchy();
|
||||||
|
const [ isFocused, setFocus ] = useState(false);
|
||||||
|
const context = useDocContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
if (!process.env.GATSBY_DOCSEARCH_API_KEY) {
|
if (!process.env.GATSBY_DOCSEARCH_API_KEY) {
|
||||||
|
@ -30,7 +36,10 @@ const SearchBox: StatelessComponent<SearchBoxProps> = ({ identifier }): ReactEle
|
||||||
if(window.docsearch){
|
if(window.docsearch){
|
||||||
window.docsearch({
|
window.docsearch({
|
||||||
algoliaOptions: {
|
algoliaOptions: {
|
||||||
facetFilters: [`version:${getCurrentVersion()}`],
|
facetFilters: [
|
||||||
|
`version:${getCurrentVersion()}`,
|
||||||
|
//`context:${context}`,
|
||||||
|
],
|
||||||
hitsPerPage: 5,
|
hitsPerPage: 5,
|
||||||
},
|
},
|
||||||
apiKey: process.env.GATSBY_DOCSEARCH_API_KEY,
|
apiKey: process.env.GATSBY_DOCSEARCH_API_KEY,
|
||||||
|
@ -42,13 +51,24 @@ const SearchBox: StatelessComponent<SearchBoxProps> = ({ identifier }): ReactEle
|
||||||
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleFocus = () => setFocus(true);
|
||||||
|
const handleBlur = (e) => {
|
||||||
|
if (!e.target.value.trim()) {
|
||||||
|
setFocus(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<label className={ isFocused ? `hide` : `show` } htmlFor={identifier}>Search...</label>
|
||||||
<input
|
<input
|
||||||
id={identifier}
|
id={identifier}
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search the docs..."
|
|
||||||
className="form-control search-input"
|
className="form-control search-input"
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const NodeContext = React.createContext(null);
|
||||||
|
|
||||||
|
export default NodeContext;
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { graphql, useStaticQuery } from 'gatsby';
|
||||||
|
|
||||||
|
const useDocContext = (): string => {
|
||||||
|
const result = useStaticQuery(graphql`
|
||||||
|
query {
|
||||||
|
site {
|
||||||
|
siteMetadata {
|
||||||
|
context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
return result.site.siteMetadata.context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDocContext;
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import NodeContext from '../contexts/NodeContext';
|
||||||
|
import { SilverstripeDocument } from '../types';
|
||||||
|
|
||||||
|
interface NodeFunctions {
|
||||||
|
initialise(nodes: SilverstripeDocument[]): undefined;
|
||||||
|
getChildren(node: SilverstripeDocument, includeFolders: boolean): SilverstripeDocument[];
|
||||||
|
getSiblings(node: SilverstripeDocument): SilverstripeDocument[];
|
||||||
|
getNodes(): SilverstripeDocument[];
|
||||||
|
getParent(node: SilverstripeDocument): SilverstripeDocument|null;
|
||||||
|
getCurrentNode(): SilverstripeDocument|null;
|
||||||
|
getHomePage(): SilverstripeDocument|null;
|
||||||
|
getNavChildren(node: SilverstripeDocument): SilverstripeDocument[];
|
||||||
|
getCurrentVersion(): string;
|
||||||
|
setCurrentPath(slug: string): undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useHierarchy = (): NodeFunctions => {
|
||||||
|
const hierarchy = useContext(NodeContext);
|
||||||
|
|
||||||
|
return hierarchy;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useHierarchy;
|
|
@ -1,37 +1,23 @@
|
||||||
import React, { StatelessComponent, ReactElement, useEffect } from 'react';
|
import React, { StatelessComponent, ReactElement } from 'react';
|
||||||
import { graphql } from 'gatsby';
|
import { graphql } from 'gatsby';
|
||||||
import SEO from '../components/SEO';
|
import DocsPage from '../components/DocsPage';
|
||||||
import { SingleFileQuery } from '../types';
|
import { SingleFileQuery } from '../types';
|
||||||
import parseHTML from '../utils/parseHTML';
|
|
||||||
import { setCurrentNode } from '../utils/nodes';
|
|
||||||
|
|
||||||
const Template: StatelessComponent<SingleFileQuery> = (result): ReactElement => {
|
const Template: StatelessComponent<SingleFileQuery> = (result): ReactElement => {
|
||||||
const currentNode = result.data.silverstripeDocument;
|
const currentNode = result.data.silverstripeDocument;
|
||||||
let html;
|
const { title } = currentNode;
|
||||||
if (currentNode.watchFile) {
|
const { html } = currentNode.watchFile;
|
||||||
html = currentNode.watchFile.html;
|
const { relativePath, gitRemote } = currentNode.parent.parent;
|
||||||
} else {
|
const { ref, href } = gitRemote;
|
||||||
html = currentNode.parent.html;
|
|
||||||
}
|
|
||||||
const { title, slug } = currentNode;
|
|
||||||
const { relativePath, gitRemote: { ref, webLink } } = currentNode.parent.parent;
|
|
||||||
const editLink = `${webLink}/edit/${ref}/${relativePath}`;
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentNode(slug);
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DocsPage
|
||||||
<SEO title={title} />
|
title={title}
|
||||||
{parseHTML(html)}
|
html={html}
|
||||||
<div className="github-edit">
|
relPath={relativePath}
|
||||||
<a target="_blank" href={editLink} title="Edit on Github">
|
branch={ref}
|
||||||
<i className="fas fa-pen fa-fw" />{` `}
|
gitURL={href}
|
||||||
Edit on Github
|
/>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,8 +39,8 @@ export const pageQuery = graphql`
|
||||||
... on File {
|
... on File {
|
||||||
relativePath
|
relativePath
|
||||||
gitRemote {
|
gitRemote {
|
||||||
|
href
|
||||||
ref
|
ref
|
||||||
webLink
|
|
||||||
sourceInstanceName
|
sourceInstanceName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,30 @@ body {
|
||||||
.search-form {
|
.search-form {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
label {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(50% - 0.5rem);
|
||||||
|
font-size: 1rem;
|
||||||
|
z-index: 100;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: text;
|
||||||
|
&.hide {
|
||||||
|
transform: translateY(-15px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
border-radius: 5px;
|
border: 1px solid $gray-900;
|
||||||
background: $gray-200;
|
border-width: 0 0 1px 0;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 1rem 0;
|
||||||
&:focus {
|
&:focus {
|
||||||
color: $gray-900;
|
color: $gray-900;
|
||||||
|
border-color: $gray-900;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,26 +101,37 @@ code {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 1rem;
|
|
||||||
.site-logo {
|
.site-logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 180px;
|
||||||
|
padding-left: 1rem;
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
text-indent: -9999em;
|
||||||
|
display: inline-block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
span {
|
||||||
align-items: center;
|
&:nth-child(2) {
|
||||||
justify-content: center;
|
width: 1px;
|
||||||
span {
|
background: $gray-700;
|
||||||
text-transform: uppercase;
|
height: 32px;
|
||||||
font-size: 0.8rem;
|
}
|
||||||
color: $gray-850;
|
&:nth-child(3) {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: darken($gray-850, 5%);
|
||||||
|
font-weight: bold;
|
||||||
|
justify-self: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
img {
|
|
||||||
width: 7rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.docs-top-utilities {
|
.docs-top-utilities {
|
||||||
|
@ -110,34 +140,47 @@ code {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
li {
|
li {
|
||||||
margin: 0;
|
margin: 0 1rem;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
a {
|
||||||
|
color: $gray-850;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.version-select {
|
.version-select {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 7rem;
|
width: 3rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: $gray-800;
|
color: $gray-800;
|
||||||
label {
|
label {
|
||||||
font-size: 0.7rem;
|
font-size: 0.75rem;
|
||||||
margin: 0 0.5rem;
|
margin: 0 0.5rem;
|
||||||
color: $gray-900;
|
color: $gray-800;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
color: $gray-800;
|
color: $gray-800;
|
||||||
background: none;
|
background: $theme-color-primary;
|
||||||
padding-right: 2rem;
|
border-radius: 100px;
|
||||||
flex-shrink: 0;
|
color: #fff;
|
||||||
|
padding: 0.45rem 1.25rem 0.45rem 0.75rem;
|
||||||
|
width: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
i {
|
i {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
margin-left: -1rem;
|
margin: 2px 0 0 -1.25rem;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,14 +254,27 @@ h1, h2, h3 {
|
||||||
width: auto;
|
width: auto;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
/* purgecss ignore */
|
||||||
/* purgecss ignore */
|
.algolia-autocomplete .ds-dropdown-menu {
|
||||||
.algolia-autocomplete .ds-dropdown-menu {
|
min-width: 350px;
|
||||||
min-width: 400px;
|
max-width: 470px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.search-form {
|
.search-form {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
.docs-logo-wrapper {
|
||||||
|
padding-left: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.docs-top-utilities {
|
||||||
|
.social-list {
|
||||||
|
li {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
code[class*="language-"], pre[class*="language-"] {
|
code[class*="language-"], pre[class*="language-"] {
|
||||||
|
@ -229,9 +285,14 @@ code[class*="language-"], pre[class*="language-"] {
|
||||||
background: #f5f6f8;
|
background: #f5f6f8;
|
||||||
color: #5d6778;
|
color: #5d6778;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
.algolia-autocomplete {
|
header {
|
||||||
width: 100%;
|
.algolia-autocomplete {
|
||||||
|
width: 100%;
|
||||||
|
.ds-dropdown-menu {
|
||||||
|
min-width: 535px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.gatsby-highlight {
|
.gatsby-highlight {
|
||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
|
|
|
@ -15,6 +15,7 @@ export interface SinglePage {
|
||||||
parent: {
|
parent: {
|
||||||
relativePath: string;
|
relativePath: string;
|
||||||
gitRemote: {
|
gitRemote: {
|
||||||
|
href: string;
|
||||||
ref: string;
|
ref: string;
|
||||||
webLink: string;
|
webLink: string;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +23,10 @@ export interface SinglePage {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum DocCategory {
|
||||||
|
'docs' = 'docs',
|
||||||
|
'user' = 'user',
|
||||||
|
}
|
||||||
|
|
||||||
export interface SilverstripeDocument {
|
export interface SilverstripeDocument {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -38,6 +43,7 @@ export interface SilverstripeDocument {
|
||||||
summary: string;
|
summary: string;
|
||||||
fileTitle: string;
|
fileTitle: string;
|
||||||
fileAbsolutePath: string;
|
fileAbsolutePath: string;
|
||||||
|
category: DocCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HierarchyQuery {
|
export interface HierarchyQuery {
|
||||||
|
|
|
@ -1,52 +1,27 @@
|
||||||
import { useStaticQuery } from 'gatsby';
|
import { SilverstripeDocument } from '../types';
|
||||||
import { graphql } from 'gatsby';
|
|
||||||
import { HierarchyQuery, SilverstripeDocument } from '../types';
|
|
||||||
import sortFiles from './sortFiles';
|
import sortFiles from './sortFiles';
|
||||||
|
|
||||||
let __nodes: SilverstripeDocument[] | undefined;
|
let __nodes: SilverstripeDocument[];
|
||||||
let __currentNode: SilverstripeDocument | null = null;
|
|
||||||
let __currentVersion: string | null = null;
|
let __currentVersion: string | null = null;
|
||||||
let __home: SilverstripeDocument | null = null;
|
let __path: string | null = null;
|
||||||
|
|
||||||
|
const homeMap = new Map();
|
||||||
const childrenMap = new Map();
|
const childrenMap = new Map();
|
||||||
const navChildrenMap = new Map();
|
const navChildrenMap = new Map();
|
||||||
const siblingMap = new Map();
|
const siblingMap = new Map();
|
||||||
const parentMap = new Map();
|
const parentMap = new Map();
|
||||||
|
const nodeMap = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydrate these functions with the list of all nodes
|
||||||
|
* @param nodes
|
||||||
|
*/
|
||||||
|
const initialise = (nodes: SilverstripeDocument[]) => __nodes = nodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all documents in the source
|
* Get all documents in the source
|
||||||
*/
|
*/
|
||||||
const getNodes = (): SilverstripeDocument[] => {
|
const getNodes = (): SilverstripeDocument[] => __nodes;
|
||||||
if (__nodes) {
|
|
||||||
return __nodes;
|
|
||||||
}
|
|
||||||
const result:HierarchyQuery = useStaticQuery(graphql`
|
|
||||||
{
|
|
||||||
allSilverstripeDocument {
|
|
||||||
nodes {
|
|
||||||
title
|
|
||||||
summary
|
|
||||||
isIndex
|
|
||||||
introduction
|
|
||||||
icon
|
|
||||||
iconBrand
|
|
||||||
hideChildren
|
|
||||||
unhideSelf
|
|
||||||
slug
|
|
||||||
parentSlug
|
|
||||||
fileTitle
|
|
||||||
fileAbsolutePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
__nodes = result.allSilverstripeDocument.nodes.map(node => ({
|
|
||||||
...node,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return __nodes;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the children of a given node
|
* Get the children of a given node
|
||||||
|
@ -132,22 +107,35 @@ const getParent = (node: SilverstripeDocument): SilverstripeDocument | null => {
|
||||||
/**
|
/**
|
||||||
* Get the current node. Must be set by setCurrentNode(string: slug)
|
* Get the current node. Must be set by setCurrentNode(string: slug)
|
||||||
*/
|
*/
|
||||||
const getCurrentNode = (): SilverstripeDocument | null => __currentNode;
|
const getCurrentNode = (): SilverstripeDocument | null => {
|
||||||
|
if (!__path) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (nodeMap.has(__path)) {
|
||||||
|
return nodeMap.get(__path) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = getNodes().find(n => n.slug === __path) || null;
|
||||||
|
|
||||||
|
nodeMap.set(__path, node);
|
||||||
|
|
||||||
|
return nodeMap.get(__path);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the home page
|
* Get the home page
|
||||||
*/
|
*/
|
||||||
const getHomePage = (): SilverstripeDocument | null => {
|
const getHomePage = (): SilverstripeDocument | null => {
|
||||||
if (__home) {
|
|
||||||
return __home;
|
|
||||||
}
|
|
||||||
const nodes = getNodes();
|
const nodes = getNodes();
|
||||||
const version = getCurrentVersion();
|
const version = getCurrentVersion();
|
||||||
const homePage = nodes.find(n => n.slug === `/en/${version}/`) || null;
|
let slug = `/en/${version}/`;
|
||||||
|
if (homeMap.has(slug)) {
|
||||||
|
return homeMap.get(slug) || null;
|
||||||
|
}
|
||||||
|
const homePage = nodes.find(n => n.slug === slug) || null;
|
||||||
|
homeMap.set(slug, homePage);
|
||||||
|
|
||||||
__home = homePage;
|
return homeMap.get(slug);
|
||||||
|
|
||||||
return __home;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -156,22 +144,17 @@ const getHomePage = (): SilverstripeDocument | null => {
|
||||||
const getCurrentVersion = (): string => __currentVersion || '4';
|
const getCurrentVersion = (): string => __currentVersion || '4';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the current node by its slug.
|
* Set the current path, with some side effects for version
|
||||||
* @param slug
|
* @param slug
|
||||||
*/
|
*/
|
||||||
const setCurrentNode = (slug: string): void => {
|
const setCurrentPath = (path: string) => {
|
||||||
const currentNode = getNodes().find(n => n.slug === slug) || null;
|
__path = path || `/`;
|
||||||
__currentNode = currentNode;
|
const [_, lang, version] = __path.split('/');
|
||||||
|
__currentVersion = version;
|
||||||
if (currentNode) {
|
|
||||||
const matches = currentNode.slug.match(/^\/en\/([0-9]+)\//);
|
|
||||||
if (matches) {
|
|
||||||
__currentVersion = matches[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
initialise,
|
||||||
getNodes,
|
getNodes,
|
||||||
getChildren,
|
getChildren,
|
||||||
getSiblings,
|
getSiblings,
|
||||||
|
@ -180,5 +163,5 @@ export {
|
||||||
getHomePage,
|
getHomePage,
|
||||||
getNavChildren,
|
getNavChildren,
|
||||||
getCurrentVersion,
|
getCurrentVersion,
|
||||||
setCurrentNode
|
setCurrentPath
|
||||||
};
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import { ReactElement, createElement } from 'react';
|
import { ReactElement, createElement } from 'react';
|
||||||
import ChildrenOf from '../components/ChildrenOf';
|
import ChildrenOf from '../components/ChildrenOf';
|
||||||
import { getCurrentNode } from '../utils/nodes';
|
import { getCurrentNode } from './nodes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn [CHILDREN ... ] into a proper React component.
|
* Turn [CHILDREN ... ] into a proper React component.
|
||||||
|
|
|
@ -4,12 +4,22 @@ import { Link } from 'gatsby';
|
||||||
import rewriteAPILink from './rewriteAPILink';
|
import rewriteAPILink from './rewriteAPILink';
|
||||||
import { getCurrentNode, getCurrentVersion } from '../utils/nodes';
|
import { getCurrentNode, getCurrentVersion } from '../utils/nodes';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { SilverstripeDocument } from '../types';
|
||||||
|
|
||||||
interface LinkAttributes {
|
interface LinkAttributes {
|
||||||
href?: string;
|
href?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const relativeLink = (currentNode: SilverstripeDocument, href: string): string => {
|
||||||
|
const slug = path.join(currentNode.isIndex ? currentNode.slug : currentNode.parentSlug, href);
|
||||||
|
if (!slug.endsWith('/')) {
|
||||||
|
return `${slug}/`
|
||||||
|
}
|
||||||
|
|
||||||
|
return slug;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure links use the Gatsby <Link /> component. Client-side routing FTW
|
* Ensure links use the Gatsby <Link /> component. Client-side routing FTW
|
||||||
*
|
*
|
||||||
|
@ -80,10 +90,23 @@ const rewriteLink = (
|
||||||
|
|
||||||
// Relative to page
|
// Relative to page
|
||||||
if (currentNode && currentNode.parentSlug) {
|
if (currentNode && currentNode.parentSlug) {
|
||||||
|
|
||||||
|
// Relative links to markdown files should be resolved to their pretty urls.
|
||||||
|
if (href.endsWith('.md')) {
|
||||||
|
return createElement(
|
||||||
|
Link,
|
||||||
|
{
|
||||||
|
to: relativeLink(currentNode, href.replace(/\.md$/, '')),
|
||||||
|
className: 'gatsby-link',
|
||||||
|
},
|
||||||
|
domToReact(children, parseOptions)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return createElement(
|
return createElement(
|
||||||
Link,
|
Link,
|
||||||
{
|
{
|
||||||
to: path.join(currentNode.parentSlug, href),
|
to: relativeLink(currentNode, href),
|
||||||
className: 'gatsby-link'
|
className: 'gatsby-link'
|
||||||
},
|
},
|
||||||
domToReact(children, parseOptions)
|
domToReact(children, parseOptions)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { SilverstripeDocument } from "../types";
|
||||||
|
|
||||||
const sortFiles = (a: SilverstripeDocument, b: SilverstripeDocument): number => {
|
const sortFiles = (a: SilverstripeDocument, b: SilverstripeDocument): number => {
|
||||||
if (a.isIndex !== b.isIndex) {
|
if (a.isIndex !== b.isIndex) {
|
||||||
return a.isIndex ? -1 : 1;
|
//return a.isIndex ? -1 : 1;
|
||||||
}
|
}
|
||||||
return a.fileAbsolutePath > b.fileAbsolutePath ? 1 : -1;
|
return a.fileAbsolutePath > b.fileAbsolutePath ? 1 : -1;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue