diff --git a/gatsby-config.js b/gatsby-config.js index 5b6fe531e..9977d10d1 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -72,6 +72,13 @@ const sourcePlugins = { path: path.resolve(__dirname, '..', 'tailor', 'docs'), }, }, + { + resolve: 'gatsby-source-filesystem', + options: { + name: 'colonyTutorials', + path: path.resolve(__dirname, '..', 'colonyTutorials', 'tutorials'), + } + }, // { // resolve: 'gatsby-source-filesystem', // options: { @@ -173,6 +180,17 @@ module.exports = { } } }, + { + resolve: 'gatsby-transform-md-tutorials', + options: { + slugPrefix: 'tutorials', + langConfig: { + langs: CONFIGURED_LOCALES, + defaultLangKey, + prefixDefaultLangKey, + } + } + }, { resolve: 'gatsby-transformer-remark', options: { diff --git a/plugins/gatsby-transform-md-docs/gatsby-node.js b/plugins/gatsby-transform-md-docs/gatsby-node.js index 99d6a1913..f5c15ee89 100644 --- a/plugins/gatsby-transform-md-docs/gatsby-node.js +++ b/plugins/gatsby-transform-md-docs/gatsby-node.js @@ -41,13 +41,12 @@ exports.onCreateNode = ({ node, actions, getNode }, nodeOptions) => { createNodeField, createParentChildLink, } = actions - - const { langConfig: { defaultLangKey, prefixDefaultLangKey } } = nodeOptions; + const { langConfig: { defaultLangKey, prefixDefaultLangKey }, projects: configuredProjects } = nodeOptions; let projectNode let sectionNode - if (node.base === 'doc.config.json') { + if (node.base === 'doc.config.json' && configuredProjects.includes(node.sourceInstanceName)) { const { projectName, projectId } = getProjectInfo(node) projectNode = getNode(projectId) if (!projectNode) { @@ -75,7 +74,7 @@ exports.onCreateNode = ({ node, actions, getNode }, nodeOptions) => { projectNode.description = config.description projectNode.descriptionTranslations = getProjectDescriptionTranslations(config); projectNode.repoUrl = `https://github.com/JoinColony/${projectNode.name}`; - } else if (node.internal.type === 'MarkdownRemark') { + } else if (node.internal.type === 'MarkdownRemark' && configuredProjects.includes(getNode(node.parent).sourceInstanceName)) { const sectionName = node.frontmatter.section // If section does not exist in frontmatter we just return diff --git a/plugins/gatsby-transform-md-tutorials/gatsby-node.js b/plugins/gatsby-transform-md-tutorials/gatsby-node.js new file mode 100644 index 000000000..d55fe0649 --- /dev/null +++ b/plugins/gatsby-transform-md-tutorials/gatsby-node.js @@ -0,0 +1,104 @@ +const path = require('path'); +const slugify = require('slugify'); + +const { TutorialNode } = require('./nodes'); + +const nodeQuery = ` + { + tutorials: allTutorial { + edges { + node { + id + name + slug + fields { + markdownNodeId + } + } + } + } + } +`; + +const onCreateNode = async ({ actions: { createNode, createNodeField }, getNode, node }, nodeOptions) => { + const { projects: configuredProjects } = nodeOptions; + if (node.internal.type === 'MarkdownRemark' && configuredProjects.includes(getNode(node.parent).sourceInstanceName)) { + const { langConfig: { defaultLangKey, prefixDefaultLangKey } } = nodeOptions; + + let tutorialNode; + const { tutorialName, tutorialId } = getTutorialInfo(node); + tutorialNode = getNode(tutorialId); + if (!tutorialNode) { + tutorialNode = createTutorialNode(tutorialName, createNode, nodeOptions); + } + createNodeField({ node: tutorialNode, name: 'markdownNodeId', value: node.id }); + + const editUrl = getNodeEditUrl(getNode(node.parent)) + createNodeField({ + node: tutorialNode, + name: 'editUrl', + value: editUrl, + }) + node.editUrl = editUrl; + + if (!node.frontmatter.locale) { + node.frontmatter.locale = defaultLangKey; + } + const nodeLocale = node.frontmatter.locale; + const localeSlugPrefix = nodeLocale === defaultLangKey && !prefixDefaultLangKey ? '' : `${nodeLocale}/`; + // Add a slug as the TOC creation requires that (for linking) + node.slug = slugify(node.frontmatter.title, { lower: true }) + // Slug for the actual page + createNodeField({ + node: tutorialNode, + name: 'slug', + value: `/${localeSlugPrefix}${tutorialNode.slug}`, + }) + } +}; + +const createPages = ({ graphql, actions: { createPage } }, nodeOptions) => { + return graphql(nodeQuery).then(({ data: { tutorials: { edges }} }) => { + edges.forEach(({ node: tutorial }) => { + createTutorialPage(tutorial, createPage, nodeOptions); + }); + }); +}; + +const createTutorialNode = (name, createNode, { slugPrefix }) => { + const tutorialNode = TutorialNode({ + name, + slug: `${slugify(slugPrefix, { lower: true })}/${slugify(name, { lower: true })}`, + }); + createNode(tutorialNode); + return tutorialNode; +}; + +const createTutorialPage = ({ slug, fields: { markdownNodeId } }, createPage, nodeOptions) => { + createPage({ + path: slug, + component: path.resolve(__dirname, '..', '..', 'src', 'modules', 'pages', 'components', 'TutorialPage', 'TutorialPage.jsx'), + context: { + tutorialId: markdownNodeId, + }, + }); +}; + +const getNodeEditUrl = parent => { + // github sourced + if(parent && parent.githubEditPath) { + return `https://github.com/${parent.githubEditPath}`; + } + // filesystem sourced - assume master branch + const projectName = parent.sourceInstanceName; + return `https://github.com/JoinColony/${projectName}/edit/master/tutorials/${parent.relativePath}`; +}; + +const getTutorialInfo = node => { + const tutorialName = node.frontmatter.title; + const tutorialId = TutorialNode({ name: tutorialName }).id; + return { tutorialName, tutorialId }; +}; + +exports.createPages = createPages; +exports.onCreateNode = onCreateNode; diff --git a/plugins/gatsby-transform-md-tutorials/nodes/index.js b/plugins/gatsby-transform-md-tutorials/nodes/index.js new file mode 100644 index 000000000..f693d4e15 --- /dev/null +++ b/plugins/gatsby-transform-md-tutorials/nodes/index.js @@ -0,0 +1 @@ +exports.TutorialNode = require('./tutorial'); diff --git a/plugins/gatsby-transform-md-tutorials/nodes/tutorial.js b/plugins/gatsby-transform-md-tutorials/nodes/tutorial.js new file mode 100644 index 000000000..9cfc58fa0 --- /dev/null +++ b/plugins/gatsby-transform-md-tutorials/nodes/tutorial.js @@ -0,0 +1,17 @@ +const createNodeHelpers = require('gatsby-node-helpers').default; + +const { createNodeFactory, generateNodeId } = createNodeHelpers({ + typePrefix: 'Tutorials', +}); + +const TUTORIAL_TYPE = 'Tutorial'; + +const TutorialNode = createNodeFactory(TUTORIAL_TYPE, node => { + node.id = generateNodeId(TUTORIAL_TYPE, node.name); + node.internal = { + type: TUTORIAL_TYPE, + }; + return node; +}); + +module.exports = TutorialNode; diff --git a/plugins/gatsby-transform-md-tutorials/package.json b/plugins/gatsby-transform-md-tutorials/package.json new file mode 100644 index 000000000..e3436eb6a --- /dev/null +++ b/plugins/gatsby-transform-md-tutorials/package.json @@ -0,0 +1,13 @@ +{ + "name": "gatsby-transform-md-tutorials", + "version": "1.0.0", + "description": "", + "main": "gatsby-node.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "contributors": [ + "Curtis Olson " + ], + "license": "ISC" +} diff --git a/src/modules/core/components/Button/Button.jsx b/src/modules/core/components/Button/Button.jsx index fe19fbdbc..f4dc4a3f6 100644 --- a/src/modules/core/components/Button/Button.jsx +++ b/src/modules/core/components/Button/Button.jsx @@ -11,7 +11,7 @@ import { getMainClasses } from '~utils/css'; import styles from './Button.module.css'; type Appearance = { - theme?: 'primary' | 'reset', + theme?: 'primary' | 'primaryHollow' | 'reset', }; type Props = { diff --git a/src/modules/core/components/Button/Button.module.css b/src/modules/core/components/Button/Button.module.css index 0a74eb1d4..8bfcc9960 100644 --- a/src/modules/core/components/Button/Button.module.css +++ b/src/modules/core/components/Button/Button.module.css @@ -3,29 +3,40 @@ align-items: center; border-radius: 4px; display: inline-flex; - height: 45px; - font-size: 15px; + font-size: 16px; justify-content: center; - font-family: 'Avenir Next Demi'; + font-family: 'Avenir Next Light'; text-align: center; transition: 0.2s; } -.themePrimary { +.primaryBase { composes: main; - background-color: rgb(105, 220, 210); - border: 2px solid rgb(105, 220, 210); - color: rgb(0, 40, 75); + border: 1px solid #289BDC; min-width: 130px; - padding: 11px 15px; + padding: 3px 31px; +} + +.themePrimary { + composes: primaryBase; + background-color: #289BDC; + color: #ffffff; } .themePrimary:hover { - background-color: transparent !important; - border: 2px solid rgb(105, 220, 210) !important; - box-shadow: none !important; - cursor: pointer !important; - text-decoration: none !important; + background-color: transparent; + color: #00284B; +} + +.themePrimaryHollow { + composes: primaryBase; + background-color: transparent; + color: #00284B; +} + +.themePrimaryHollow:hover { + background-color: #289BDC; + color: #ffffff; } .themeReset { diff --git a/src/modules/layouts/components/DeveloperPortalLayout/DocsDropdownContent/DocsDropdownContent.module.css b/src/modules/layouts/components/DeveloperPortalLayout/DocsDropdownContent/DocsDropdownContent.module.css index 2da1993c7..794ff13ca 100644 --- a/src/modules/layouts/components/DeveloperPortalLayout/DocsDropdownContent/DocsDropdownContent.module.css +++ b/src/modules/layouts/components/DeveloperPortalLayout/DocsDropdownContent/DocsDropdownContent.module.css @@ -10,6 +10,11 @@ text-align: left; } +.heroLink { + display: block; + height: 82px; +} + .hero { align-items: stretch; border-radius: borderRadius; @@ -21,7 +26,8 @@ border-radius: borderRadius; box-shadow: boxShadow; display: flex; - align-items: center + align-items: center; + justify-content: center; } .logo { diff --git a/src/modules/pages/components/Developers/Developers.jsx b/src/modules/pages/components/Developers/Developers.jsx index bba1c231d..a6bfd5437 100644 --- a/src/modules/pages/components/Developers/Developers.jsx +++ b/src/modules/pages/components/Developers/Developers.jsx @@ -6,11 +6,11 @@ import { defineMessages } from 'react-intl'; import { Helmet } from 'react-helmet'; import SEO from '~parts/SEO'; +import SupportCta from '~parts/SupportCta'; import CoreProducts from './CoreProducts'; import Hero from './Hero'; import OpenSource from './OpenSource'; -import SupportCta from './SupportCta'; import styles from './Developers.module.css'; @@ -47,7 +47,7 @@ const Developers = ({ intl: { formatMessage } }: Props) => { - + ); diff --git a/src/modules/pages/components/Developers/SupportCta/SupportCta.jsx b/src/modules/pages/components/Developers/SupportCta/SupportCta.jsx deleted file mode 100644 index 82c7f0f9a..000000000 --- a/src/modules/pages/components/Developers/SupportCta/SupportCta.jsx +++ /dev/null @@ -1,59 +0,0 @@ -/* @flow */ -import React from 'react'; -import { defineMessages } from 'react-intl'; -import { withPrefix } from 'gatsby'; - -import Heading from '~core/Heading'; -import Icon from '~core/Icon'; -import Link from '~core/Link'; -import { - COLONY_DISCOURSE, - COLONY_GITHUB, - COLONY_GITTER_COLONYJS, -} from '~routes'; - -import styles from './SupportCta.module.css'; - -const MSG = defineMessages({ - sectionTitle: { - id: 'pages.Developers.SupportCta.sectionTitle', - defaultMessage: 'Questions? Problems? Existential dilemmas? We can help!', - }, -}); - -const displayName = 'pages.Developers.SupportCta'; - -const SupportCta = () => ( -
-
- -
- - - - - - - - - -
-
-
-); - -SupportCta.displayName = displayName; - -export default SupportCta; diff --git a/src/modules/pages/components/DocPage/DocPage.jsx b/src/modules/pages/components/DocPage/DocPage.jsx index 49018ee21..fb72b329f 100644 --- a/src/modules/pages/components/DocPage/DocPage.jsx +++ b/src/modules/pages/components/DocPage/DocPage.jsx @@ -1,6 +1,5 @@ /* @flow */ import React, { Component, createElement } from 'react'; -import { defineMessages } from 'react-intl'; import RehypeReact from 'rehype-react'; import Helmet from 'react-helmet'; import { withProps } from 'recompose'; @@ -12,44 +11,13 @@ import type { Doc, HtmlAst, Project } from '~types'; import Link from '~core/Link'; import Image from '~core/Image'; import DeveloperPortalLayout from '~layouts/DeveloperPortalLayout'; +import DevRelCta from '~parts/DevRelCta'; import SEO from '~parts/SEO'; -import { COLONY_DISCOURSE_SUPPORT } from '~routes'; -import CtaItem from './CtaItem'; import Sidebar from './Sidebar'; import styles from './DocPage.module.css'; -const MSG = defineMessages({ - ctaSupportHeading: { - id: 'pages.DocPage.ctaSupportHeading', - defaultMessage: 'Support', - }, - ctaSupportContent: { - id: 'pages.DocPage.ctaSupportContent', - defaultMessage: - 'Questions? Problems? Existential dilemmas? We’re here to help!', - }, - ctaSupportLinkText: { - id: 'pages.DocPage.ctaSupportLinkText', - defaultMessage: 'Contact DevRel', - }, - ctaImproveDocHeading: { - id: 'pages.DocPage.ctaImproveDocHeading', - defaultMessage: 'Improve this doc.', - }, - ctaImproveDocContent: { - id: 'pages.DocPage.ctaImproveDocContent', - defaultMessage: - // eslint-disable-next-line max-len - 'All improvements to documentation are welcome and encouraged. Submit a PR for documentation on GitHub.', - }, - ctaImproveDocLinkText: { - id: 'pages.DocPage.ctaSupportLinkText', - defaultMessage: 'To the repo!', - }, -}); - type Props = {| data: { project: Project, @@ -223,24 +191,7 @@ class DocPage extends Component {

{doc.frontmatter.title}

{this.renderAst(doc.htmlAst)} -
-
- -
-
- -
-
+ diff --git a/src/modules/pages/components/DocPage/DocPage.module.css b/src/modules/pages/components/DocPage/DocPage.module.css index 4d0966d2e..efdcdf102 100644 --- a/src/modules/pages/components/DocPage/DocPage.module.css +++ b/src/modules/pages/components/DocPage/DocPage.module.css @@ -135,14 +135,6 @@ border-left: inset 4px rgb(105, 220, 210); } -.ctaContainer { - margin: 60px 0; -} - -.ctaItem + .ctaItem { - margin-top: 40px; -} - @media screen and (mediumUp) { .main { margin: 0 9.5%; @@ -189,19 +181,6 @@ margin: 68px auto 0; } - .ctaContainer { - display: flex; - margin: 100px 0; - } - - .ctaItem { - width: 35%; - } - - .ctaItem + .ctaItem { - margin: 0 0 0 25%; - } - } @media (largeUp) { diff --git a/src/modules/pages/components/TutorialPage/TutorialPage.jsx b/src/modules/pages/components/TutorialPage/TutorialPage.jsx new file mode 100644 index 000000000..cbeef169d --- /dev/null +++ b/src/modules/pages/components/TutorialPage/TutorialPage.jsx @@ -0,0 +1,117 @@ +/* @flow */ +import React, { createElement } from 'react'; +import { graphql } from 'gatsby'; +import RehypeReact from 'rehype-react'; +import { withProps } from 'recompose'; +import { FormattedDate } from 'react-intl'; +import Helmet from 'react-helmet'; + +import type { Tutorial } from '~types'; +import type { Appearance as HeadingAppearance } from '~core/Heading'; + +import Heading from '~core/Heading'; +import Image from '~core/Image'; +import Link from '~core/Link'; +import DeveloperPortalLayout from '~layouts/DeveloperPortalLayout'; +import DevRelCta from '~parts/DevRelCta'; +import SEO from '~parts/SEO'; + +import styles from './TutorialPage.module.css'; + +type Props = {| + data: { + tutorial: Tutorial, + }, +|}; + +const commonHeadingAppearanceProps: HeadingAppearance = { + margin: 'none', + theme: 'dark', + weight: 'thin', +}; + +const headingWithSize = (size: string) => + withProps({ + appearance: { ...commonHeadingAppearanceProps, size }, + })(Heading); + +const displayName = 'pages.TutorialPage'; + +const TutorialPage = ({ + data: { + tutorial: { + editUrl, + excerpt, + frontmatter: { author, publishDate, title }, + htmlAst, + name, + }, + }, +}: Props) => { + const renderAst = new RehypeReact({ + createElement, + components: { + a: withProps({ + // @TODO handle i18n / transforming internalUrls + // transformUrl: this.transformInternalUrls, + persistLocale: false, + })(Link), + h1: headingWithSize('huge'), + h2: headingWithSize('large'), + h3: headingWithSize('medium'), + h4: headingWithSize('normal'), + h5: headingWithSize('small'), + h6: headingWithSize('tiny'), + img: withProps({ project: name })(Image), + }, + }).Compiler; + const tutorialContent = renderAst(htmlAst); + return ( + + + + {title} + +
+
+
+ +
+
+ +
+
+ + + +
+
+
+ {tutorialContent} + +
+
+
+ ); +}; + +TutorialPage.displayName = displayName; + +export const pageQuery = graphql` + query tutorialQuery($tutorialId: String!) { + ...singleTutorialFragment + } +`; + +export default TutorialPage; diff --git a/src/modules/pages/components/TutorialPage/TutorialPage.module.css b/src/modules/pages/components/TutorialPage/TutorialPage.module.css new file mode 100644 index 000000000..7b1640b91 --- /dev/null +++ b/src/modules/pages/components/TutorialPage/TutorialPage.module.css @@ -0,0 +1,38 @@ +@value mediumUp from '~styles/breakpoints.css'; + +.main { + display: flex; + justify-content: center; + margin: 0 9.5%; +} + +.mainInnerContainer { + display: flex; + flex-direction: column; + max-width: 1200px; + width: 100%; +} + +.metaContainer { + text-align: center; +} + +.metaContent { + align-items: center; + display: flex; + justify-content: center; +} + +.metaItem { + padding: 0 10px; +} + +@media screen and (mediumUp) { + .metaContainer { + padding: 96px 15%; + } + + .metaContent { + margin: 50px 0 0; + } +} diff --git a/src/modules/pages/components/Tutorials/Tutorials.js b/src/modules/pages/components/Tutorials/Tutorials.js new file mode 100644 index 000000000..140af79cf --- /dev/null +++ b/src/modules/pages/components/Tutorials/Tutorials.js @@ -0,0 +1,11 @@ +/* @flow */ +import { compose, nest } from 'recompose'; +import { injectIntl } from 'react-intl'; + +import DeveloperPortalLayout from '~layouts/DeveloperPortalLayout'; + +import Tutorials from './Tutorials.jsx'; + +const enhance = compose(injectIntl); + +export default nest<{}>(DeveloperPortalLayout, enhance(Tutorials)); diff --git a/src/modules/pages/components/Tutorials/Tutorials.jsx b/src/modules/pages/components/Tutorials/Tutorials.jsx new file mode 100644 index 000000000..b627a5585 --- /dev/null +++ b/src/modules/pages/components/Tutorials/Tutorials.jsx @@ -0,0 +1,133 @@ +/* @flow */ +import type { IntlShape } from 'react-intl'; + +import React from 'react'; +import { defineMessages } from 'react-intl'; +import Helmet from 'react-helmet'; + +import { graphql, useStaticQuery } from 'gatsby'; + +import type { TutorialNode } from '~types'; + +import Button from '~core/Button'; +import Heading from '~core/Heading'; +import Link from '~core/Link'; +import Search from '~core/Search'; +import SEO from '~parts/SEO'; +import SupportCta from '~parts/SupportCta'; +import { COLONY_GITHUB_TUTORIALS } from '~routes'; + +import styles from './Tutorials.module.css'; + +const MSG = defineMessages({ + btnClearSearch: { + id: 'pages.Tutorials.btnClearSearch', + defaultMessage: 'Clear Search', + }, + btnWriteTutorial: { + id: 'pages.Tutorials.btnWriteTutorial', + defaultMessage: 'Write a Tutorial', + }, + pageDescription: { + id: 'pages.Tutorials.pageDescription', + defaultMessage: 'Tutorials using the Colony protocol.', + }, + pageTitle: { + id: 'pages.Tutorials.pageTitle', + defaultMessage: 'Explore Tutorials', + }, + pageSubtitle: { + id: 'pages.Tutorials.pageSubtitle', + defaultMessage: 'Tutorials', + }, + searchPlaceholder: { + id: 'pages.Tutorials.searchPlaceholder', + defaultMessage: 'Search tutorials', + }, +}); + +type QueryData = { + allTutorials: { + edges: Array<{ + node: TutorialNode, + }>, + }, +}; + +type Props = {| + /** Injected via `injectIntl` */ + intl: IntlShape, +|}; + +const displayName = 'pages.Tutorials'; + +const Tutorials = ({ intl: { formatMessage } }: Props) => { + const title = formatMessage(MSG.pageSubtitle); + const tutorialsQueryData: QueryData = useStaticQuery(graphql` + { + ...allTutorialsFragment + } + `); + return ( + <> + + + {title} + +
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+ {tutorialsQueryData.allTutorials.edges.map( + ({ + node: { + fields: { slug }, + name, + }, + }) => ( + +
+

{name}

+
+ + ), + )} +
+
+ + + ); +}; + +Tutorials.displayName = displayName; + +export default Tutorials; diff --git a/src/modules/pages/components/Tutorials/Tutorials.module.css b/src/modules/pages/components/Tutorials/Tutorials.module.css new file mode 100644 index 000000000..244620f6b --- /dev/null +++ b/src/modules/pages/components/Tutorials/Tutorials.module.css @@ -0,0 +1,48 @@ +@value mediumUp from '~styles/breakpoints.css'; + +.metaContainer { + padding: 150px 0; + text-align: center; +} + +.searchContainer { + margin-top: 58px; +} + +.tutorialsContainer { + margin: 0 auto; + max-width: 1000px; + padding: 0 20px; +} + +.tutorialList { + padding: 130px 0 110px; +} + +.tutorialListItem { + border-bottom: 1px solid #C8D2DC; + color: #2E4153; + font-size: 16px; + padding: 44px 0; +} + +.tutorialListItem p { + margin: 0; +} + + +@media (mediumUp) { + .tutorialsContainerMeta { + align-items: center; + display: flex; + justify-content: space-between; + } + + .totorialsActionItem { + display: inline-block; + } + + .totorialsActionItem + .totorialsActionItem { + margin-left: 10px; + } +} diff --git a/src/modules/pages/components/Tutorials/index.js b/src/modules/pages/components/Tutorials/index.js new file mode 100644 index 000000000..2cf175667 --- /dev/null +++ b/src/modules/pages/components/Tutorials/index.js @@ -0,0 +1,2 @@ +/* @flow */ +export { default } from './Tutorials'; diff --git a/src/modules/pages/components/DocPage/CtaItem/CtaItem.jsx b/src/modules/parts/components/DevRelCta/CtaItem/CtaItem.jsx similarity index 95% rename from src/modules/pages/components/DocPage/CtaItem/CtaItem.jsx rename to src/modules/parts/components/DevRelCta/CtaItem/CtaItem.jsx index 162c43c6a..e05d58748 100644 --- a/src/modules/pages/components/DocPage/CtaItem/CtaItem.jsx +++ b/src/modules/parts/components/DevRelCta/CtaItem/CtaItem.jsx @@ -16,7 +16,7 @@ type Props = {| linkUrl: string, |}; -const displayName = 'pages.DocPage.CtaItem'; +const displayName = 'parts.DevRelCta.CtaItem'; const CtaItem = ({ contentText, headingText, linkText, linkUrl }: Props) => (
diff --git a/src/modules/pages/components/DocPage/CtaItem/CtaItem.module.css b/src/modules/parts/components/DevRelCta/CtaItem/CtaItem.module.css similarity index 100% rename from src/modules/pages/components/DocPage/CtaItem/CtaItem.module.css rename to src/modules/parts/components/DevRelCta/CtaItem/CtaItem.module.css diff --git a/src/modules/pages/components/DocPage/CtaItem/index.js b/src/modules/parts/components/DevRelCta/CtaItem/index.js similarity index 100% rename from src/modules/pages/components/DocPage/CtaItem/index.js rename to src/modules/parts/components/DevRelCta/CtaItem/index.js diff --git a/src/modules/parts/components/DevRelCta/DevRelCta.jsx b/src/modules/parts/components/DevRelCta/DevRelCta.jsx new file mode 100644 index 000000000..fe616ba6e --- /dev/null +++ b/src/modules/parts/components/DevRelCta/DevRelCta.jsx @@ -0,0 +1,70 @@ +/* @flow */ +import React from 'react'; +import { defineMessages } from 'react-intl'; + +import { COLONY_DISCOURSE_SUPPORT } from '~routes'; + +import CtaItem from './CtaItem'; + +import styles from './DevRelCta.module.css'; + +const MSG = defineMessages({ + supportHeading: { + id: 'parts.DevRelCta.supportHeading', + defaultMessage: 'Support', + }, + supportContent: { + id: 'parts.DevRelCta.supportContent', + defaultMessage: + 'Questions? Problems? Existential dilemmas? We’re here to help!', + }, + supportLinkText: { + id: 'parts.DevRelCta.supportLinkText', + defaultMessage: 'Contact DevRel', + }, + improveDocHeading: { + id: 'parts.DevRelCta.improveDocHeading', + defaultMessage: 'Improve this doc.', + }, + improveDocContent: { + id: 'parts.DevRelCta.improveDocContent', + defaultMessage: + // eslint-disable-next-line max-len + 'All improvements to documentation are welcome and encouraged. Submit a PR for documentation on GitHub.', + }, + improveDocLinkText: { + id: 'parts.DevRelCta.supportLinkText', + defaultMessage: 'To the repo!', + }, +}); + +type Props = {| + editUrl: string, +|}; + +const displayName = 'parts.DevRelCta'; + +const DevRelCta = ({ editUrl }: Props) => ( +
+
+ +
+
+ +
+
+); + +DevRelCta.displayName = displayName; + +export default DevRelCta; diff --git a/src/modules/parts/components/DevRelCta/DevRelCta.module.css b/src/modules/parts/components/DevRelCta/DevRelCta.module.css new file mode 100644 index 000000000..d080b38f1 --- /dev/null +++ b/src/modules/parts/components/DevRelCta/DevRelCta.module.css @@ -0,0 +1,28 @@ +@value mediumUp from '~styles/breakpoints.css'; + +.main { + margin: 60px 0; +} + +.main a { + color: #289BDC; +} + +.ctaItem + .ctaItem { + margin-top: 40px; +} + +@media (mediumUp) { + .main { + display: flex; + margin: 100px 0; + } + + .ctaItem { + width: 35%; + } + + .ctaItem + .ctaItem { + margin: 0 0 0 25%; + } +} diff --git a/src/modules/parts/components/DevRelCta/index.js b/src/modules/parts/components/DevRelCta/index.js new file mode 100644 index 000000000..9db5b7474 --- /dev/null +++ b/src/modules/parts/components/DevRelCta/index.js @@ -0,0 +1,2 @@ +/* @flow */ +export { default } from './DevRelCta.jsx'; diff --git a/src/modules/parts/components/SEO/SEO.js b/src/modules/parts/components/SEO/SEO.js index e117b7f03..99f4b5260 100644 --- a/src/modules/parts/components/SEO/SEO.js +++ b/src/modules/parts/components/SEO/SEO.js @@ -1,16 +1,42 @@ /* @flow */ +import type { HOC } from 'recompose'; + import { injectIntl } from 'react-intl'; -import { compose } from 'recompose'; +import { compose, defaultProps, withHandlers, withProps } from 'recompose'; import { withFileContext } from '~hoc/files'; import { withLocation } from '~hoc/location'; +import type { InProps } from './types'; + import SEO from './SEO.jsx'; -const enhance = compose( +const enhance: HOC<*, InProps> = compose( injectIntl, withFileContext(), withLocation(), + defaultProps({ + isDocPage: false, + }), + withProps(() => ({ + baseUrl: 'https://docs.colony.io', + })), + withHandlers({ + getAbsoluteImagePath: ({ baseUrl, files, project }) => ( + imagePath: string, + ) => { + return imagePath.startsWith('http') + ? imagePath + : `${baseUrl}${ + files && files[`${project}/${imagePath}`] + ? files[`${project}/${imagePath}`] + : imagePath + }`; + }, + }), + withProps(({ getAbsoluteImagePath }) => ({ + siteLogo: getAbsoluteImagePath('/img/colonyDocs_combomark.svg'), + })), ); export default enhance(SEO); diff --git a/src/modules/parts/components/SEO/SEO.jsx b/src/modules/parts/components/SEO/SEO.jsx index e770058bf..69963adb8 100644 --- a/src/modules/parts/components/SEO/SEO.jsx +++ b/src/modules/parts/components/SEO/SEO.jsx @@ -1,13 +1,10 @@ /* @flow */ -import type { RouteProps } from '@reach/router'; -import type { IntlShape, MessageDescriptor } from 'react-intl'; - -import React, { Component } from 'react'; +import React from 'react'; import { defineMessages } from 'react-intl'; import Helmet from 'react-helmet'; import { withPrefix } from 'gatsby'; -import type { FileContext as FileContextType } from '~types'; +import type { OutProps as Props } from './types'; const MSG = defineMessages({ siteName: { @@ -16,147 +13,119 @@ const MSG = defineMessages({ }, }); -type Props = RouteProps & { - description: MessageDescriptor | string, - descriptionValues?: Object, - files?: FileContextType, - images: Array, - /** Injected by `injectIntl` */ - intl: IntlShape, - isDocPage: boolean, - project: string, - title: MessageDescriptor | string, - titleValues?: Object, -}; - -class SEO extends Component { - static displayName = 'parts.SEO'; - - baseUrl = 'https://docs.colony.io'; - - getAbsoluteImagePath = (imagePath: string) => { - const { files, project } = this.props; - return imagePath.startsWith('http') - ? imagePath - : `${this.baseUrl}${ - files && files[`${project}/${imagePath}`] - ? files[`${project}/${imagePath}`] - : imagePath - }`; - }; - - render() { - const siteLogo = this.getAbsoluteImagePath('/img/colonyDocs_combomark.svg'); - const { - description: descriptionContent, - descriptionValues, - images = [siteLogo], - intl: { formatMessage }, - isDocPage = false, - location, - title: titleContent, - titleValues, - } = this.props; - - const absolutePath = - location && `${this.baseUrl}${withPrefix(location.pathname)}`; - const imagePaths = images.map(this.getAbsoluteImagePath); - if (imagePaths.indexOf(siteLogo) < 0) imagePaths.push(siteLogo); - const ogType = - location && location.pathname === '/' ? 'website' : 'article'; - const siteName = formatMessage(MSG.siteName); - const title = - typeof titleContent === 'string' - ? titleContent - : formatMessage(titleContent, titleValues); - const description = - typeof descriptionContent === 'string' - ? descriptionContent - : formatMessage(descriptionContent, descriptionValues); - - const schemaOrgJSONLD = [ +const displayName = 'parts.SEO'; + +const SEO = ({ + baseUrl, + description: descriptionContent, + descriptionValues, + getAbsoluteImagePath, + intl: { formatMessage }, + isDocPage, + location, + siteLogo, + title: titleContent, + titleValues, + images = [siteLogo], +}: Props) => { + const absolutePath = location && `${baseUrl}${withPrefix(location.pathname)}`; + const imagePaths = images.map(getAbsoluteImagePath); + if (imagePaths.indexOf(siteLogo) < 0) imagePaths.push(siteLogo); + const ogType = location && location.pathname === '/' ? 'website' : 'article'; + const siteName = formatMessage(MSG.siteName); + const title = + typeof titleContent === 'string' + ? titleContent + : formatMessage(titleContent, titleValues); + const description = + typeof descriptionContent === 'string' + ? descriptionContent + : formatMessage(descriptionContent, descriptionValues); + + const schemaOrgJSONLD = [ + { + '@context': 'http://schema.org', + '@type': 'WebSite', + url: baseUrl, + name: siteName, + }, + ]; + + if (isDocPage) { + schemaOrgJSONLD.push( { '@context': 'http://schema.org', - '@type': 'WebSite', - url: this.baseUrl, - name: siteName, - }, - ]; - - if (isDocPage) { - schemaOrgJSONLD.push( - { - '@context': 'http://schema.org', - '@type': 'BreadcrumbList', - itemListElement: [ - { - '@type': 'ListItem', - position: 1, - item: { - '@id': absolutePath, - name: title, - image: imagePaths[0], - }, + '@type': 'BreadcrumbList', + itemListElement: [ + { + '@type': 'ListItem', + position: 1, + item: { + '@id': absolutePath, + name: title, + image: imagePaths[0], }, - ], - }, - { - '@context': 'http://schema.org', - '@type': 'BlogPosting', - author: 'Colony', - url: absolutePath, - name: title, - headline: title, - image: { - '@type': 'ImageObject', - url: imagePaths[0], }, - description, + ], + }, + { + '@context': 'http://schema.org', + '@type': 'BlogPosting', + author: 'Colony', + url: absolutePath, + name: title, + headline: title, + image: { + '@type': 'ImageObject', + url: imagePaths[0], }, - ); - } - - return ( - - {/* General tags */} - - {imagePaths.map(imagePath => ( - - ))} - - {/* Schema.org tags */} - - - {/* Google+ tags */} - - - {imagePaths.map(imagePath => ( - - ))} - - {/* OpenGraph tags */} - - - - - - {imagePaths.map(imagePath => ( - - ))} - - {/* Twitter Card tags */} - - - - - {imagePaths.map(imagePath => ( - - ))} - + description, + }, ); } -} + + return ( + + {/* General tags */} + + {imagePaths.map(imagePath => ( + + ))} + + {/* Schema.org tags */} + + + {/* Google+ tags */} + + + {imagePaths.map(imagePath => ( + + ))} + + {/* OpenGraph tags */} + + + + + + {imagePaths.map(imagePath => ( + + ))} + + {/* Twitter Card tags */} + + + + + {imagePaths.map(imagePath => ( + + ))} + + ); +}; + +SEO.displayName = displayName; export default SEO; diff --git a/src/modules/parts/components/SEO/types.js b/src/modules/parts/components/SEO/types.js new file mode 100644 index 000000000..4456300d8 --- /dev/null +++ b/src/modules/parts/components/SEO/types.js @@ -0,0 +1,25 @@ +/* @flow */ +import type { RouteProps } from '@reach/router'; +import type { IntlShape, MessageDescriptor } from 'react-intl'; + +import type { FileContext as FileContextType } from '~types'; + +export type InProps = {| + description: MessageDescriptor | string, + descriptionValues?: Object, + images?: Array, + isDocPage?: boolean, + project?: string, + title: MessageDescriptor | string, + titleValues?: Object, +|}; + +export type OutProps = RouteProps & + InProps & { + baseUrl: string, + /** Injected by `injectIntl` */ + intl: IntlShape, + files?: FileContextType, + getAbsoluteImagePath: (imagePath: string) => string, + siteLogo: string, + }; diff --git a/src/modules/parts/components/SupportCta/SupportCta.jsx b/src/modules/parts/components/SupportCta/SupportCta.jsx new file mode 100644 index 000000000..7510414f9 --- /dev/null +++ b/src/modules/parts/components/SupportCta/SupportCta.jsx @@ -0,0 +1,66 @@ +/* @flow */ +import React from 'react'; +import { defineMessages } from 'react-intl'; +import { withPrefix } from 'gatsby'; + +import Heading from '~core/Heading'; +import Icon from '~core/Icon'; +import Link from '~core/Link'; +import { + COLONY_DISCOURSE, + COLONY_GITHUB, + COLONY_GITTER_COLONYJS, +} from '~routes'; + +import styles from './SupportCta.module.css'; + +const MSG = defineMessages({ + sectionTitle: { + id: 'parts.SupportCta.sectionTitle', + defaultMessage: 'Questions? Problems? Existential dilemmas? We can help!', + }, +}); + +type Props = {| + withBackground?: boolean, +|}; + +const displayName = 'parts.SupportCta'; + +const SupportCta = ({ withBackground = false }: Props) => { + const containerStyles = {}; + if (withBackground) { + containerStyles.backgroundImage = `url(${withPrefix( + '/img/devPortal_pattern_bg.svg', + )})`; + } + return ( +
+
+ +
+ + + + + + + + + +
+
+
+ ); +}; + +SupportCta.displayName = displayName; + +export default SupportCta; diff --git a/src/modules/pages/components/Developers/SupportCta/SupportCta.module.css b/src/modules/parts/components/SupportCta/SupportCta.module.css similarity index 88% rename from src/modules/pages/components/Developers/SupportCta/SupportCta.module.css rename to src/modules/parts/components/SupportCta/SupportCta.module.css index 89d4621b4..52180ea8d 100644 --- a/src/modules/pages/components/Developers/SupportCta/SupportCta.module.css +++ b/src/modules/parts/components/SupportCta/SupportCta.module.css @@ -21,6 +21,10 @@ fill: #289BDC; } +.iconItemLink:hover svg { + fill: #17567C; +} + .iconItemLink + .iconItemLink { margin-left: 37px; } diff --git a/src/modules/pages/components/Developers/SupportCta/index.js b/src/modules/parts/components/SupportCta/index.js similarity index 100% rename from src/modules/pages/components/Developers/SupportCta/index.js rename to src/modules/parts/components/SupportCta/index.js diff --git a/src/pages/tutorials.js b/src/pages/tutorials.js new file mode 100644 index 000000000..b0b36b711 --- /dev/null +++ b/src/pages/tutorials.js @@ -0,0 +1,8 @@ +/* @flow */ +import { createElement } from 'react'; + +import Tutorials from '~pages/Tutorials'; + +const TutorialsPage = () => createElement(Tutorials); + +export default TutorialsPage; diff --git a/src/queries/tutorial.js b/src/queries/tutorial.js new file mode 100644 index 000000000..9310ac999 --- /dev/null +++ b/src/queries/tutorial.js @@ -0,0 +1,34 @@ +/* @flow */ +import { graphql } from 'gatsby'; + +export const allTutorialsFragment = graphql` + fragment allTutorialsFragment on Query { + allTutorials: allTutorial { + edges { + node { + name + fields { + slug + } + } + } + } + } +`; + +// eslint-disable-next-line import/prefer-default-export +export const singleTutorialFragment = graphql` + fragment singleTutorialFragment on Query { + tutorial: markdownRemark(id: { eq: $tutorialId }) { + editUrl + excerpt + frontmatter { + author + order + publishDate(formatString: "YYYY/MM/DD") + title + } + htmlAst + } + } +`; diff --git a/src/routes/index.js b/src/routes/index.js index 9ec38fd0f..c62d0e03a 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -21,6 +21,8 @@ export const COLONY_BLOG = 'https://blog.colony.io'; export const COLONY_DISCOURSE = 'https://build.colony.io'; export const COLONY_DISCOURSE_SUPPORT = `${COLONY_DISCOURSE}/c/support`; export const COLONY_GITHUB = 'https://github.com/JoinColony'; +export const COLONY_GITHUB_TUTORIALS = + 'https://github.com/JoinColony/colonyTutorials'; export const COLONY_GITTER = 'https://gitter.im/JoinColony'; export const COLONY_GITTER_COLONYJS = `${COLONY_GITTER}/colonyJS`; export const COLONY_REDDIT = 'https://www.reddit.com/r/joincolony/'; diff --git a/src/styles/mixins.module.css b/src/styles/mixins.module.css index 3afbf2e77..c04e3daaf 100644 --- a/src/styles/mixins.module.css +++ b/src/styles/mixins.module.css @@ -5,3 +5,7 @@ text-decoration: none; transition: all 100ms cubic-bezier(0.4, 0, 0.2, 1); } + +.prettylink a:hover:not(:global(.anchor)) { + color: #17567C; +} diff --git a/src/types/doc.js b/src/types/doc.js index f8b214a3b..ed29b0083 100644 --- a/src/types/doc.js +++ b/src/types/doc.js @@ -1,20 +1,11 @@ /* @flow */ +import type { HtmlAst } from '~types'; + export type DocFields = {| locale: string, slug: string, |}; -export type HtmlAst = {| - children?: Array, - data?: { - quirksMode: boolean, - }, - properties?: Object, - tagName?: string, - type: string, - value?: string, -|}; - export type Doc = {| editUrl: string, fields: DocFields, diff --git a/src/types/htmlAst.js b/src/types/htmlAst.js new file mode 100644 index 000000000..d0330620f --- /dev/null +++ b/src/types/htmlAst.js @@ -0,0 +1,12 @@ +/* @flow */ + +export type HtmlAst = {| + children?: Array, + data?: { + quirksMode: boolean, + }, + properties?: Object, + tagName?: string, + type: string, + value?: string, +|}; diff --git a/src/types/index.js b/src/types/index.js index 0d7144103..264f52b10 100644 --- a/src/types/index.js +++ b/src/types/index.js @@ -1,4 +1,6 @@ /* @flow */ export * from './contexts'; export * from './doc'; +export * from './htmlAst'; export * from './project'; +export * from './tutorial'; diff --git a/src/types/tutorial.js b/src/types/tutorial.js new file mode 100644 index 000000000..54d85e96a --- /dev/null +++ b/src/types/tutorial.js @@ -0,0 +1,27 @@ +/* @flow */ +import type { HtmlAst } from '~types'; + +export type TutorialFrontmatter = {| + author: string, + exampleUrl?: string, + exampleLinkText?: string, + order: number, + publishDate: string, + title: string, +|}; + +export type Tutorial = {| + editUrl: string, + excerpt: string, + frontmatter: TutorialFrontmatter, + htmlAst: HtmlAst, + name: string, + slug: string, +|}; + +export type TutorialNode = {| + name: string, + fields: { + slug: string, + }, +|}; diff --git a/static/img/devPortal_pattern_bg.svg b/static/img/devPortal_pattern_bg.svg index 5791c2eeb..61b9f42ae 100644 --- a/static/img/devPortal_pattern_bg.svg +++ b/static/img/devPortal_pattern_bg.svg @@ -1,15 +1,768 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/social_gitter_devPortal.svg b/static/img/social_gitter_devPortal.svg old mode 100755 new mode 100644 index 33fc703e5..eee03b6e6 --- a/static/img/social_gitter_devPortal.svg +++ b/static/img/social_gitter_devPortal.svg @@ -1,7 +1,3 @@ - - Gitter - - - +