From 14f02a0b50c4b250be5a8a7ce4559d39bab4fe86 Mon Sep 17 00:00:00 2001 From: Josh Barr Date: Wed, 24 Feb 2016 12:44:14 +0200 Subject: [PATCH] Tooling for modern front-end components: React JS, ES6, and BEM CSS Thanks to @justinvdm for the help Merges: #2275 --- .babelrc | 6 ++ .drone.yml | 7 +- .eslintignore | 34 ++++++++ .eslintrc | 25 ++++++ .gitignore | 1 + .jscsrc | 37 --------- client/.npmignore | 0 client/README.md | 42 ++++++++++ client/package.json | 18 ++++ client/scss/_components.scss | 3 + client/scss/_objects.scss | 1 + client/scss/objects/_o.icon.scss | 3 + client/scss/states/_animations.scss | 4 + client/scss/states/_states.scss | 0 client/scss/style.scss | 10 +++ client/scss/themes/_themes.scss | 0 client/scss/utilities/_utilities.scss | 9 ++ client/src/cli/component.js | 83 +++++++++++++++++++ client/src/cli/index.js | 15 ++++ client/src/components/explorer/README.md | 1 + .../src/components/explorer/explorer-item.js | 28 +++++++ client/src/components/explorer/index.js | 67 +++++++++++++++ client/src/components/explorer/style.scss | 13 +++ client/src/components/index.js | 7 ++ .../components/loading-indicator/README.md | 1 + .../src/components/loading-indicator/index.js | 9 ++ .../components/loading-indicator/style.scss | 3 + .../src/components/state-indicator/README.md | 9 ++ .../src/components/state-indicator/index.js | 16 ++++ .../src/components/state-indicator/style.scss | 5 ++ client/src/config/index.js | 1 + client/src/index.js | 1 + client/template/README.mst | 9 ++ client/template/component.mst | 15 ++++ client/template/style.mst | 5 ++ client/tests/components/explorer.test.js | 10 +++ gulpfile.js/tasks/watch.js | 1 + package.json | 43 ++++++++-- wagtail/utils/setup.py | 37 +++++++++ .../wagtailadmin/app/wagtailadmin.entry.js | 23 +++++ .../static_src/wagtailadmin/scss/core.scss | 8 +- .../templates/wagtailadmin/admin_base.html | 1 + .../templates/wagtailadmin/skeleton.html | 2 +- wagtail/wagtailcore/__init__.py | 2 + webpack.base.config.js | 77 +++++++++++++++++ webpack.dev.config.js | 8 ++ webpack.prd.config.js | 8 ++ 47 files changed, 657 insertions(+), 51 deletions(-) create mode 100644 .babelrc create mode 100644 .eslintignore create mode 100644 .eslintrc delete mode 100644 .jscsrc create mode 100644 client/.npmignore create mode 100644 client/README.md create mode 100644 client/package.json create mode 100644 client/scss/_components.scss create mode 100644 client/scss/_objects.scss create mode 100644 client/scss/objects/_o.icon.scss create mode 100644 client/scss/states/_animations.scss create mode 100644 client/scss/states/_states.scss create mode 100644 client/scss/style.scss create mode 100644 client/scss/themes/_themes.scss create mode 100644 client/scss/utilities/_utilities.scss create mode 100644 client/src/cli/component.js create mode 100755 client/src/cli/index.js create mode 100644 client/src/components/explorer/README.md create mode 100644 client/src/components/explorer/explorer-item.js create mode 100644 client/src/components/explorer/index.js create mode 100644 client/src/components/explorer/style.scss create mode 100644 client/src/components/index.js create mode 100644 client/src/components/loading-indicator/README.md create mode 100644 client/src/components/loading-indicator/index.js create mode 100644 client/src/components/loading-indicator/style.scss create mode 100644 client/src/components/state-indicator/README.md create mode 100644 client/src/components/state-indicator/index.js create mode 100644 client/src/components/state-indicator/style.scss create mode 100644 client/src/config/index.js create mode 100644 client/src/index.js create mode 100644 client/template/README.mst create mode 100644 client/template/component.mst create mode 100644 client/template/style.mst create mode 100644 client/tests/components/explorer.test.js create mode 100644 wagtail/wagtailadmin/static_src/wagtailadmin/app/wagtailadmin.entry.js create mode 100644 webpack.base.config.js create mode 100644 webpack.dev.config.js create mode 100644 webpack.prd.config.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..bd20dc1799 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "es2015", + "react" + ] +} diff --git a/.drone.yml b/.drone.yml index 70ae33a0fd..71fe23ad51 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,11 +6,12 @@ build: commands: - XDG_CACHE_HOME=/drone/pip-cache pip install flake8 - flake8 wagtail - jscs: + js: image: node:4.2.4 commands: - - npm install -g jscs@"^1.12.0" --quiet - - jscs ./wagtail + - npm install --quiet + - npm run lint + - npm run test:unit scss-lint: image: wagtail-scss-lint commands: diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..d95352240c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,34 @@ +node_modules +*.min.js +**/lib/ +public/ +coverage/ +gulp/ +**/vendor/ +gulpfile.js +client/src/cli +wagtail/wagtailadmin/static +wagtail/wagtaildocs/static +wagtail/wagtailimages/static +wagtail/wagtailimages/static +wagtail/wagtailembeds/static +wagtail/wagtailsnippets/static +wagtail/wagtailusers/static +wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/inline_panel.js +wagtail/contrib/wagtailsearchpromotions/templates/wagtailsearchpromotions/includes/searchpromotions_formset.js +wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.js +wagtail/wagtailsnippets/templates/wagtailsnippets/chooser/chosen.js +wagtail/wagtailimages/templates/wagtailimages/chooser/image_chosen.js +wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.js +wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/chooser.js +wagtail/wagtailimages/templates/wagtailimages/chooser/select_format.js +wagtail/wagtailembeds/templates/wagtailembeds/chooser/embed_chosen.js +wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.js +wagtail/wagtaildocs/templates/wagtaildocs/chooser/chooser.js +wagtail/wagtaildocs/templates/wagtaildocs/chooser/document_chosen.js +wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.js +wagtail/wagtailadmin/templates/wagtailadmin/chooser/external_link_chosen.js +wagtail/wagtailadmin/templates/wagtailadmin/chooser/external_link.js +wagtail/wagtailadmin/templates/wagtailadmin/chooser/email_link.js +wagtail/wagtailadmin/templates/wagtailadmin/chooser/browse.js +wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy_done.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..9fcfb7739c --- /dev/null +++ b/.eslintrc @@ -0,0 +1,25 @@ +{ + "extends": "airbnb", + + "rules": { + "indent": [2, 2], + "max-len": [1, 120, 4, {"ignoreUrls": true}], + "id-length": [1, {"min": 2, "exceptions": ["x", "y", "e", "i", "j", "k", "d", "n", "_", "$"]}], + "object-shorthand": [2, "methods"], + "no-new": [1], + "comma-dangle": [0], + "no-multi-spaces": [0], + "prefer-template": [0], + "no-var": [0], + "prefer-arrow-callback": [1], + "no-undef": [1], + "no-unused-vars": [1], + "no-warning-comments": [1, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], + "react/sort-comp": [0], + "react/jsx-boolean-value": [0], + "react/jsx-no-bind": [0], + "react/prefer-es6-class": [0, 'never'], + "react/jsx-indent-props": [2, 4], + "jsx-quotes": [1, "prefer-double"] + } +} diff --git a/.gitignore b/.gitignore index ba3f39260d..34437df1ce 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ npm-debug.log *.iml *.ipr *.iws +coverage/ diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 6fb0264638..0000000000 --- a/.jscsrc +++ /dev/null @@ -1,37 +0,0 @@ -{ - "validateIndentation": 4, - "safeContextKeyword": ["_this", "widget", "jcropapi"], - "requireSpaceBeforeKeywords": [ - "else", - "while", - "catch" - ], - "disallowMultipleVarDecl": "exceptUndefined", - "excludeFiles": [ - "node_modules/**", - "**/*.min.js", - "**/vendor/**/*.js", - "./wagtail/wagtailadmin/static/**", - "./wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/inline_panel.js", - "./wagtail/contrib/wagtailsearchpromotions/templates/wagtailsearchpromotions/includes/searchpromotions_formset.js", - "./wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.js", - "./wagtail/wagtailsnippets/templates/wagtailsnippets/chooser/chosen.js", - "./wagtail/wagtailimages/templates/wagtailimages/chooser/image_chosen.js", - "./wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.js", - "./wagtail/wagtailsearch/templates/wagtailsearch/queries/chooser/chooser.js", - "./wagtail/wagtailimages/templates/wagtailimages/chooser/select_format.js", - "./wagtail/wagtailembeds/templates/wagtailembeds/chooser/embed_chosen.js", - "./wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.js", - "./wagtail/wagtaildocs/templates/wagtaildocs/chooser/chooser.js", - "./wagtail/wagtaildocs/templates/wagtaildocs/chooser/document_chosen.js", - "./wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy.js", - "./wagtail/wagtailadmin/templates/wagtailadmin/chooser/external_link_chosen.js", - "./wagtail/wagtailadmin/templates/wagtailadmin/chooser/external_link.js", - "./wagtail/wagtailadmin/templates/wagtailadmin/chooser/email_link.js", - "./wagtail/wagtailadmin/templates/wagtailadmin/chooser/browse.js", - "./wagtail/wagtailadmin/templates/wagtailadmin/page_privacy/set_privacy_done.js" - ], - "fileExtensions": [".js"], - "preset":"airbnb", - "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties" -} diff --git a/client/.npmignore b/client/.npmignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000000..28af81e936 --- /dev/null +++ b/client/README.md @@ -0,0 +1,42 @@ +# Wagtail client-side components + +This library aims to give developers the ability to subclass and configure Wagtail's UI components. + +## Usage + +``` +npm install wagtail +``` + +```javascript +import { Explorer } from 'wagtail'; + +... + + { console.log(`You picked ${page}`); }} /> + +``` + +## Available components + +TODO + +- [ ] Explorer +- [ ] Modal +- [ ] DatePicker +- [ ] LinkChooser +- [ ] DropDown + +## Building in development + +Run `webpack` from the Wagtail project root. + +``` +webpack +``` + +## How to release + +The front-end is bundled at the same time as the Wagtail project, via `setuptools`. You'll need to set the `__semver__` property to a npm-compliant version number in `wagtail.wagtailcore`. + + diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000000..3e25e6d150 --- /dev/null +++ b/client/package.json @@ -0,0 +1,18 @@ +{ + "name": "wagtail", + "license": "BSD-3-Clause", + "author": "Wagtail", + "version": "0.0.2", + "bin": { + "wagtail": "./src/cli/index.js" + }, + "scripts": { + "test": "npm test" + }, + "main": "src/index.js", + "description": "Wagtail's client side code", + "dependencies": { + "mustache": "^2.2.1", + "yargs": "^4.2.0" + } +} diff --git a/client/scss/_components.scss b/client/scss/_components.scss new file mode 100644 index 0000000000..db754bc26e --- /dev/null +++ b/client/scss/_components.scss @@ -0,0 +1,3 @@ +@import '../src/components/explorer/style'; +@import '../src/components/loading-indicator/style'; +@import '../src/components/state-indicator/style'; diff --git a/client/scss/_objects.scss b/client/scss/_objects.scss new file mode 100644 index 0000000000..c1fa0d0886 --- /dev/null +++ b/client/scss/_objects.scss @@ -0,0 +1 @@ +@import 'objects/o.icon'; diff --git a/client/scss/objects/_o.icon.scss b/client/scss/objects/_o.icon.scss new file mode 100644 index 0000000000..82fb39e8f2 --- /dev/null +++ b/client/scss/objects/_o.icon.scss @@ -0,0 +1,3 @@ +.o-icon { + display: inline-block; +} diff --git a/client/scss/states/_animations.scss b/client/scss/states/_animations.scss new file mode 100644 index 0000000000..8611d764ba --- /dev/null +++ b/client/scss/states/_animations.scss @@ -0,0 +1,4 @@ +.is-spinning { + +} + diff --git a/client/scss/states/_states.scss b/client/scss/states/_states.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/scss/style.scss b/client/scss/style.scss new file mode 100644 index 0000000000..a8305d95d5 --- /dev/null +++ b/client/scss/style.scss @@ -0,0 +1,10 @@ +// ============================================================================= +// Wagtail CMS main stylesheet +// ============================================================================= + +@import 'objects'; +@import 'components'; +@import 'states/states'; +@import 'states/animations'; +@import 'utilities/utilities'; +@import 'themes/themes'; diff --git a/client/scss/themes/_themes.scss b/client/scss/themes/_themes.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/scss/utilities/_utilities.scss b/client/scss/utilities/_utilities.scss new file mode 100644 index 0000000000..929078e623 --- /dev/null +++ b/client/scss/utilities/_utilities.scss @@ -0,0 +1,9 @@ +.u-text-center { + text-align: center; +} + +@media screen and (min-width: 15em) { + .u-text-center\@sm { + text-align: center; + } +} diff --git a/client/src/cli/component.js b/client/src/cli/component.js new file mode 100644 index 0000000000..d34b3a6f9f --- /dev/null +++ b/client/src/cli/component.js @@ -0,0 +1,83 @@ +var path = require('path'); +var fs = require('fs'); +var Mustache = require('mustache'); + +var TEMPLATES = path.join(__dirname, '..', '..', 'template'); + +var files = [ +{ + name: 'index.js', + template: 'component.mst' +}, +{ + name: 'style.scss', + template: 'style.mst' +}, +{ + name: 'README.md', + template: 'README.mst' +} +]; + + +// ============================================================================= +// Helper methods +// ============================================================================= + +function slugify(text) { + return text.toString().split(/(?=[A-Z])/).join('-').toLowerCase().trim() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(/&/g, '-and-') // Replace & with 'and' + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-'); // Replace multiple - with single - +} + + +function write(name, data) { + fs.writeFile(name, data, function(err) { + if (err) { + return console.log('[ error ] ' + err); + } + console.log('[ created ] ' + name); + }); +} + + +// ============================================================================= +// Write files! +// ============================================================================= +function run(argv) { + var name = argv.name; + var slug = slugify(name); + var directory = path.join(argv.dir, slug); + + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } else { + console.warn('[ error ] ' + directory + ' already exists'); + return; + } + + files.forEach(function(file) { + var template = fs.readFileSync(path.join(TEMPLATES, file.template), 'utf8'); + var newPath = path.join(directory, file.name); + var context = { + name: name, + slug: slug + }; + + write(newPath, Mustache.render(template, context)); + }); +} + + +function build(cli) { + return cli + .option('dir', { + default: process.env.PWD + }); +} + + +exports.handler = run; +exports.builder = build; diff --git a/client/src/cli/index.js b/client/src/cli/index.js new file mode 100755 index 0000000000..ac39b3e8d8 --- /dev/null +++ b/client/src/cli/index.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node +var cli = require('yargs'); + +cli + .usage('Usage: $0 [options]') + .help('help'); + +cli + .command( + 'component ', + 'scaffold out a wagtail component', + require('./component')); + +cli + .argv; diff --git a/client/src/components/explorer/README.md b/client/src/components/explorer/README.md new file mode 100644 index 0000000000..1fdb99a28b --- /dev/null +++ b/client/src/components/explorer/README.md @@ -0,0 +1 @@ +# Explorer diff --git a/client/src/components/explorer/explorer-item.js b/client/src/components/explorer/explorer-item.js new file mode 100644 index 0000000000..a0f381e695 --- /dev/null +++ b/client/src/components/explorer/explorer-item.js @@ -0,0 +1,28 @@ +import React, { Component, PropTypes } from 'react'; +import StateIndicator from 'components/state-indicator'; + +export default class ExplorerItem extends Component { + constructor(props) { + super(props); + this.state = {}; + } + + render() { + const { title, data } = this.props; + + return ( +
+

