diff --git a/.gitignore b/.gitignore index 964f8c52e7..7c940fe464 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ coverage/ dist/ docs/build/ docs/app/docgenInfo.json +docs/app/menuInfo.json dll/ .DS_Store diff --git a/docs/app/Components/ComponentDoc/ComponentDoc.js b/docs/app/Components/ComponentDoc/ComponentDoc.js index 8adb676aed..7fcef60c61 100644 --- a/docs/app/Components/ComponentDoc/ComponentDoc.js +++ b/docs/app/Components/ComponentDoc/ComponentDoc.js @@ -9,7 +9,9 @@ import { repoURL } from 'docs/app/utils' import { META } from 'src/lib' import * as semanticUIReact from 'src' import docgenInfo from '../../docgenInfo.json' +import menuInfo from '../../menuInfo.json' import ComponentExamples from './ComponentExamples' +import ComponentDocHeader from './ComponentDocHeader' import ComponentProps from './ComponentProps' const { Divider, Grid, Header, Icon, List } = semanticUIReact @@ -30,11 +32,6 @@ const getGithubSourceUrl = (componentName) => { return `${repoURL}/blob/master/${posixPath}` } -const getGithubEditUrl = (componentName) => { - const posixPath = getPosixPath(componentName) - return `${repoURL}/edit/master/${posixPath}` -} - const getSemanticUIDocsUrl = _meta => ( `https://semantic-ui.com/${_meta.type}s/${_meta.parent || _meta.name}`.toLowerCase() ) @@ -205,38 +202,18 @@ export default class ComponentDoc extends Component { ) } - renderHeader = () => { - const { _meta } = this.props - const docgen = docgenInfo[getDocgenPath(_meta.name)] - - return ( -
- {_meta.name} - - {/* TODO: Remove once all components have descriptions */} - {docgen.docBlock.description || ( - - Add a description. Instructions are{' '} - - here. - - {' '}Description is in the SUI Docs, right there - - )} - -
- ) - } - render() { const { _meta } = this.props + const docgen = docgenInfo[getDocgenPath(_meta.name)] return (
- {this.renderHeader()} + {this.renderSee()} {this.renderGithubSourceLink()} diff --git a/docs/app/Components/ComponentDoc/ComponentDocHeader.js b/docs/app/Components/ComponentDoc/ComponentDocHeader.js new file mode 100644 index 0000000000..3b806541d8 --- /dev/null +++ b/docs/app/Components/ComponentDoc/ComponentDocHeader.js @@ -0,0 +1,21 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Header } from 'semantic-ui-react' + +import { neverUpdate } from 'docs/app/HOC' + +const headerStyle = { marginBottom: '0.25em' } + +const ComponentDocHeader = ({ description, name }) ( +
+ {name} + {description} +
+) + +ComponentDocHeader.propTypes = { + description: PropTypes.string, + name: PropTypes.string, +} + +export default neverUpdate(ComponentDocHeader) diff --git a/gulp/plugins/gulp-menugen.js b/gulp/plugins/gulp-menugen.js new file mode 100644 index 0000000000..4b0a6bbcaf --- /dev/null +++ b/gulp/plugins/gulp-menugen.js @@ -0,0 +1,61 @@ +import gutil from 'gulp-util' +import _ from 'lodash' +import path from 'path' +import through from 'through2' + +import config from '../../config' +import { parseDocExample, parseDocSection } from './util' + +const examplesPath = `${config.paths.docsSrc()}/Examples/` + +export default (filename) => { + const defaultFilename = 'menuInfo.json' + const result = {} + const pluginName = 'gulp-menugen' + let finalFile + let latestFile + + function bufferContents(file, enc, cb) { + latestFile = file + + if (file.isNull()) { + cb(null, file) + return + } + + if (file.isStream()) { + cb(new gutil.PluginError(pluginName, 'Streaming is not supported')) + return + } + + try { + const relativePath = file.path.replace(examplesPath, '') + const [,component,section] = _.split(relativePath, '/') + + if(section === 'index.js') { + result[component] = parseDocExample(file.contents) + cb() + return + } + const { examples } = parseDocSection(file.contents) + + result[component][section]['examples'] = examples + // result[component][section] = 100 + cb() + } catch (err) { + const pluginError = new gutil.PluginError(pluginName, err) + pluginError.message += `\nFile: ${file.path}.` + this.emit('error', pluginError) + } + } + + function endStream(cb) { + finalFile = latestFile.clone({ contents: false }) + finalFile.path = path.join(latestFile.base, (filename || defaultFilename)) + finalFile.contents = new Buffer(JSON.stringify(result, null, 2)) + this.push(finalFile) + cb() + } + + return through.obj(bufferContents, endStream) +} diff --git a/gulp/plugins/util/index.js b/gulp/plugins/util/index.js index 97d3dfa8ac..6c60b8f63b 100644 --- a/gulp/plugins/util/index.js +++ b/gulp/plugins/util/index.js @@ -1,3 +1,5 @@ export parseDefaultValue from './parseDefaultValue' export parseDocBlock from './parseDocBlock' +export parseDocExample from './parseDocExample' +export parseDocSection from './parseDocSection' export parseType from './parseType' diff --git a/gulp/plugins/util/parseDocExample.js b/gulp/plugins/util/parseDocExample.js new file mode 100644 index 0000000000..492b38237c --- /dev/null +++ b/gulp/plugins/util/parseDocExample.js @@ -0,0 +1,34 @@ +import _ from 'lodash' +import { parse } from "babylon"; +import traverse from "babel-traverse"; + + +export default (buffer) => { + const ast = parse(buffer.toString(), { + sourceType: 'module', + plugins: [ + "classProperties", + "jsx", + ] + }); + const sections = {} + + traverse(ast, { + ImportDeclaration: (path) => { + const specifier = _.first(path.get('specifiers')) + + if(!specifier.isImportDefaultSpecifier()) return + + const name = _.get(specifier, 'node.local.name') + const source = _.get(path, 'node.source.value') + + if(_.startsWith(source, './')) { + sections[name] = { + name, + } + } + } + }); + + return sections +} \ No newline at end of file diff --git a/gulp/plugins/util/parseDocSection.js b/gulp/plugins/util/parseDocSection.js new file mode 100644 index 0000000000..263528d0d6 --- /dev/null +++ b/gulp/plugins/util/parseDocSection.js @@ -0,0 +1,46 @@ +import _ from 'lodash' +import { parse } from "babylon"; +import traverse from "babel-traverse"; + +export default (buffer) => { + const ast = parse(buffer.toString(), { + sourceType: 'module', + plugins: [ + "classProperties", + "jsx", + ] + }); + const section = { + examples: [] + } + let position = 0 + + traverse(ast, { + JSXOpeningElement: (path) => { + const attrs = _.map(_.get(path, 'node.attributes'), (a) => { + return { + name: _.get(a, 'name.name'), + value: _.get(a, 'value.value') + } + }) + const name = _.get(path, 'node.name.name') + + if(name === 'ExampleSection') { + const title = _.find(attrs, { name: 'title'}) + section.name = title.name + return + } + + if(name === 'ComponentExample') { + const title = _.find(attrs, { name: 'title'}) + if(title) { + const examplePath = _.find(attrs, { name: 'examplePath'}).value + + section.examples.push({ title: title.value, path: examplePath }) + } + } + } + }); + + return section +} \ No newline at end of file diff --git a/gulp/tasks/docs.js b/gulp/tasks/docs.js index 62377068b1..c7cd888dbe 100644 --- a/gulp/tasks/docs.js +++ b/gulp/tasks/docs.js @@ -8,6 +8,7 @@ import WebpackDevMiddleware from 'webpack-dev-middleware' import WebpackHotMiddleware from 'webpack-hot-middleware' import config from '../../config' +import gulpMenuGen from '../plugins/gulp-menugen' import gulpReactDocgen from '../plugins/gulp-react-docgen' const g = loadPlugins() @@ -45,6 +46,16 @@ task('build:docs:docgen', () => src([ .pipe(gulpReactDocgen()) .pipe(dest(config.paths.docsSrc()))) +task('build:docs:menugen', () => src(`${config.paths.docsSrc()}/Examples/**/index.js`) +// do not remove the function keyword +// we need 'this' scope here + .pipe(g.plumber(function handleError(err) { + log(err.toString()) + this.emit('end') + })) + .pipe(gulpMenuGen()) + .pipe(dest(config.paths.docsSrc()))) + task('build:docs:html', () => src(config.paths.docsSrc('404.html')) .pipe(dest(config.paths.docsDist())))