diff --git a/lib/commands/start.js b/lib/commands/start.js index 5b7a96e831..3dcc529b73 100644 --- a/lib/commands/start.js +++ b/lib/commands/start.js @@ -50,7 +50,7 @@ module.exports = async function start(siteDir, cliOptions = {}) { } const port = await getPort(cliOptions.port); - const hotPort = await getPort(port + 1); + const hotPort = await getPort(5555); const {baseUrl} = props; // create compiler from generated webpack config diff --git a/lib/core/App.js b/lib/core/App.js index 6cef21dbbe..dd1ca50162 100644 --- a/lib/core/App.js +++ b/lib/core/App.js @@ -3,5 +3,6 @@ import {renderRoutes} from 'react-router-config'; import routes from '@generated/routes'; // eslint-disable-line import docsData from '@generated/docsData'; // eslint-disable-line import pagesData from '@generated/pagesData'; // eslint-disable-line +import config from '@site/siteConfig.js'; //eslint-disable-line -export default () => renderRoutes(routes, {docsData, pagesData}); +export default () => renderRoutes(routes, {docsData, pagesData, config}); diff --git a/lib/theme/Docs.js b/lib/theme/Docs.js deleted file mode 100644 index ac697dde59..0000000000 --- a/lib/theme/Docs.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import Layout from '@theme/Layout'; - -export default class Docs extends React.Component { - render() { - return {this.props.children}; - } -} diff --git a/lib/theme/Docs/index.js b/lib/theme/Docs/index.js new file mode 100644 index 0000000000..5a6ff22aa4 --- /dev/null +++ b/lib/theme/Docs/index.js @@ -0,0 +1,38 @@ +/* eslint-disable */ +import React from 'react'; +import Helmet from 'react-helmet'; +import styles from './styles.css'; +import Layout from '@theme/Layout'; // eslint-disable-line + +export default class Docs extends React.Component { + render() { + const {location, docsData, config} = this.props; + const currentDoc = docsData.find(data => data.path === location.pathname); + + const highlight = Object.assign( + {}, + { + version: '9.12.0', + theme: 'default' + }, + config.highlight + ); + + // Use user-provided themeUrl if it exists, else construct one from version and theme. + const highlightThemeURL = highlight.themeUrl + ? highlight.themeUrl + : `//cdnjs.cloudflare.com/ajax/libs/highlight.js/${ + highlight.version + }/styles/${highlight.theme}.min.css`; + + return ( + + + {currentDoc.title || 'Document'} + + +
{this.props.children}
+
+ ); + } +} diff --git a/lib/theme/layout.css b/lib/theme/Docs/styles.css similarity index 100% rename from lib/theme/layout.css rename to lib/theme/Docs/styles.css diff --git a/lib/theme/Layout.js b/lib/theme/Layout.js deleted file mode 100644 index 259b8c37dc..0000000000 --- a/lib/theme/Layout.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import style from './layout.css'; - -export default class Layout extends React.Component { - render() { - const {children} = this.props; - return
{children}
; - } -} diff --git a/lib/theme/Layout/index.js b/lib/theme/Layout/index.js new file mode 100644 index 0000000000..fc677cc836 --- /dev/null +++ b/lib/theme/Layout/index.js @@ -0,0 +1,27 @@ +import React from 'react'; +import {Link} from 'react-router-dom'; +import styles from './styles.css'; + +/* eslint-disable react/prefer-stateless-function */ +export default class Layout extends React.Component { + render() { + console.log(this.props); + const {children, pagesData, docsData, location} = this.props; + const routeLinks = [...pagesData, ...docsData].map( + data => + data.path !== location.pathname && ( +
  • + {data.path} +
  • + ) + ); + return ( +
    + {children} +
    + +
    +
    + ); + } +} diff --git a/lib/theme/Layout/styles.css b/lib/theme/Layout/styles.css new file mode 100644 index 0000000000..fe65cd056e --- /dev/null +++ b/lib/theme/Layout/styles.css @@ -0,0 +1,38 @@ +.footer { + color: #777; + padding: 10px 15px; + height: 20px; + text-align: center; + border-top: 1px solid #e6e6e6; +} + +.routeLinks { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +.routeLinks li { + display: inline; +} + +.routeLinks li a { + color: inherit; + margin: 3px; + padding: 3px 7px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 3px; +} + +.routeLinks li a.selected, +.routeLinks li a:hover { + border-color: rgba(175, 47, 47, 0.1); +} + +.routeLinks li a.selected { + border-color: rgba(175, 47, 47, 0.2); +} \ No newline at end of file diff --git a/lib/webpack/server.js b/lib/webpack/server.js index 33b6fe0c29..bc236de34d 100644 --- a/lib/webpack/server.js +++ b/lib/webpack/server.js @@ -26,7 +26,7 @@ module.exports = function createServerConfig(props) { paths } ]); - + // show compilation progress bar and build time config .plugin('niceLog') diff --git a/website/components/Tictactoe/board.js b/website/components/Tictactoe/board.js new file mode 100644 index 0000000000..a189cb5a45 --- /dev/null +++ b/website/components/Tictactoe/board.js @@ -0,0 +1,36 @@ +import React from 'react'; +import Square from './square'; +import styles from './styles.css'; + +export default class Board extends React.Component { + renderSquare(i) { + return ( + this.props.onClick(i)} + /> + ); + } + + render() { + return ( +
    +
    + {this.renderSquare(0)} + {this.renderSquare(1)} + {this.renderSquare(2)} +
    +
    + {this.renderSquare(3)} + {this.renderSquare(4)} + {this.renderSquare(5)} +
    +
    + {this.renderSquare(6)} + {this.renderSquare(7)} + {this.renderSquare(8)} +
    +
    + ); + } +} diff --git a/website/components/Tictactoe/index.js b/website/components/Tictactoe/index.js new file mode 100644 index 0000000000..9602eaa1e7 --- /dev/null +++ b/website/components/Tictactoe/index.js @@ -0,0 +1,106 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import Layout from '@theme/Layout'; +import Board from './board'; +import styles from './styles.css'; + +class Game extends React.Component { + constructor(props) { + super(props); + this.state = { + history: [ + { + squares: Array(9).fill(null) + } + ], + stepNumber: 0, + xIsNext: true + }; + } + + calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6] + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if ( + squares[a] && + squares[a] === squares[b] && + squares[a] === squares[c] + ) { + return squares[a]; + } + } + return null; + } + + handleClick(i) { + const history = this.state.history.slice(0, this.state.stepNumber + 1); + const current = history[history.length - 1]; + const squares = current.squares.slice(); + if (this.calculateWinner(squares) || squares[i]) { + return; + } + squares[i] = this.state.xIsNext ? 'X' : 'O'; + this.setState({ + history: history.concat([ + { + squares: squares + } + ]), + stepNumber: history.length, + xIsNext: !this.state.xIsNext + }); + } + + jumpTo(step) { + this.setState({ + stepNumber: step, + xIsNext: step % 2 === 0 + }); + } + + render() { + const history = this.state.history; + const current = history[this.state.stepNumber]; + const winner = this.calculateWinner(current.squares); + + const moves = history.map((step, move) => { + const desc = move ? 'Go to move #' + move : 'Go to game start'; + return ( +
  • + +
  • + ); + }); + + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } + + return ( +
    +
    + this.handleClick(i)} /> +
    +
    +
    {status}
    +
      {moves}
    +
    +
    + ); + } +} + +export default Game; diff --git a/website/components/Tictactoe/square.js b/website/components/Tictactoe/square.js new file mode 100644 index 0000000000..1fed93a270 --- /dev/null +++ b/website/components/Tictactoe/square.js @@ -0,0 +1,10 @@ +import React from 'react'; +import styles from './styles.css'; + +export default props => { + return ( + + ); +}; diff --git a/website/pages/tictactoe.css b/website/components/Tictactoe/styles.css similarity index 81% rename from website/pages/tictactoe.css rename to website/components/Tictactoe/styles.css index 4a1a102c6e..5d5a305eba 100644 --- a/website/pages/tictactoe.css +++ b/website/components/Tictactoe/styles.css @@ -30,6 +30,9 @@ .game { display: flex; flex-direction: row; + margin-left: auto; + margin-right: auto; + justify-content: center; } .gameInfo { diff --git a/website/components/Todo/TodoItem.js b/website/components/Todo/TodoItem.js new file mode 100644 index 0000000000..a6a68879d5 --- /dev/null +++ b/website/components/Todo/TodoItem.js @@ -0,0 +1,94 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import classNames from 'classnames'; + +const ESCAPE_KEY = 27; +const ENTER_KEY = 13; + +export default class TodoItem extends React.Component { + constructor(props) { + super(props); + this.state = { + editText: props.todo.title + }; + this.handleEdit = this.handleEdit.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleChange = this.handleChange.bind(this); + } + + shouldComponentUpdate(nextProps, nextState) { + return ( + nextProps.todo !== this.props.todo || + nextProps.editing !== this.props.editing || + nextState.editText !== this.state.editText + ); + } + + componentDidUpdate(prevProps) { + if (!prevProps.editing && this.props.editing) { + const node = ReactDOM.findDOMNode(this.refs.editField); + node.focus(); + node.setSelectionRange(node.value.length, node.value.length); + } + } + + handleSubmit() { + const val = this.state.editText.trim(); + if (val) { + this.props.onSave(val); + this.setState({editText: val}); + } else { + this.props.onDestroy(); + } + } + + handleEdit() { + this.props.onEdit(); + this.setState({editText: this.props.todo.title}); + } + + handleKeyDown(event) { + if (event.which === ESCAPE_KEY) { + this.setState({editText: this.props.todo.title}); + this.props.onCancel(event); + } else if (event.which === ENTER_KEY) { + this.handleSubmit(event); + } + } + + handleChange(event) { + if (this.props.editing) { + this.setState({editText: event.target.value}); + } + } + + render() { + return ( +
  • +
    + + +
    + +
  • + ); + } +} diff --git a/website/components/Todo/TodoList.js b/website/components/Todo/TodoList.js new file mode 100644 index 0000000000..070a815c16 --- /dev/null +++ b/website/components/Todo/TodoList.js @@ -0,0 +1,29 @@ +import React from 'react'; +import TodoItem from './TodoItem'; + +export default function TodoList(props) { + const todoItems = props.todos.map(todo => ( + { + props.onToggle(todo); + }} + onDestroy={() => { + props.onDestroy(todo); + }} + onEdit={() => { + props.onEdit(todo); + }} + editing={props.editing(todo)} + onSave={text => { + props.onSave(todo, text); + }} + onCancel={() => { + props.onCancel(); + }} + /> + )); + + return
    {todoItems}
    ; +} diff --git a/website/components/Todo/index.js b/website/components/Todo/index.js new file mode 100644 index 0000000000..ec158d4a28 --- /dev/null +++ b/website/components/Todo/index.js @@ -0,0 +1,209 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import TodoList from './TodoList'; + +const ENTER_KEY = 13; + +function uuid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; +} + +const todos = [ + { + id: 'ed0bcc48-bbbe-5f06-c7c9-2ccb0456ceba', + title: 'Build this Todo App.', + completed: true + }, + { + id: '42582304-3c6e-311e-7f88-7e3791caf88c', + title: '?????', + completed: true + }, + { + id: '1cf63885-5f75-8deb-19dc-9b6765deae6c', + title: '1,000 stars on GitHub.', + completed: false + }, + { + id: '63a871b2-0b6f-4427-9c35-304bc680a4b7', + title: 'Write a popular medium post.', + completed: false + }, + { + id: '63a871b2-0b6f-4422-9c35-304bc680a4b7', + title: 'Earn money through open source work.', + completed: false + }, + { + id: '036af7f9-1181-fb8f-258f-3f06034c020f', + title: 'Write a blog post.', + completed: false + } +]; + +class TodoApp extends React.Component { + constructor(props) { + super(props); + + this.state = { + editing: null, + newTodo: '', + todos: todos + }; + } + + handleChange(event) { + this.setState({newTodo: event.target.value}); + } + + handleNewTodoKeyDown(event) { + if (event.keyCode !== ENTER_KEY) { + return; + } + + event.preventDefault(); + + const val = this.state.newTodo.trim(); + + if (val) { + this.setState({ + todos: this.state.todos.concat({ + id: uuid(), + title: val, + completed: false + }), + newTodo: '' + }); + } + } + + toggleAll(event) { + const {checked} = event.target; + this.setState({ + todos: this.state.todos.map(todo => + Object.assign({}, todo, {completed: checked}) + ) + }); + } + + toggle(todoToToggle) { + this.setState({ + todos: this.state.todos.map(todo => { + if (todo === todoToToggle) { + return Object.assign({}, todo, { + completed: !todo.completed + }); + } + return todo; + }) + }); + } + + destroy(passedTodo) { + this.setState({ + todos: this.state.todos.filter(todo => todo !== passedTodo) + }); + } + + edit(todo) { + this.setState({editing: todo.id}); + } + + save(todoToSave, text) { + this.setState({ + todos: this.state.todos.map(todo => { + if (todo === todoToSave) { + return Object.assign({}, todo, { + title: text + }); + } + return todo; + }), + editing: null + }); + } + + cancel() { + this.setState({editing: null}); + } + + clearCompleted() { + this.setState({ + todos: this.state.todos.filter(todo => !todo.completed) + }); + } + + render() { + let main; + const {todos} = this.state; + + const activeTodoCount = todos.reduce( + (accum, todo) => (todo.completed ? accum : accum + 1), + 0 + ); + + if (todos.length) { + main = ( +
    + +
      + { + this.toggle(todo); + }} + onDestroy={todo => { + this.destroy(todo); + }} + onEdit={todo => { + this.edit(todo); + }} + editing={todo => this.state.editing === todo.id} + onSave={(todo, text) => { + this.save(todo, text); + }} + onCancel={() => this.cancel()} + /> +
    +
    + ); + } + + return ( +
    + + Todo App + + +
    +

    todos

    + { + this.handleNewTodoKeyDown(event); + }} + onChange={event => { + this.handleChange(event); + }} + autoFocus + /> +
    + {main} +
    + ); + } +} + +export default TodoApp; diff --git a/website/pages/index.js b/website/pages/index.js index 5dd3022176..4b7db8f1e5 100644 --- a/website/pages/index.js +++ b/website/pages/index.js @@ -1,21 +1,17 @@ import React from 'react'; import Helmet from 'react-helmet'; -import {Link} from 'react-router-dom'; +import Layout from '@theme/Layout'; +import Todo from '@site/components/Todo'; export default class Home extends React.Component { render() { - const {pagesData, docsData} = this.props; - const routeLinks = [...pagesData, ...docsData].map(data => ( -
  • - {data.path} -
  • - )); return ( -
    - -

    Available Urls

    -
      {routeLinks}
    -
    + + + Homepage + + + ); } } diff --git a/website/pages/tictactoe.js b/website/pages/tictactoe.js index e9c93dd8a2..41072c4387 100644 --- a/website/pages/tictactoe.js +++ b/website/pages/tictactoe.js @@ -1,142 +1,17 @@ -import React from 'react'; -import Helmet from 'react-helmet'; -import style from './tictactoe.css'; - -function Square(props) { - return ( - - ); -} - -function calculateWinner(squares) { - const lines = [ - [0, 1, 2], - [3, 4, 5], - [6, 7, 8], - [0, 3, 6], - [1, 4, 7], - [2, 5, 8], - [0, 4, 8], - [2, 4, 6] - ]; - for (let i = 0; i < lines.length; i++) { - const [a, b, c] = lines[i]; - if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { - return squares[a]; - } - } - return null; -} - -class Board extends React.Component { - renderSquare(i) { - return ( - this.props.onClick(i)} - /> - ); - } - - render() { - return ( -
    -
    - {this.renderSquare(0)} - {this.renderSquare(1)} - {this.renderSquare(2)} -
    -
    - {this.renderSquare(3)} - {this.renderSquare(4)} - {this.renderSquare(5)} -
    -
    - {this.renderSquare(6)} - {this.renderSquare(7)} - {this.renderSquare(8)} -
    -
    - ); - } -} - -class Game extends React.Component { - constructor(props) { - super(props); - this.state = { - history: [ - { - squares: Array(9).fill(null) - } - ], - stepNumber: 0, - xIsNext: true - }; - } - - handleClick(i) { - const history = this.state.history.slice(0, this.state.stepNumber + 1); - const current = history[history.length - 1]; - const squares = current.squares.slice(); - if (calculateWinner(squares) || squares[i]) { - return; - } - squares[i] = this.state.xIsNext ? 'X' : 'O'; - this.setState({ - history: history.concat([ - { - squares: squares - } - ]), - stepNumber: history.length, - xIsNext: !this.state.xIsNext - }); - } - - jumpTo(step) { - this.setState({ - stepNumber: step, - xIsNext: step % 2 === 0 - }); - } - - render() { - const history = this.state.history; - const current = history[this.state.stepNumber]; - const winner = calculateWinner(current.squares); - - const moves = history.map((step, move) => { - const desc = move ? 'Go to move #' + move : 'Go to game start'; - return ( -
  • - -
  • - ); - }); - - let status; - if (winner) { - status = 'Winner: ' + winner; - } else { - status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); - } - - return ( -
    - -
    - this.handleClick(i)} /> -
    -
    -
    {status}
    -
      {moves}
    -
    -
    - ); - } -} - -export default Game; +import React from 'react'; +import Helmet from 'react-helmet'; +import Layout from '@theme/Layout'; +import Tictactoe from '@site/components/Tictactoe'; + +export default class Home extends React.Component { + render() { + return ( + + + Tic Tac Toe + + + + ); + } +}