+ + {title} +

+
+ ); + } +} + + +ExplorerItem.propTypes = { + title: PropTypes.string, + data: PropTypes.object +}; diff --git a/client/src/components/explorer/index.js b/client/src/components/explorer/index.js new file mode 100644 index 0000000000..17e9bc7da3 --- /dev/null +++ b/client/src/components/explorer/index.js @@ -0,0 +1,67 @@ +import React, { Component, PropTypes } from 'react'; +import LoadingIndicator from 'components/loading-indicator'; +import ExplorerItem from './explorer-item'; + +import { API } from 'config'; + + +class Explorer extends Component { + + constructor(props) { + super(props); + this.state = { cursor: null }; + } + + componentDidMount() { + fetch(`${API}/pages/?child_of=root`) + .then(res => res.json()) + .then(body => { + this.setState({ + cursor: body + }); + }); + } + + componentWillUnmount(cursor) { + + } + + _getPages(cursor) { + if (!cursor) { + return []; + } + + return cursor.pages.map(item => + + ); + } + + getPosition() { + const { position } = this.props; + return { + left: position.right + 'px', + top: position.top + 'px' + }; + } + + render() { + const { cursor } = this.state; + const pages = this._getPages(cursor); + + return ( +
+ {cursor ? pages : } +
+ ); + } +} + +Explorer.propTypes = { + onPageSelect: PropTypes.func, + initialPath: PropTypes.string, + apiPath: PropTypes.string, + size: PropTypes.number, + position: PropTypes.object +}; + +export default Explorer; diff --git a/client/src/components/explorer/style.scss b/client/src/components/explorer/style.scss new file mode 100644 index 0000000000..a0f3426b32 --- /dev/null +++ b/client/src/components/explorer/style.scss @@ -0,0 +1,13 @@ +.c-explorer { + width: 320px; + height: 500px; + background: #333; + position: absolute; + z-index: 25; + top: 0; + left: 180px; +} + +.c-explorer__item { + +} diff --git a/client/src/components/index.js b/client/src/components/index.js new file mode 100644 index 0000000000..c7c4fc6c04 --- /dev/null +++ b/client/src/components/index.js @@ -0,0 +1,7 @@ +import Explorer from './explorer'; +import LoadingIndicator from './loading-indicator'; +import StateIndicator from './state-indicator'; + +export { Explorer }; +export { LoadingIndicator }; +export { StateIndicator }; diff --git a/client/src/components/loading-indicator/README.md b/client/src/components/loading-indicator/README.md new file mode 100644 index 0000000000..d36bac20d0 --- /dev/null +++ b/client/src/components/loading-indicator/README.md @@ -0,0 +1 @@ +# Loading indicator diff --git a/client/src/components/loading-indicator/index.js b/client/src/components/loading-indicator/index.js new file mode 100644 index 0000000000..6f0722440a --- /dev/null +++ b/client/src/components/loading-indicator/index.js @@ -0,0 +1,9 @@ +import React from 'react'; + +const LoadingIndicator = () => +
+ Loading... +
; + + +export default LoadingIndicator; diff --git a/client/src/components/loading-indicator/style.scss b/client/src/components/loading-indicator/style.scss new file mode 100644 index 0000000000..c07c6dc9f9 --- /dev/null +++ b/client/src/components/loading-indicator/style.scss @@ -0,0 +1,3 @@ +.c-indicator { + +} diff --git a/client/src/components/state-indicator/README.md b/client/src/components/state-indicator/README.md new file mode 100644 index 0000000000..ff1ac97af1 --- /dev/null +++ b/client/src/components/state-indicator/README.md @@ -0,0 +1,9 @@ +# StateIndicator + +About this component + +## Usage + +```javascript +import { StateIndicator } from 'wagtail'; +``` diff --git a/client/src/components/state-indicator/index.js b/client/src/components/state-indicator/index.js new file mode 100644 index 0000000000..ddde684c44 --- /dev/null +++ b/client/src/components/state-indicator/index.js @@ -0,0 +1,16 @@ +import React, { Component, PropTypes } from 'react'; + +export default class StateIndicator extends Component { + constructor(props) { + super(props); + this.state = {}; + } + + render() { + return ( +
+ +
+ ); + } +} diff --git a/client/src/components/state-indicator/style.scss b/client/src/components/state-indicator/style.scss new file mode 100644 index 0000000000..bc1f218cc4 --- /dev/null +++ b/client/src/components/state-indicator/style.scss @@ -0,0 +1,5 @@ +// StateIndicator + +.c-state-indicator { + display: block; +} diff --git a/client/src/config/index.js b/client/src/config/index.js new file mode 100644 index 0000000000..e43c98fade --- /dev/null +++ b/client/src/config/index.js @@ -0,0 +1 @@ +export const API = '/admin/api/v2beta/'; diff --git a/client/src/index.js b/client/src/index.js new file mode 100644 index 0000000000..07635cbbc8 --- /dev/null +++ b/client/src/index.js @@ -0,0 +1 @@ +export * from './components'; diff --git a/client/template/README.mst b/client/template/README.mst new file mode 100644 index 0000000000..243776236d --- /dev/null +++ b/client/template/README.mst @@ -0,0 +1,9 @@ +# {{ name }} + +About this component + +## Usage + +```javascript +import { {{ name }} } from 'wagtail'; +``` diff --git a/client/template/component.mst b/client/template/component.mst new file mode 100644 index 0000000000..3fedd86bb4 --- /dev/null +++ b/client/template/component.mst @@ -0,0 +1,15 @@ +import React, { Component, PropTypes } from 'react'; + +export default class {{ name }} extends Component { + constructor(props) { + super(props); + this.state = {}; + } + + render() { + return ( +
+
+ ); + } +} diff --git a/client/template/style.mst b/client/template/style.mst new file mode 100644 index 0000000000..75a77fb9ce --- /dev/null +++ b/client/template/style.mst @@ -0,0 +1,5 @@ +// {{ name }} + +.c-{{ slug }} { + display: block; +} diff --git a/client/tests/components/explorer.test.js b/client/tests/components/explorer.test.js new file mode 100644 index 0000000000..88d17dcda3 --- /dev/null +++ b/client/tests/components/explorer.test.js @@ -0,0 +1,10 @@ +/*eslint-disable */ +import { expect } from 'chai'; +import Explorer from '../../src/components/explorer'; + + +describe('Explorer', () => { + it('exists', () => { + expect(Explorer).to.exist; + }); +}); diff --git a/gulpfile.js/tasks/watch.js b/gulpfile.js/tasks/watch.js index e19282248c..d95ee33f7c 100644 --- a/gulpfile.js/tasks/watch.js +++ b/gulpfile.js/tasks/watch.js @@ -7,6 +7,7 @@ var config = require('../config'); */ gulp.task('watch', ['build'], function () { config.apps.forEach(function(app) { + gulp.watch(path.join('./client/src/**/*.scss'), ['styles:sass']); gulp.watch(path.join(app.sourceFiles, '*/scss/**'), ['styles:sass']); gulp.watch(path.join(app.sourceFiles, '*/css/**'), ['styles:css']); gulp.watch(path.join(app.sourceFiles, '*/js/**'), ['scripts']); diff --git a/package.json b/package.json index 0de3fa4993..364b1773a7 100644 --- a/package.json +++ b/package.json @@ -11,22 +11,47 @@ }, "browserify-shim": {}, "devDependencies": { - "browserify": "~3.46.1", - "browserify-shim": "~3.4.1", + "babel-cli": "^6.5.1", + "babel-core": "^6.5.2", + "babel-loader": "^6.2.3", + "babel-preset-es2015": "^6.5.0", + "babel-preset-react": "^6.5.0", + "chai": "^3.5.0", + "eslint": "^2.2.0", + "eslint-config-airbnb": "^6.0.2", + "eslint-plugin-react": "^4.1.0", + "glob": "^7.0.0", "gulp": "~3.8.11", "gulp-autoprefixer": "~3.0.2", "gulp-rename": "^1.2.2", "gulp-sass": "~2.0.4", "gulp-sourcemaps": "~1.5.2", "gulp-util": "~2.2.14", - "jscs": "^1.12.0", - "require-dir": "^0.3.0" + "isparta": "^4.0.0", + "lodash": "^4.5.1", + "mocha": "^2.4.5", + "mustache": "^2.2.1", + "redux-devtools": "^3.1.1", + "require-dir": "^0.3.0", + "sinon": "^1.17.3" + }, + "dependencies": { + "exports-loader": "^0.6.3", + "imports-loader": "^0.6.5", + "react-redux": "^4.4.0", + "redux": "^3.3.1", + "whatwg-fetch": "^0.11.0" }, - "dependencies": {}, "scripts": { - "build": "gulp build", - "start": "gulp watch", - "lint:js": "./node_modules/.bin/jscs ./wagtail || true", - "format:js": "./node_modules/.bin/jscs ./wagtail -x" + "install": "pushd ./client; npm install; popd", + "build": "gulp build; webpack --progress --colors --config webpack.prd.config.js", + "watch": "webpack --progress --colors --config webpack.dev.config.js & gulp watch", + "start": "npm run watch", + "lint:js": "eslint --max-warnings 16 webpack.*.config.js ./client/src", + "lint": "npm run lint:js", + "test": "npm run test:unit", + "test:unit": "env NODE_PATH=$NODE_PATH:$PWD/client/src mocha --compilers js:babel-core/register client/tests/**/*.test.js", + "test:unit:coverage": "env NODE_PATH=$NODE_PATH:$PWD/client/src babel-node $(npm bin)/isparta cover node_modules/mocha/bin/_mocha -- client/tests/**/*.test.js", + "component": "node ./client/src/cli/index.js component --dir ./client/src/components/" } } diff --git a/wagtail/utils/setup.py b/wagtail/utils/setup.py index 9838a2ad5a..57ccc41545 100644 --- a/wagtail/utils/setup.py +++ b/wagtail/utils/setup.py @@ -1,11 +1,15 @@ from __future__ import absolute_import, print_function, unicode_literals import os +import io import subprocess +import json + from setuptools import Command from setuptools.command.bdist_egg import bdist_egg from setuptools.command.sdist import sdist as base_sdist +from wagtail.wagtailcore import __semver__ class assets_mixin(object): @@ -17,6 +21,37 @@ class assets_mixin(object): print('Error compiling assets: ' + str(e)) raise SystemExit(1) + def publish_assets(self): + try: + subprocess.check_call(['npm', 'publish', 'client']) + except (OSError, subprocess.CalledProcessError) as e: + print('Error publishing front-end assets: ' + str(e)) + raise SystemExit(1) + + def bump_client_version(self): + """ + Writes the current Wagtail version number into package.json + """ + path = os.path.join('.', 'client', 'package.json') + input_file = io.open(path, "r") + + try: + package = json.loads(input_file.read().decode("utf-8")) + except (ValueError) as e: + print('Unable to read ' + path + ' ' + e) + raise SystemExit(1) + + package['version'] = __semver__ + + try: + with io.open(path, 'w', encoding='utf-8') as f: + from django.utils import six + + f.write(six.text_type(json.dumps(package, indent=2, ensure_ascii=False))) + except (IOError) as e: + print('Error setting the version for front-end assets: ' + str(e)) + raise SystemExit(1) + class assets(Command, assets_mixin): user_options = [] @@ -28,7 +63,9 @@ class assets(Command, assets_mixin): pass def run(self): + self.bump_client_version() self.compile_assets() + self.publish_assets() class sdist(base_sdist, assets_mixin): diff --git a/wagtail/wagtailadmin/static_src/wagtailadmin/app/wagtailadmin.entry.js b/wagtail/wagtailadmin/static_src/wagtailadmin/app/wagtailadmin.entry.js new file mode 100644 index 0000000000..3f8831f96d --- /dev/null +++ b/wagtail/wagtailadmin/static_src/wagtailadmin/app/wagtailadmin.entry.js @@ -0,0 +1,23 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Explorer from 'components/explorer'; + + +document.addEventListener('DOMContentLoaded', e => { + const top = document.querySelector('.wrapper'); + const div = document.createElement('div'); + const trigger = document.querySelector('[data-explorer-menu-url]'); + + trigger.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + + if (!div.childNodes.length) { + ReactDOM.render(, div); + } else { + ReactDOM.unmountComponentAtNode(div); + } + }); + + top.parentNode.appendChild(div); +}); diff --git a/wagtail/wagtailadmin/static_src/wagtailadmin/scss/core.scss b/wagtail/wagtailadmin/static_src/wagtailadmin/scss/core.scss index a0f9ab64a5..671c082fff 100644 --- a/wagtail/wagtailadmin/static_src/wagtailadmin/scss/core.scss +++ b/wagtail/wagtailadmin/static_src/wagtailadmin/scss/core.scss @@ -19,6 +19,12 @@ @import 'wagtailadmin/scss/fonts'; +// scss-lint:disable all +#wagtail { + @import '../../../../../client/scss/style'; +} +// scss-lint:enable all + html { background: $color-grey-4; height: 100%; @@ -107,7 +113,7 @@ footer { @include transition(bottom 0.5s ease 1s); @include row(); border-radius: 3px 3px 0 0; - box-shadow: 0 0 2px rgba(255, 255, 255, 0.5); + box-shadow: 0 0 2px rgba(255, 255, 255, 0.5); background: $color-grey-1; position: fixed; bottom: 0; diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/admin_base.html b/wagtail/wagtailadmin/templates/wagtailadmin/admin_base.html index 74dcc5ca1c..244b968824 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/admin_base.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/admin_base.html @@ -25,6 +25,7 @@ + {% main_nav_js %} {% block extra_js %}{% endblock %} diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/skeleton.html b/wagtail/wagtailadmin/templates/wagtailadmin/skeleton.html index d40fbcebf0..4ac3afe7df 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/skeleton.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/skeleton.html @@ -16,7 +16,7 @@ {% block branding_favicon %}{% endblock %} - + diff --git a/wagtail/wagtailcore/__init__.py b/wagtail/wagtailcore/__init__.py index 56caa41275..8f387d3272 100644 --- a/wagtail/wagtailcore/__init__.py +++ b/wagtail/wagtailcore/__init__.py @@ -1,4 +1,6 @@ __version__ = '1.4a0' +# Required for npm package for frontend +__semver__ = '1.4.0-alpha' default_app_config = 'wagtail.wagtailcore.apps.WagtailCoreAppConfig' diff --git a/webpack.base.config.js b/webpack.base.config.js new file mode 100644 index 0000000000..ea02511e6d --- /dev/null +++ b/webpack.base.config.js @@ -0,0 +1,77 @@ +var _ = require('lodash'); +var path = require('path'); +var glob = require('glob').sync; +var webpack = require('webpack'); + +var COMMON_PATH = './wagtail/wagtailadmin/static/wagtailadmin/js/common.js'; + + +function appName(filename) { + return _(filename) + .split(path.sep) + .get(2); +} + + +function entryPoint(filename) { + var name = appName(filename); + var entryName = path.basename(filename, '.entry.js'); + var outputPath = path.join('wagtail', name, 'static', name, 'js', entryName); + return [outputPath, filename]; +} + + +function entryPoints(paths) { + return _(glob(paths)) + .map(entryPoint) + .fromPairs() + .value(); +} + + +module.exports = function exports() { + var CLIENT_DIR = path.resolve(__dirname, 'client', 'src'); + + return { + entry: entryPoints('./wagtail/**/static_src/**/app/*.entry.js'), + resolve: { + alias: { + components: path.resolve(CLIENT_DIR, 'components') + } + }, + output: { + path: './', + filename: '[name].js', + publicPath: '/static/js/' + }, + plugins: [ + new webpack.ProvidePlugin({ + fetch: 'imports?this=>global!exports?global.fetch!whatwg-fetch' + }), + new webpack.optimize.CommonsChunkPlugin('common', COMMON_PATH, Infinity) + ], + devtool: '#inline-source-map', + module: { + loaders: [ + { + test: /\.js$/, + loader: 'babel', + exclude: /node_modules/, + include: [ + CLIENT_DIR, + path.resolve(__dirname, 'wagtail') + ] + }, + { + test: /\.jsx$/, + loader: 'babel', + exclude: /node_modules/, + include: [ + CLIENT_DIR, + path.resolve(__dirname, 'wagtail') + ] + } + ] + } + }; +}; diff --git a/webpack.dev.config.js b/webpack.dev.config.js new file mode 100644 index 0000000000..c9d3035488 --- /dev/null +++ b/webpack.dev.config.js @@ -0,0 +1,8 @@ +var base = require('./webpack.base.config'); +var config = base('development'); + + +// development overrides go here +config.watch = true; + +module.exports = config; diff --git a/webpack.prd.config.js b/webpack.prd.config.js new file mode 100644 index 0000000000..fd54571e26 --- /dev/null +++ b/webpack.prd.config.js @@ -0,0 +1,8 @@ +var base = require('./webpack.base.config'); +var config = base('production'); + + +// production overrides go here + + +module.exports = config;