diff --git a/.eslintignore b/.eslintignore
index 84cf66469e19d..164ae5ac2c7ef 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,4 @@
build
coverage
-docs
vendor
+node_modules
diff --git a/.eslintrc.json b/.eslintrc.json
index 76fc971b5fea4..a1e9739eda4b4 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -6,14 +6,14 @@
"plugin:react/recommended",
"plugin:jsx-a11y/recommended",
"plugin:jest/recommended"
- ],
+ ],
"env": {
"browser": false,
"es6": true,
"node": true,
"mocha": true,
"jest/globals": true
- },
+ },
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
diff --git a/.gitignore b/.gitignore
index 69f1023725d88..11169828c8085 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,6 @@
build
coverage
node_modules
-storybook-static
gutenberg.zip
# Directories/files that may appear in your environment
diff --git a/.storybook/addons.js b/.storybook/addons.js
deleted file mode 100644
index 5659100510d17..0000000000000
--- a/.storybook/addons.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import '@storybook/addon-knobs/register';
-import '@storybook/addon-options/register';
diff --git a/.storybook/config.js b/.storybook/config.js
deleted file mode 100644
index 35d9b93b2b298..0000000000000
--- a/.storybook/config.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * External dependencies
- */
-import 'prismjs';
-import { configure, setAddon } from '@storybook/react';
-import infoAddon from '@storybook/addon-info';
-import { setOptions } from '@storybook/addon-options';
-
-/**
- * Internal dependencies
- */
-import * as element from 'element';
-import './style.scss';
-
-function loadStories() {
- window.wp = { ...window.wp, element };
- require( './stories/intro' );
- require( './stories/contributing' );
- require( './stories/coding-guidelines' );
- require( './stories/design' );
- require( '../i18n/story' );
- require( '../element/story' );
- require( '../blocks/story' );
- require( '../editor/story' );
- require( '../components/story' );
- require( '../components/button/story' );
- require( '../components/higher-order/story' );
- require( '../components/higher-order/with-instance-id/story' );
-}
-
-setOptions( {
- name: 'Gutenberg',
- url: 'https://github.com/WordPress/gutenberg',
- goFullScreen: false,
- showLeftPanel: true,
- showDownPanel: true,
- showSearchBox: false,
- downPanelInRight: true,
- sortStoriesByKind: false,
-} );
-setAddon( infoAddon );
-
-configure( loadStories, module );
diff --git a/.storybook/stories/coding-guidelines.js b/.storybook/stories/coding-guidelines.js
deleted file mode 100644
index 85ec888d93f41..0000000000000
--- a/.storybook/stories/coding-guidelines.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * External dependencies
- */
-import ReactMarkdown from 'react-markdown';
-import { storiesOf } from '@storybook/react';
-import { withKnobs } from '@storybook/addon-knobs';
-
-/**
- * Internal dependencies
- */
-import readme from '../../docs/coding-guidelines.md';
-
-storiesOf( 'Gutenberg', module )
- .addDecorator( withKnobs )
- .add( 'Coding Guidelines', () =>
Hello saved content.
; + }, +} ); +``` +{% end %} + +Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/). + +A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. + +The `edit` and `save` functions describe the structure of your block in the context of the editor and the saved content respectively. While the difference is not obvious in this simple example, in the following sections we'll explore how these are used to enable customization of the block in the editor preview. diff --git a/docs/blocks-controls.md b/docs/blocks-controls.md new file mode 100644 index 0000000000000..bb5699324c579 --- /dev/null +++ b/docs/blocks-controls.md @@ -0,0 +1,152 @@ +# Block Controls: Toolbars and Inspector + +To simplify block customization and ensure a consistent experience for users, there are a number of built-in UI patterns to help generate the editor preview. Like with the `Editable` component covered in the previous chapter, the `wp.blocks` global includes a few other common components to render editing interfaces. In this chapter, we'll explore toolbars and the block inspector. + +## Toolbar + + + +When the user selects a block, a number of control buttons may be shown in a toolbar above the selected block. Some of these block-level controls are included automatically if the editor is able to transform the block to another type, or if the focused element is an Editable component. + +You can also customize the toolbar to include controls specific to your block type. If the return value of your block type's `edit` function includes a `BlockControls` element, those controls will be shown in the selected block's toolbar. + +{% codetabs %} +{% ES5 %} +```js +var el = wp.element.createElement, + registerBlockType = wp.blocks.registerBlockType, + Editable = wp.blocks.Editable, + BlockControls = wp.blocks.BlockControls, + AlignmentToolbar = wp.blocks.AlignmentToolbar, + children = wp.blocks.query.children; + +registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-04', { + title: 'Hello World (Step 4)', + + icon: 'universal-access-alt', + + category: 'layout', + + attributes: { + content: children( 'p' ), + }, + + edit: function( props ) { + var content = props.attributes.content, + alignment = props.attributes.alignment, + focus = props.focus; + + function onChangeContent( newContent ) { + props.setAttributes( { content: newContent } ); + } + + function onChangeAlignment( newAlignment ) { + props.setAttributes( { alignment: newAlignment } ); + } + + return [ + !! focus && el( + BlockControls, + { key: 'controls' }, + el( + AlignmentToolbar, + { + value: alignment, + onChange: onChangeAlignment + } + ) + ), + el( + Editable, + { + key: 'editable', + tagName: 'p', + className: props.className, + style: { textAlign: alignment }, + onChange: onChangeContent, + value: content, + focus: focus, + onFocus: props.setFocus + } + ) + ]; + }, + + save: function( props ) { + var content = props.attributes.content; + + return el( 'p', { className: props.className }, content ); + }, +} ); +``` +{% ESNext %} +```js +const { + registerBlockType, + Editable, + BlockControls, + AlignmentToolbar, + query +} = wp.blocks; +const { children } = children; + +registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-04', { + title: 'Hello World (Step 4)', + + icon: 'universal-access-alt', + + category: 'layout', + + attributes: { + content: children( 'p' ), + }, + + edit( { attributes, setAttributes, focus } ) { + const { content, alignment } = attributes; + + function onChangeContent( newContent ) { + setAttributes( { content: newContent } ); + } + + function onChangeAlignment( newAlignment ) { + setAttributes( { alignment: newAlignment } ); + } + + return [ + !! focus && ( +{ content }
; + }, +} ); +``` +{% end %} + +Note that you should only include `BlockControls` if the block is currently selected. We must test that the `focus` value is truthy before rendering the element, otherwise you will inadvertently cause controls to be shown for the incorrect block type. + +## Inspector + +While the toolbar area is useful for displaying controls to toggle attributes of a block, sometimes you will find that you need more screen space for form fields. The inspector region is shown in place of the post settings sidebar when a block is selected. + +Similar to rendering a toolbar, if you include an `InspectorControls` element in the return value of your block type's `edit` function, those controls will be shown in the inspector region. diff --git a/docs/blocks-editable.md b/docs/blocks-editable.md new file mode 100644 index 0000000000000..c0b40f2a29076 --- /dev/null +++ b/docs/blocks-editable.md @@ -0,0 +1,114 @@ +# Introducing Attributes and Editable Fields + +Our example block is still not very interesting because it lacks options to customize the appearance of the message. In this section, we will implement an editable field allowing the user to specify their own message. Before doing so, it's important to understand how the state of a block (its "attributes") is maintained and changed over time. + +## Attributes + +Until now, the `edit` and `save` functions have returned a simple representation of a paragraph element. We also learned how these functions are responsible for _describing_ the structure of the block's appearance. If the user changes a block, this structure may need to change. To achieve this, the state of a block is maintained throughout the editing session as a plain JavaScript object, and when an update occurs, the `edit` function is invoked again. Put another way: __the output of a block is a function of its attributes__. + +One challenge of maintaining the representation of a block as a JavaScript object is that we must be able to extract this object again from the saved content of a post. This is achieved with the block type's `attribute` property: + +{% codetabs %} +{% ES5 %} +```js +var el = wp.element.createElement, + registerBlockType = wp.blocks.registerBlockType, + Editable = wp.blocks.Editable, + children = wp.blocks.query.children; + +registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-03', { + title: 'Hello World (Step 3)', + + icon: 'universal-access-alt', + + category: 'layout', + + attributes: { + content: children( 'p' ), + }, + + edit: function( props ) { + var content = props.attributes.content, + focus = props.focus; + + function onChangeContent( newContent ) { + props.setAttributes( { content: newContent } ); + } + + return el( + Editable, + { + tagName: 'p', + className: props.className, + onChange: onChangeContent, + value: content, + focus: focus, + onFocus: props.setFocus + } + ); + }, + + save: function( props ) { + var content = props.attributes.content; + + return el( 'p', { className: props.className }, content ); + }, +} ); +``` +{% ESNext %} +```js +const { registerBlockType, Editable, query } = wp.blocks; +const { children } = query; + +registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-03', { + title: 'Hello World (Step 3)', + + icon: 'universal-access-alt', + + category: 'layout', + + attributes: { + content: children( 'p' ), + }, + + edit( { attributes, setAttributes, focus, className } ) { + const { content } = attributes; + + function onChangeContent( newContent ) { + setAttributes( { content: newContent } ); + } + + return ( +{ content }
; + }, +} ); +``` +{% end %} + +When registering a new block type, the `attributes` property describes the shape of the attributes object you'd like to receive in the `edit` and `save` functions. Each value is a [matcher function]() to find the desired value from the markup of the block. + +In the code snippet above, when loading the editor, we will extract the `content` value as the children of the paragraph element in the saved post's markup. + +## Components and the `Editable` Component + +Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into ["components"](). This abstraction helps as a pattern to share common behaviors and to hide complexity into self-contained units. There are a number of components available to use in implementing your blocks. You can see one such component in the snippet above: the [`Editable` component](). + +The `Editable` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. It is not too much unlike the single editor region of the legacy post editor, and is in fact powered by the same TinyMCE library. + +Implementing this behavior as a component enables you as the block implementer to be much more granular about editable fields. Your block may not need `Editable` at all, or it may need many independent `Editable` elements, each operating on a subset of the overall block state. + +Because `Editable` allows for nested nodes, you'll most often use it in conjunction with the `children` attribute matcher when extracting the value from saved content. diff --git a/docs/blocks-stylesheet.md b/docs/blocks-stylesheet.md new file mode 100644 index 0000000000000..9fc07fff8ba11 --- /dev/null +++ b/docs/blocks-stylesheet.md @@ -0,0 +1,105 @@ +# Applying Styles From a Stylesheet + +In the previous section, the block had applied its own styles by an inline `style` attribute. While this might be adequate for very simple components, you will quickly find that it becomes easier to write your styles by extracting them to a separate stylesheet file. + +The editor will automatically generate a class name for each block type to simplify styling. It can be accessed from the object argument passed to the edit and save functions: + +{% codetabs %} +{% ES5 %} +```js +var el = wp.element.createElement, + registerBlockType = wp.blocks.registerBlockType; + +registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-02', { + title: 'Hello World (Step 2)', + + icon: 'universal-access-alt', + + category: 'layout', + + edit: function( props ) { + return el( 'p', { className: props.className }, 'Hello editor.' ); + }, + + save: function( props ) { + return el( 'p', { className: props.className }, 'Hello saved content.' ); + }, +} ); +``` +{% ESNext %} +```js +const { registerBlockType } = wp.blocks; + +registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-02', { + title: 'Hello World (Step 2)', + + icon: 'universal-access-alt', + + category: 'layout', + + edit( { className } ) { + returnHello editor.
; + }, + + save( { className } ) { + returnHello saved content.
; + }, +} ); +``` +{% end %} + +The class name is generated using the block's name prefixed with `wp-block-`, replacing the `/` namespace separator with a single `-`. + +```css +.wp-block-gutenberg-boilerplate-es5-hello-world-step-02 { + color: green; + background: #cfc; + border: 2px solid #9c9; + padding: 20px; +} +``` + +## Enqueuing Block Styles + +Like scripts, your block's editor-specific styles should be enqueued during the `enqueue_block_editor_assets` action. + +```php +kickoff post: +The all-encompassing goal of Gutenberg is to create a post and page building experience that makes it easy to create _rich post layouts_. From the [kickoff post](https://make.wordpress.org/core/2017/01/04/focus-tech-and-design-leads/): > The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery. diff --git a/docs/faq.md b/docs/faq.md index 8b590470c348f..21c2115c243da 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,4 +1,4 @@ -# Gutenberg F.A.Q. +# Frequently Asked Questions ## What is Gutenberg? @@ -132,7 +132,7 @@ $blocks = $parser->parse( $post_content ); Blocks are likely to become the main way users interact with content. Users are going to be discovering functionality in the new universal inserter tool, with a richer block interface that provides more layout opportunities. ## What features will be available at launch? What does the post-launch roadmap look like? -As part of the focus on the editor in 2017, a focus on customization and sitebuilding is next. From the kickoff post: +As part of the focus on the editor in 2017, a focus on customization and sitebuilding is next. From [the kickoff post](https://make.wordpress.org/core/2017/01/04/focus-tech-and-design-leads/): > The customizer will help out the editor at first, then shift to bring those fundamental building blocks into something that could allow customization “outside of the box” of post_content, including sidebars and possibly even an entire theme. diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 0000000000000..70a7fff8353ec --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,15 @@ +# Glossary + +- __Attribute matchers__: An object describing the attributes shape of a block. The keys can be named as most appropriate to describe the state of a block type. The value for each key is a function which describes the strategy by which the attribute value should be extracted from the content of a saved post's content. When processed, a new object is created, taking the form of the keys defined in the attribute matchers, where each value is the result of the attribute matcher function. +- __Attributes__: The object representation of the current state of a block in post content. When loading a saved post, this is determined by the attribute matchers for the block type. These values can change over time during an editing session when the user modifies a block, and are used when determining how to serialize the block. +- __Block__: The abstract term used to describe units of markup that, composed together, form the content or layout of a webpage. The idea combines concepts of what in WordPress today we achieve with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. +- __Block name__: A unique identifier for a block type, consisting of a plugin-specific namespace and a short label describing the block's intent. e.g. `core/image` +- __Block type__: In contrast with the blocks composing a particular post, a block type describes the blueprint by which any block of that type should behave. So while there may be many images within a post, each behaves consistent with a unified image block type definition. +- __Dynamic block__: A type of block where the content of which may change and cannot be determined at the time of saving a post, instead calculated any time the post is shown on the front of a site. These blocks may save fallback content or no content at all in their JavaScript implementation, instead deferring to a PHP block implementation for runtime rendering. +- __Editable__: A common component enabling rich content editing including bold, italics, hyperlinks, etc. It is not too much unlike the single editor region of the legacy post editor, and is in fact powered by the same TinyMCE library. +- __Inspector__: A block settings region shown in place of the post settings when a block is selected. Fields may be shown here to allow the user to customize the selected block. +- __Post settings__: A sidebar region containing metadata fields for the post, including scheduling, visibility, terms, and featured image. +- __Serialization__: The process of converting a block's attributes object into HTML markup, typically occurring when saving the post. +- __Static block__: A type of block where the content of which is known at the time of saving a post. A static block will be saved with HTML markup directly in post content. +- __TinyMCE__: [TinyMCE](https://www.tinymce.com/) is a web-based JavaScript WYSIWYG (What You See Is What You Get) editor. +- __Toolbar__: A set of button controls. In the context of a block, usually referring to the toolbar of block controls shown above the selected block. diff --git a/docs/index.js b/docs/index.js new file mode 100644 index 0000000000000..cff85abc0e16d --- /dev/null +++ b/docs/index.js @@ -0,0 +1,86 @@ +/** + * WordPress dependencies + */ +import { addStory } from 'docutron'; + +addStory( { + name: 'intro', + title: 'Introduction', + path: '/', + markdown: require( './readme.md' ), +} ); + +addStory( { + name: 'blocks', + title: 'Creating Block Types', + markdown: require( './blocks.md' ), +} ); + +addStory( { + parents: [ 'blocks' ], + name: 'writing-your-first-block-type', + title: 'Writing Your First Block Type', + markdown: require( 'blocks-basic.md' ), +} ); + +addStory( { + parents: [ 'blocks' ], + name: 'applying-styles-with-stylesheets', + title: 'Applying Styles With Stylesheets', + markdown: require( './blocks-stylesheet.md' ), +} ); + +addStory( { + parents: [ 'blocks' ], + name: 'introducing-attributes-and-editable-fields', + title: 'Introducing Attributes and Editable Fields', + markdown: require( './blocks-editable.md' ), +} ); + +addStory( { + parents: [ 'blocks' ], + name: 'block-controls-toolbars-and-inspector', + title: 'Block Controls: Toolbars and Inspector', + markdown: require( './blocks-controls.md' ), +} ); + +addStory( { + name: 'reference', + title: 'Reference', + markdown: require( './reference.md' ), +} ); + +addStory( { + parents: [ 'reference' ], + name: 'attribute-matchers', + title: 'Attribute Matchers', + markdown: require( './attribute-matchers.md' ), +} ); + +addStory( { + parents: [ 'reference' ], + name: 'glossary', + title: 'Glossary', + markdown: require( './glossary.md' ), +} ); + +addStory( { + parents: [ 'reference' ], + name: 'design-principles', + title: 'Design Principles', + markdown: require( './design.md' ), +} ); + +addStory( { + parents: [ 'reference' ], + name: 'coding-guidelines', + title: 'Coding Guidelines', + markdown: require( './coding-guidelines.md' ), +} ); + +addStory( { + parents: [ 'reference' ], + name: 'faq', + title: 'Frequently Asked Questions', + markdown: require( './faq.md' ), +} ); diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000000000..55afe27762232 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,13 @@ +# Introduction + +"Gutenberg" is the codename for the 2017 WordPress editor focus. The goal if this focus is to create a new post and page editing experience that makes it easy for anyone to create rich post layouts. This was the kickoff goal: + +> The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery. + +Key take-aways from parsing that paragraph: + +- Authoring richly laid out posts is a key strength of WordPress. +- By embracing "the block", we can potentially unify multiple different interfaces into one. Instead of learning how to write shortcodes, custom HTML, or paste URLs to embed, you should do with just learning the block, and all the pieces should fall in place. +- "Mystery meat" refers to hidden features in software, features that you have to discover. WordPress already supports a large amount of blocks and 30+ embeds, so let's surface them. + +Gutenberg is being developed on [GitHub](https://github.com/WordPress/gutenberg), and you can try [an early beta version today from the plugin repository](https://wordpress.org/plugins/gutenberg/). Though keep in mind it's not fully functional, feature complete, or production ready. diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 0000000000000..3313c417fcd69 --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,7 @@ +# Reference + +- [Attribute Matchers](./reference/attribute-matchers) +- [Glossary](./reference/glossary) +- [Design Principles](./reference/design-principles) +- [Coding Guidelines](./reference/coding-guidelines) +- [Frequently Asked Questions](./reference/faq) diff --git a/docutron/.eslintrc.json b/docutron/.eslintrc.json new file mode 100644 index 0000000000000..05ca3cd3a5f8a --- /dev/null +++ b/docutron/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "env": { + "worker": true + }, + "settings": { + "react": { + "pragma": "React" + } + } +} diff --git a/docutron/.gitignore b/docutron/.gitignore new file mode 100644 index 0000000000000..d30f40ef4422f --- /dev/null +++ b/docutron/.gitignore @@ -0,0 +1,21 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +/node_modules + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/docutron/bin/build.js b/docutron/bin/build.js new file mode 100644 index 0000000000000..617372abb1d65 --- /dev/null +++ b/docutron/bin/build.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +/** + * External Dependencies + */ +const path = require( 'path' ); + +/** + * Internal Dependencies + */ +const extendsConfig = require( './helpers/extend-webpack-config' ); + +const usersCwd = process.cwd(); +const docsFolder = process.argv[ 2 ]; + +// webpack.config.prod.js checks this. +process.env.NODE_ENV = 'production'; + +// Load and edit the create-react-app config. +process.chdir( path.resolve( __dirname, '../' ) ); +const webpackConfig = require( 'react-scripts/config/webpack.config.prod' ); +extendsConfig( webpackConfig, usersCwd, docsFolder ); + +// Run the build. +require( 'react-scripts/scripts/build' ); diff --git a/docutron/bin/cli.js b/docutron/bin/cli.js new file mode 100755 index 0000000000000..73b4dfc5e610f --- /dev/null +++ b/docutron/bin/cli.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +/** + * External Dependencies + */ +const path = require( 'path' ); +const childProcess = require( 'child_process' ); + +const command = process.argv[ 2 ]; +const docsFolder = process.argv[ 3 ] || 'docs'; + +if ( command === 'start' ) { + childProcess.execSync( 'node ' + path.resolve( __dirname, 'start.js ' + docsFolder ), { stdio: [ 0, 1, 2 ] } ); +} else if ( command === 'build' ) { + childProcess.execSync( 'node ' + path.resolve( __dirname, 'build.js ' + docsFolder ), { stdio: [ 0, 1, 2 ] } ); + childProcess.execSync( 'cd ' + path.resolve( __dirname, '../' ) + ' && ./node_modules/.bin/react-snapshot', { stdio: [ 0, 1, 2 ] } ); +} else { + console.error( 'Unknown command ' + command ); // eslint-disable-line no-console + process.exit( 1 ); +} diff --git a/docutron/bin/helpers/extend-webpack-config.js b/docutron/bin/helpers/extend-webpack-config.js new file mode 100644 index 0000000000000..224ea575cf30e --- /dev/null +++ b/docutron/bin/helpers/extend-webpack-config.js @@ -0,0 +1,36 @@ +/** + * External Dependencies + */ +const path = require( 'path' ); + +module.exports = function( webpackConfig, usersCwd, docsFolder ) { + // Adding "docutron" alias + webpackConfig.resolve.alias.docutron = path.resolve( __dirname, '../../src/config/' ); + + // Loading the config folder + webpackConfig.resolve.alias.config = path.resolve( usersCwd, docsFolder ); + webpackConfig.resolve.modules = webpackConfig.resolve.modules.concat( [ webpackConfig.resolve.alias.config ] ); + + // Using the user's node_modules + const usersNodeModules = path.resolve( usersCwd, 'node_modules' ); + webpackConfig.resolve.modules = webpackConfig.resolve.modules.concat( [ usersNodeModules ] ); + + // Deleting CRA scoping + webpackConfig.resolve.plugins = []; + webpackConfig.module.rules.forEach( ( rule ) => { + if ( rule.include ) { + rule.include = [ rule.include, webpackConfig.resolve.alias.config ]; + } + } ); + + // Adding the markdown loader and exclude if from the file loader + webpackConfig.module.rules.forEach( rule => { + if ( rule.loader === require.resolve( 'file-loader' ) ) { + rule.exclude.push( /\.md/ ); + } + } ); + webpackConfig.module.rules.push( { + test: /\.md/, + use: require.resolve( 'raw-loader' ), + } ); +}; diff --git a/docutron/bin/start.js b/docutron/bin/start.js new file mode 100644 index 0000000000000..d72d5a27e4534 --- /dev/null +++ b/docutron/bin/start.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +/** + * External Dependencies + */ +const path = require( 'path' ); + +/** + * Internal Dependencies + */ +const extendConfig = require( './helpers/extend-webpack-config' ); + +const usersCwd = process.cwd(); +const docsFolder = process.argv[ 2 ]; + +// webpack.config.prod.js checks this. +process.env.NODE_ENV = 'development'; + +// Load and edit the create-react-app config. +process.chdir( path.resolve( __dirname, '../' ) ); +const webpackConfig = require( 'react-scripts/config/webpack.config.dev' ); +extendConfig( webpackConfig, usersCwd, docsFolder ); + +// Run the build. +require( 'react-scripts/scripts/start' ); diff --git a/docutron/package.json b/docutron/package.json new file mode 100644 index 0000000000000..028386ad1ef94 --- /dev/null +++ b/docutron/package.json @@ -0,0 +1,21 @@ +{ + "name": "docutron", + "version": "5000.0.0", + "dependencies": { + "lodash": "^4.17.4", + "markdown-it": "^8.3.1", + "markdown-it-prism": "^1.1.1", + "markdown-it-toc-and-anchor": "^4.1.2", + "prismjs": "^1.6.0", + "react": "^15.6.1", + "react-dom": "^15.6.1", + "react-router-dom": "^4.1.1", + "react-scripts": "1.0.10", + "react-snapshot": "^1.1.0", + "store": "^2.0.12" + }, + "devDependencies": {}, + "bin": { + "docutron": "./bin/cli.js" + } +} diff --git a/docutron/public/index.html b/docutron/public/index.html new file mode 100644 index 0000000000000..4a04d5ece7745 --- /dev/null +++ b/docutron/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + ++ + ← { previousStory.title } + +
+ ) } + { !! nextStory && ( ++ + { nextStory.title } → + +
+ ) } +