diff --git a/.editorconfig b/.editorconfig index 32b4d70cbf..abfebe906f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,7 @@ insert_final_newline = true indent_style = tab [*.js] -indent_size = 2 +indent_size = 4 [*.json] indent_style = space diff --git a/.eslintignore b/.eslintignore index b56f42833b..9bef6ff66d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,4 +7,3 @@ docs/* fields/types/**/lib/* node_modules/* test/* -website/* diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index a636cc24ee..de49483053 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,9 +3,10 @@ Please ask questions and support requests on: * https://stackoverflow.com/questions/tagged/keystonejs + * https://gitter.im/keystonejs/keystone - Join the KeystoneJS Slack for discussion with the community & contributors: - * https://launchpass.com/keystonejs + New features can be requested and voted upon on: + * https://productpains.com/product/keystonejs --> ### Expected behavior @@ -22,7 +23,7 @@ ### Steps to reproduce the actual/current behavior - + @@ -32,5 +33,4 @@ | Software | Version | ---------------- | ------- | Keystone | -| Node.js | -| Browser | +| Node | diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e9020a1986..0fefdd3397 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,30 +1,29 @@ ## Description of changes + ## Related issues (if any) ## Testing - - [ ] List browser version(s) any admin UI changes were tested in: - - [ ] Please confirm you've added (or verified) test coverage for this change. - - [ ] Please confirm `npm run test-all` ran successfully. +- [ ] Please confirm `npm run test-all` ran successfully. diff --git a/.gitignore b/.gitignore index 573ee573bc..618b8d2a41 100644 --- a/.gitignore +++ b/.gitignore @@ -33,9 +33,4 @@ reports test/e2e/drivers/* -package-lock.json yarn.lock - -# Website dependencies -website/.cache -website/public diff --git a/.travis.yml b/.travis.yml index f2b49e5d1c..60d334fe54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,21 @@ matrix: - npm run test-admin env: - JOB=unit_tests_node_6 - + - node_js: '5' + script: + - npm run test-admin + env: + - JOB=unit_tests_node_5 + - node_js: '4' + script: + - npm run test-admin + env: + - JOB=unit_tests_node_4 + - node_js: '0.12' + script: + - npm run test-admin + env: + - JOB=unit_tests_node_0.12 before_script: - sleep 15 @@ -62,9 +76,8 @@ services: git: depth: 10 cache: - yarn: true directories: - - node_modules + - node_modules addons: apt: sources: diff --git a/HISTORY.md b/HISTORY.md index 6181334b72..13d3dd9fdb 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,9 +2,6 @@ KeystoneJS is maintained by [@JedWatson](https://github.com/JedWatson) and an amazing team of contributors. All contributions are given credit here except for Jed's. -Release notes for Keystone 4 beta & release candidates can be found on the [GitHub releases]( https://github.com/keystonejs/keystone/releases) page. - -Changes for production releases are included below. ## v0.3.22 / 2016-07-22 diff --git a/README.md b/README.md index 331a53f481..7a9c417d3f 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,87 @@ -![KeystoneJS](http://v3.keystonejs.com/images/logo.svg) +![KeystoneJS](http://keystonejs.com/images/logo.svg) =================================== [![Build Status](https://travis-ci.org/keystonejs/keystone.svg?branch=master)](https://travis-ci.org/keystonejs/keystone) - - [About Keystone](#about) - - [Getting Started](#getting-started) - - [Community](#community) - - [Contributing](#contributing) - - [License](#license) +[KeystoneJS](http://keystonejs.com) is a powerful Node.js content management system and web app framework built on [express](http://expressjs.com) and [mongoose](http://mongoosejs.com). Keystone makes it easy to create sophisticated web sites and apps, and comes with a beautiful auto-generated Admin UI. -## About Keystone +Check out [keystonejs.com](http://keystonejs.com) for documentation and guides. -[KeystoneJS](http://keystonejs.com) is a powerful Node.js content management system and web app framework built on the [Express](https://expressjs.com/) web framework and [Mongoose ODM](http://mongoosejs.com). Keystone makes it easy to create sophisticated web sites and apps, and comes with a beautiful auto-generated Admin UI. +You can also deploy a starter project to [Heroku](https://www.heroku.com/) for free to try it out: -### Documentation +[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/JedWatson/keystone-starter) -For Keystone v4 documentation and guides, see [keystonejs.com](https://keystonejs.com). -For Keystone v0.3 documentation, see [v3.keystonejs.com](https://v3.keystonejs.com). +## Keystone 4.0 Beta Released!!! -### Keystone 4.0 Release Candidate (RC) +We've been working on a major update to KeystoneJS for the last year, and it's a complete rebuild of Keystone's Admin UI and internal architecture. Improvements include: -We've been working on a major update to KeystoneJS. Keystone 4 is a complete rebuild of Keystone's Admin UI and internal architecture. - -Improvements include: - -* The Admin UI has been re-written as a single page app using [React.js](https://reactjs.org), [Redux](https://redux.js.org/), and [Elemental UI](http://elemental-ui.com/) +* The Admin UI has been re-written as a single page app using React.js, Redux and Elemental UI * An updated API for Lists and Fields * Better support for using Keystone without Express, or with your own express instance * Core functionality has been refactored and we're breaking Keystone up into separate npm packages * Startup time has been significantly reduced -* LocalFile, S3File, and AzureFile have been replaced by a new generic `keystone.Storage` engine and File field +* LocalFile, S3File and AzureFile have been replaced by a new generic `keystone.Storage` engine and File field * We have much higher unit and end-to-end test coverage -Please try out Keystone 4 and let us know what you think: +Please try out the beta and let us know what you think: ``` -npm install --save keystone +npm install --save keystone@next ``` -We'll be publishing a summary of the new features, changes, and improvements as we get closer to the final release. In the meantime, see the [v0.3 -> v4.0 Upgrade Guide](https://keystonejs.com/guides/v-0-3-to-v-4-0-upgrade-guide) for information on what's changed. +We'll be publishing a summary of the new features, changes and improvements as we get closer to the final release. In the meantime, see the [v0.3 -> v4.0 Upgrade Guide](https://github.com/keystonejs/keystone/blob/master/docs/guides/v0.3-to-v4.0-Upgrade-Guide.md) for information on what's changed. + +Also check out our [demo site](http://demo.keystonejs.com/), which has been updated to the new version! + + +## About + +Keystone gives you: +* A simple way to create a dynamic web site or app with well-structured routes, templates and models +* A beautiful Admin UI based on the database models you define +* Enhanced `models` with additional field types and functionality, building on those natively supported by Mongoose +* Out of the box session management and authentication +* An updates framework for managing data updates or initialisation +* Integration with Cloudinary for image uploading, storage and resizing +* Integration with Mandrill for sending emails easily +* Integration with Google Places for clever location fields +* Integration with Embedly for powerful video and rich media embedding tools + +... plus a lot of other tools and utilities to make creating complex web apps easier. + +Use our [Yeoman Generator](https://github.com/keystonejs/generator-keystone) to get up and running with KeystoneJS quickly, then check out our getting started guide & docs at [keystonejs.com/docs/getting-started](http://keystonejs.com/docs/getting-started). + +We have a demo website at [demo.keystonejs.com](http://demo.keystonejs.com/) where you can play with the Keystone Admin UI, and you can [read the source](https://github.com/keystonejs/keystone-demo) to see how it was built. + +### Community + +We have a friendly, growing community and welcome everyone to get involved. -Also check out our [demo site](http://demo.keystonejs.com), which has been updated to the new version! +Here are some ways: -## Getting Started +* Follow [@KeystoneJS](https://twitter.com/KeystoneJS) on twitter for news and announcements +* Vote on the next features on [ProductPains](https://productpains.com/product/keystonejs) +* Chat with us [![Join the chat at https://gitter.im/keystonejs/keystone](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/keystonejs/keystone?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +* If you've got ideas, questions or need some advice, check out the [KeystoneJS Google Group](https://groups.google.com/d/forum/keystonejs) +* Ask technical questions on [Stack Overflow](http://stackoverflow.com/questions/tagged/keystone.js) and tag them `keystonejs` +* Report bugs and issues on our [issue tracker](https://github.com/keystonejs/keystone/issues) +* ... or preferably, submit pull request with patches and / or new features -This section provides a short intro to Keystone. Check out the [Getting Started Guide](https://keystonejs.com/getting-started) in the Keystone documentation for a more comprehensive introduction. +We love to hear feedback about Keystone and the projects you're using it for. Ping us at [@KeystoneJS](https://twitter.com/KeystoneJS) on twitter. + +#### Related Projects +If you are using KeystoneJS in any projects we encourage you to add it to our [Related Projects Page](https://github.com/keystonejs/keystone/wiki/Related-Projects). This is also the place to find generators and such that bundle KeystoneJS. + +### Contributing + +If you can, please contribute by reporting issues, discussing ideas, or submitting pull requests with patches and new features. We do our best to respond to all issues and pull requests within a day or two, and make patch releases to npm regularly. + +If you're going to contribute code, please follow our [coding standards](https://github.com/keystonejs/keystone/wiki/Coding-Standards) and read our [CONTRIBUTING.md](https://github.com/keystonejs/keystone/blob/master/CONTRIBUTING.md). + +## Usage + +**Check out the [KeystoneJS Getting Started Guide](http://keystonejs.com/getting-started) to start using KeystoneJS.** ### Installation @@ -58,63 +94,86 @@ $ yo keystone Answer the questions, and the generator will create a new project based on the options you select, and install the required packages from **npm**. -Alternatively, to include Keystone in an existing project or start from scratch (without Yeoman), specify `keystone: "4.0.0"` in the `dependencies` array of your `package.json` file, and run `npm install` from your terminal. +Alternatively, to include Keystone in an existing project or start from scratch (without Yeoman), specify `keystone: "^0.3.9"` in the `dependencies` array of your `package.json` file, and run `npm install` from your terminal. -Then read through the [Documentation](https://keystonejs.com/documentation) and the [Example Projects](http://v3.keystonejs.com/examples) to understand how to use it. +Then read through the [Documentation](http://keystonejs.com/docs) and the [Example Projects](http://keystonejs.com/examples) to understand how to use it. ### Configuration -Config variables can be passed in an object to the `keystone.init` method, or can be set any time before `keystone.start` is called using `keystone.set(key, value)`. This allows for a more flexible order of execution. For example, if you refer to Lists in your routes you can set the routes after configuring your Lists. +Config variables can be passed in an object to the `keystone.init` method, or can be set any time before `keystone.start` is called using `keystone.set(key, value)`. This allows for a more flexible order of execution (e.g. if you refer to Lists in your routes, you can set the routes after configuring your Lists, as in the example above). -See the [KeystoneJS configuration documentation](https://keystonejs.com/documentation/configuration) for details and examples of the available options. +See the [KeystoneJS configuration documentation](http://keystonejs.com/docs/configuration) for details and examples of the available configuration options. ### Database field types -Keystone builds on the basic data types provided by MongoDB and allows you to easily add rich, functional fields to your application's models. +Keystone builds on the basic data types provided by mongo and allows you to easily add rich, functional fields to your application's models. You get helper methods on your models for dealing with each field type easily (such as formatting a date or number, resizing an image, getting an array of the available options for a select field, or using Google's Places API to improve addresses) as well as a beautiful, responsive admin UI to edit your data with. -See the [KeystoneJS database documentation](https://keystonejs.com/documentation/database) for details and examples of the various field types, as well as how to set up and use database models in your application. +See the [KeystoneJS database documentation](http://keystonejs.com/docs/database) for details and examples of the various field types, as well as how to set up and use database models in your application. + +Keystone's field types include: + +* [Boolean](http://keystonejs.com/docs/database/#fieldtypes-boolean) +* [Color](http://keystonejs.com/docs/database/#fieldtypes-color) +* [Date](http://keystonejs.com/docs/database/#fieldtypes-date) +* [Datetime](http://keystonejs.com/docs/database/#fieldtypes-datetime) +* [Email](http://keystonejs.com/docs/database/#fieldtypes-email) +* [Html](http://keystonejs.com/docs/database/#fieldtypes-html) +* [Key](http://keystonejs.com/docs/database/#fieldtypes-key) +* [Location](http://keystonejs.com/docs/database/#fieldtypes-location) +* [Markdown](http://keystonejs.com/docs/database/#fieldtypes-markdown) +* [Money](http://keystonejs.com/docs/database/#fieldtypes-money) +* [Name](http://keystonejs.com/docs/database/#fieldtypes-name) +* [Number](http://keystonejs.com/docs/database/#fieldtypes-number) +* [Password](http://keystonejs.com/docs/database/#fieldtypes-password) +* [Select](http://keystonejs.com/docs/database/#fieldtypes-select) +* [Text](http://keystonejs.com/docs/database/#fieldtypes-text) +* [Textarea](http://keystonejs.com/docs/database/#fieldtypes-textarea) +* [Url](http://keystonejs.com/docs/database/#fieldtypes-url) +* [Azure File](http://keystonejs.com/docs/database/#fieldtypes-azurefile) +* [CloudinaryImage](http://keystonejs.com/docs/database/#fieldtypes-cloudinaryimage) +* [CloudinaryImages](http://keystonejs.com/docs/database/#fieldtypes-cloudinaryimages) +* [Embedly](http://keystonejs.com/docs/database/#fieldtypes-embedly) +* [LocalFile](http://keystonejs.com/docs/database/#fieldtypes-localfile) +* [S3 File](http://keystonejs.com/docs/database/#fieldtypes-s3file) + +Keystone also has [Relationship fields](http://keystonejs.com/docs/database#relationships) for managing one-to-many and many-to-many relationships between different models. ### Running KeystoneJS in Production When you deploy your KeystoneJS app to production, be sure to set your `ENV` environment variable to `production`. - You can do this by setting `NODE_ENV=production` in your `.env` file, which gets handled by [dotenv](https://github.com/motdotla/dotenv). -Setting your environment enables certain features (including template caching, simpler error reporting, and HTML minification) that are important in production but annoying in development. +Setting your environment enables certain features, including template caching, simpler error reporting and html minification, that are important in production but annoying in development. -## Community +### Linking Keystone for Development and Testing -We have a friendly, growing community and welcome everyone to get involved: +If you want to test or develop against the `master` branch of KeystoneJS (or against your own branch), rather than a published version on **npm**, you just need to check it out then use `npm link` to link it to your project. On Mac OS, this is done like this: -* Follow [@KeystoneJS](https://twitter.com/KeystoneJS) on twitter for news and announcements. -* Ask technical questions on [Stack Overflow](http://stackoverflow.com/questions/tagged/keystone.js) and tag them `keystonejs.` -* Report bugs and feature suggestions on our GitHub [issue tracker](https://github.com/keystonejs/keystone/issues). -* Join the [KeystoneJS Slack](https://launchpass.com/keystonejs) for general discussion with the Keystone community and contributors. +* Clone KeystoneJS locally, e.g. to `~/Development/KeystoneJS` +* From the KeystoneJS directory, run `sudo npm link` (you will need to enter your system password) +* From your project directory, e.g. `~/Development/MySite` (the one with your `package.json` file in it) run `npm link keystone`. This will create a link between `~/Development/MySite/node_modules/keystone` and `~/Development/KeystoneJS`. -We love to hear feedback about Keystone and the projects you're using it for. Ping us at [@KeystoneJS](https://twitter.com/KeystoneJS) on Twitter. - -### Contributing +Then `require('keystone')` normally in your app - the development copy will be used. Note that running `npm update` will ignore new versions of keystone that have been published. -If you can, please contribute by reporting issues, discussing ideas, helping answer questions from other developers, or submitting pull requests with patches and new features. We do our best to respond to all issues and pull requests, and make patch releases to npm regularly. +To go back to using a published version of KeystoneJS from npm, from your project directory, run `npm unlink keystone` then `npm install`. -If you're going to contribute code, please follow our [coding standards](https://github.com/keystonejs/keystone/wiki/Coding-Standards) and read our [Contributing Guide](https://github.com/keystonejs/keystone/blob/master/CONTRIBUTING.md). +#### Testing +To run the test suite run `npm test`. -### Related Projects -If you are using KeystoneJS in any projects we encourage you to add to our [Related Projects Page](https://github.com/keystonejs/keystone/wiki/Related-Projects). This is also the place to find generators and other projects that bundle KeystoneJS. +## Thanks -### Thanks +KeystoneJS is a free and open source community-driven project. Thanks to our many [contributors](https://github.com/keystonejs/keystone/graphs/contributors) and [users](https://github.com/keystonejs/keystone/stargazers) for making it great. -KeystoneJS is a free and open source community-driven project. Thanks to our many [contributors](https://github.com/keystonejs/keystone/graphs/contributors) and [users](https://github.com/keystonejs/keystone/stargazers) for making it great. +Keystone's development is led by [Jed Watson](https://github.com/JedWatson), [Joss Mackison](https://github.com/jossmac) and [Max Stoiber](https://github.com/mxstbr) and supported by [Thinkmill](http://thinkmill.com.au) in Sydney, Australia. -Keystone's development has been led by key contributors including [Jed Watson](https://github.com/JedWatson), [Joss Mackison](https://github.com/jossmac), and [Max Stoiber](https://github.com/mxstbr) and is proudly supported by [Thinkmill](https://thinkmill.com.au) in Sydney, Australia. ## License (The MIT License) -Copyright (c) 2016-2018 Jed Watson +Copyright (c) 2016 Jed Watson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/admin/client/App/components/Navigation/Primary/index.js b/admin/client/App/components/Navigation/Primary/index.js index a0f003ab4f..4be38b24c3 100644 --- a/admin/client/App/components/Navigation/Primary/index.js +++ b/admin/client/App/components/Navigation/Primary/index.js @@ -91,8 +91,7 @@ var PrimaryNavigation = React.createClass({ return this.props.sections.map((section) => { // Get the link and the class name - const to = !section.lists[0].external && `${Keystone.adminPath}/${section.lists[0].path}`; - const href = section.lists[0].external && section.lists[0].path; + const href = section.lists[0].external ? section.lists[0].path : `${Keystone.adminPath}/${section.lists[0].path}`; const isActive = this.props.currentSectionKey && this.props.currentSectionKey === section.key; const className = isActive ? 'primary-navbar__item--active' : null; @@ -102,8 +101,7 @@ var PrimaryNavigation = React.createClass({ key={section.key} label={section.label} className={className} - to={to} - href={href} + to={href} > {section.label} diff --git a/admin/client/App/elemental/Button/index.js b/admin/client/App/elemental/Button/index.js index b105b83131..e0a10b5045 100644 --- a/admin/client/App/elemental/Button/index.js +++ b/admin/client/App/elemental/Button/index.js @@ -23,7 +23,7 @@ class Button extends Component { render () { var { active, - cssStyles, + aphroditeStyles, block, className, color, @@ -43,7 +43,7 @@ class Button extends Component { block ? commonClasses.block : null, disabled ? commonClasses.disabled : null, active ? variantClasses.active : null, - ...cssStyles + ...aphroditeStyles ); if (className) { props.className += (' ' + className); @@ -64,23 +64,23 @@ class Button extends Component { Button.propTypes = { active: PropTypes.bool, + aphroditeStyles: PropTypes.arrayOf(PropTypes.shape({ + _definition: PropTypes.object, + _name: PropTypes.string, + })), block: PropTypes.bool, color: PropTypes.oneOf(BUTTON_COLORS), component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string, ]), - cssStyles: PropTypes.arrayOf(PropTypes.shape({ - _definition: PropTypes.object, - _name: PropTypes.string, - })), disabled: PropTypes.bool, href: PropTypes.string, size: PropTypes.oneOf(BUTTON_SIZES), variant: PropTypes.oneOf(BUTTON_VARIANTS), }; Button.defaultProps = { - cssStyles: [], + aphroditeStyles: [], color: 'default', variant: 'fill', }; diff --git a/admin/client/App/elemental/FormField/index.js b/admin/client/App/elemental/FormField/index.js index c5363e5019..2a41aec77b 100644 --- a/admin/client/App/elemental/FormField/index.js +++ b/admin/client/App/elemental/FormField/index.js @@ -17,7 +17,7 @@ class FormField extends Component { render () { const { formLayout = 'basic', labelWidth } = this.context; const { - cssStyles, + aphroditeStyles, children, className, cropLabel, @@ -31,7 +31,7 @@ class FormField extends Component { classes.FormField, classes['FormField--form-layout-' + formLayout], offsetAbsentLabel ? classes['FormField--offset-absent-label'] : null, - cssStyles + aphroditeStyles ); if (className) { props.className += (' ' + className); @@ -75,12 +75,12 @@ FormField.childContextTypes = { formFieldId: PropTypes.string, }; FormField.propTypes = { - children: PropTypes.node, - cropLabel: PropTypes.bool, - cssStyles: PropTypes.oneOfType([ + aphroditeStyles: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.shape(stylesShape)), PropTypes.shape(stylesShape), ]), + children: PropTypes.node, + cropLabel: PropTypes.bool, htmlFor: React.PropTypes.string, label: React.PropTypes.string, offsetAbsentLabel: React.PropTypes.bool, diff --git a/admin/client/App/elemental/FormInput/index.js b/admin/client/App/elemental/FormInput/index.js index cc03405c2a..5a22203d21 100644 --- a/admin/client/App/elemental/FormInput/index.js +++ b/admin/client/App/elemental/FormInput/index.js @@ -15,7 +15,7 @@ class FormInput extends Component { } render () { const { - cssStyles, + aphroditeStyles, className, disabled, id, @@ -36,7 +36,7 @@ class FormInput extends Component { classes['FormInput__size--' + size], disabled ? classes['FormInput--disabled'] : null, formLayout ? classes['FormInput--form-layout-' + formLayout] : null, - ...concatClassnames(cssStyles) + ...concatClassnames(aphroditeStyles) ); if (className) { props.className += (' ' + className); @@ -61,7 +61,7 @@ const stylesShape = { }; FormInput.propTypes = { - cssStyles: PropTypes.oneOfType([ + aphroditeStyles: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.shape(stylesShape)), PropTypes.shape(stylesShape), ]), diff --git a/admin/client/App/elemental/FormInput/noedit.js b/admin/client/App/elemental/FormInput/noedit.js index a4f4287768..eda86bb033 100644 --- a/admin/client/App/elemental/FormInput/noedit.js +++ b/admin/client/App/elemental/FormInput/noedit.js @@ -56,6 +56,7 @@ const classes = { borderWidth: theme.input.border.width, color: theme.color.gray80, display: 'inline-block', + height: theme.input.height, lineHeight: theme.input.lineHeight, padding: `0 ${theme.input.paddingHorizontal}`, transition: 'border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s', diff --git a/admin/client/App/elemental/FormLabel/index.js b/admin/client/App/elemental/FormLabel/index.js index de50ee137f..accaaa44a3 100644 --- a/admin/client/App/elemental/FormLabel/index.js +++ b/admin/client/App/elemental/FormLabel/index.js @@ -3,7 +3,7 @@ import React, { PropTypes } from 'react'; import classes from './styles'; function FormLabel ({ - cssStyles, + aphroditeStyles, className, component: Component, cropText, @@ -20,7 +20,7 @@ function FormLabel ({ classes.FormLabel, formLayout ? classes['FormLabel--form-layout-' + formLayout] : null, cropText ? classes['FormLabel--crop-text'] : null, - cssStyles + aphroditeStyles ); if (className) { props.className += (' ' + className); @@ -41,15 +41,15 @@ const stylesShape = { }; FormLabel.propTypes = { + aphroditeStyles: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.shape(stylesShape)), + PropTypes.shape(stylesShape), + ]), component: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, ]), cropText: PropTypes.bool, - cssStyles: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.shape(stylesShape)), - PropTypes.shape(stylesShape), - ]), }; FormLabel.defaultProps = { component: 'label', diff --git a/admin/client/App/elemental/Glyph/index.js b/admin/client/App/elemental/Glyph/index.js index 74300c9306..faf2cb80b3 100644 --- a/admin/client/App/elemental/Glyph/index.js +++ b/admin/client/App/elemental/Glyph/index.js @@ -10,7 +10,7 @@ import classes from './styles'; // font and CSS; inflating the project size function Glyph ({ - cssStyles, + aphroditeStyles, className, color, component: Component, @@ -24,7 +24,7 @@ function Glyph ({ classes.glyph, colorIsValidType && classes['color__' + color], classes['size__' + size], - cssStyles + aphroditeStyles ) + ` ${octicons[name]}`; if (className) { props.className += (' ' + className); @@ -40,14 +40,14 @@ function Glyph ({ }; Glyph.propTypes = { + aphroditeStyles: PropTypes.shape({ + _definition: PropTypes.object, + _name: PropTypes.string, + }), color: PropTypes.oneOfType([ PropTypes.oneOf(Object.keys(colors)), PropTypes.string, // support random color strings ]), - cssStyles: PropTypes.shape({ - _definition: PropTypes.object, - _name: PropTypes.string, - }), name: PropTypes.oneOf(Object.keys(octicons)).isRequired, size: PropTypes.oneOf(Object.keys(sizes)), }; diff --git a/admin/client/App/elemental/GlyphButton/index.js b/admin/client/App/elemental/GlyphButton/index.js index af9135a410..0e74b8527a 100644 --- a/admin/client/App/elemental/GlyphButton/index.js +++ b/admin/client/App/elemental/GlyphButton/index.js @@ -28,7 +28,7 @@ function GlyphButton ({ const icon = ( + {isLeft && icon} {children} {isRight && icon} diff --git a/admin/client/App/elemental/InlineGroup/index.js b/admin/client/App/elemental/InlineGroup/index.js index f6626c4615..b7a5027339 100644 --- a/admin/client/App/elemental/InlineGroup/index.js +++ b/admin/client/App/elemental/InlineGroup/index.js @@ -4,7 +4,7 @@ import React, { cloneElement, Children, PropTypes } from 'react'; // NOTE: only accepts InlineGroupSection as a single child function InlineGroup ({ - cssStyles, + aphroditeStyles, block, children, className, @@ -16,7 +16,7 @@ function InlineGroup ({ props.className = css( classes.group, !!block && classes.block, - cssStyles + aphroditeStyles ); if (className) { props.className += (' ' + className); @@ -28,7 +28,7 @@ function InlineGroup ({ // normalize the count const count = buttons.length - 1; - // clone children and apply classNames that glamor can target + // clone children and apply classNames that aphrodite can target props.children = buttons.map((c, idx) => { if (!c) return null; @@ -53,16 +53,16 @@ function InlineGroup ({ }; InlineGroup.propTypes = { + aphroditeStyles: PropTypes.shape({ + _definition: PropTypes.object, + _name: PropTypes.string, + }), block: PropTypes.bool, component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string, ]), contiguous: PropTypes.bool, - cssStyles: PropTypes.shape({ - _definition: PropTypes.object, - _name: PropTypes.string, - }), }; InlineGroup.defaultProps = { component: 'div', diff --git a/admin/client/App/elemental/InlineGroupSection/index.js b/admin/client/App/elemental/InlineGroupSection/index.js index b6cf3b55ff..a836dc936a 100644 --- a/admin/client/App/elemental/InlineGroupSection/index.js +++ b/admin/client/App/elemental/InlineGroupSection/index.js @@ -6,7 +6,7 @@ import classes from './styles'; function InlineGroupSection ({ active, - cssStyles, + aphroditeStyles, children, className, contiguous, @@ -20,19 +20,19 @@ function InlineGroupSection ({ // A `contiguous` section must manipulate it's child directly // A separate (default) section just wraps the child return contiguous ? cloneElement(children, { - cssStyles: [ + aphroditeStyles: [ classes.contiguous, classes['contiguous__' + position], active ? classes.active : null, grow ? classes.grow : null, - cssStyles, + aphroditeStyles, ], ...props, }) : (
{children}
diff --git a/admin/client/App/elemental/LoadingButton/index.js b/admin/client/App/elemental/LoadingButton/index.js index c9da922513..eaec19dbf8 100644 --- a/admin/client/App/elemental/LoadingButton/index.js +++ b/admin/client/App/elemental/LoadingButton/index.js @@ -34,7 +34,7 @@ function LoadingButton ({ children, loading, ...props }) { : 0, }; - // render everything + // render all that shit return ( @@ -137,15 +135,14 @@ const classes = { marginTop: 10, position: 'absolute', left: 0, - zIndex: 500, + zIndex: 2, }, swatch: { borderRadius: 1, - boxShadow: '0 0 0 1px rgba(0,0,0,0.1)', + boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.1)', display: 'block', - ' svg': { - display: 'block', - }, + height: '100%', + width: '100%', }, }; diff --git a/fields/types/color/colored-swatch.js b/fields/types/color/colored-swatch.js deleted file mode 100644 index ead482a7f6..0000000000 --- a/fields/types/color/colored-swatch.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = ( - ` - - - - ` -); diff --git a/fields/types/date/DateColumn.js b/fields/types/date/DateColumn.js index a009d3a646..d3c4880da0 100644 --- a/fields/types/date/DateColumn.js +++ b/fields/types/date/DateColumn.js @@ -10,19 +10,12 @@ var DateColumn = React.createClass({ data: React.PropTypes.object, linkTo: React.PropTypes.string, }, - toMoment (value) { - if (this.props.col.field.isUTC) { - return moment.utc(value); - } else { - return moment(value); - } - }, getValue () { const value = this.props.data.fields[this.props.col.path]; if (!value) return null; const format = (this.props.col.type === 'datetime') ? 'MMMM Do YYYY, h:mm:ss a' : 'MMMM Do YYYY'; - return this.toMoment(value).format(format); + return moment(value).format(format); }, render () { const value = this.getValue(); diff --git a/fields/types/date/DateField.js b/fields/types/date/DateField.js index 3bb5661bb5..07ea682997 100644 --- a/fields/types/date/DateField.js +++ b/fields/types/date/DateField.js @@ -28,7 +28,6 @@ module.exports = Field.create({ note: React.PropTypes.string, onChange: React.PropTypes.func, path: React.PropTypes.string, - todayButton: React.PropTypes.bool, value: React.PropTypes.string, }, @@ -86,12 +85,9 @@ module.exports = Field.create({ value={value} /> - { - this.props.todayButton - &&
- -
- } +
+ +
); }, diff --git a/fields/types/date/DateType.js b/fields/types/date/DateType.js index 5d25164900..11499a067d 100644 --- a/fields/types/date/DateType.js +++ b/fields/types/date/DateType.js @@ -13,13 +13,12 @@ function date (list, path, options) { this._nativeType = Date; this._underscoreMethods = ['format', 'moment', 'parse']; this._fixedSize = 'medium'; - this._properties = ['formatString', 'yearRange', 'isUTC', 'inputFormat', 'todayButton']; + this._properties = ['formatString', 'yearRange', 'isUTC', 'inputFormat']; this.parseFormatString = options.inputFormat || 'YYYY-MM-DD'; this.formatString = (options.format === false) ? false : (options.format || 'Do MMM YYYY'); this.yearRange = options.yearRange; this.isUTC = options.utc || false; - this.todayButton = typeof options.todayButton !== 'undefined' ? options.todayButton : true; /* * This offset is used to determine whether or not a stored date is probably corrupted or not. diff --git a/fields/types/date/Readme.md b/fields/types/date/Readme.md index 8006694fc1..4e6579cce3 100644 --- a/fields/types/date/Readme.md +++ b/fields/types/date/Readme.md @@ -16,25 +16,16 @@ String parsing with moment will be done using the `inputFormat` option, which de ``` ## Options -* `inputFormat` `String` - -How the field interpret string input. See moment documentation for more information on available options. - -Defaults to 'YYYY-MM-DD' * `format` `String` -The default format pattern data will be returned in from the database. - -Defaults to 'Do MMM YYYY' +The default format pattern to use, defaults to Do MMM YYYY -See the [momentjs format docs](http://momentjs.com/docs/#/displaying/format/) for information on the supported formats and options. - -* `todayButton` `Boolean` +* `yearRange` `Array` `minYear, maxYear` -Determines if the Today button will be displayed. +The default range of years to be displayed. -Defaults to 'true' +See the [momentjs format docs](http://momentjs.com/docs/#/displaying/format/) for information on the supported formats and options. ## Methods diff --git a/fields/types/datearray/Readme.md b/fields/types/datearray/Readme.md index 3939d06092..77c2f485f3 100644 --- a/fields/types/datearray/Readme.md +++ b/fields/types/datearray/Readme.md @@ -1,9 +1,6 @@ # Datearray field Stores an `Array` of `Dates` in the model. -In the admin UI displays a Date field, with an 'add item' button below it. - -Each item in the date array is validated and parsed using [momentjs](momentjs.com). See the moment documents for what valid formats are. ## Options @@ -15,11 +12,11 @@ Defaults to `" | "`; ### `parseFormat` `String` -The default format used to validate information being added. Defaults to `"'YYYY-MM-DD'"`. Uses moment to parse the input with the format string. +The default date format, defaults to `"'YYYY-MM-DD'"` ### `format` `String` -The default format to display information in. Defaults to `"'Do MMM YYYY'"`. This is parsed using moment. +The default format pattern to use, defaults to `"'Do MMM YYYY'"` ## Methods diff --git a/fields/types/datearray/test/explorer.js b/fields/types/datearray/test/explorer.js index 5cebddbfb3..e47b5a759c 100644 --- a/fields/types/datearray/test/explorer.js +++ b/fields/types/datearray/test/explorer.js @@ -1,6 +1,6 @@ module.exports = { - Field: require('../DateArrayField'), - Filter: require('../DateArrayFilter'), + Field: require('../DatearrayField'), + Filter: require('../DatearrayFilter'), section: 'Date', spec: { label: 'Datearray', diff --git a/fields/types/datetime/Readme.md b/fields/types/datetime/Readme.md index d408de5a87..3e8c001285 100644 --- a/fields/types/datetime/Readme.md +++ b/fields/types/datetime/Readme.md @@ -1,7 +1,7 @@ -# DateTime Field +# `DateTime` Field Stores a `String` of both date and time in the model. -Displayed as a date and time picker in the Admin UI. +Displayed as a date and time picker in the Admin UI. Internally uses [moment.js](http://momentjs.com/) to manage date parsing, formatting and comparison. @@ -17,23 +17,12 @@ String parsing with moment will be done using the `parseFormat` option, which de ## Options -* `parseFormat` `string` - -The default pattern to read in values with. Defaults to an array of values to try: - -`['YYYY-MM-DD', 'YYYY-MM-DD h:m:s a', 'YYYY-MM-DD h:m a', 'YYYY-MM-DD H:m:s', 'YYYY-MM-DD H:m', 'YYYY-MM-DD h:mm:s a Z', moment.ISO_8601]` - - * `format` `string` -The default format pattern to use when display the information. Defaults to `Do MMM YYYY hh:mm:ss a` +The default format pattern to use, defaults to `Do MMM YYYY hh:mm:ss a` See the [momentjs format docs](http://momentjs.com/docs/#/displaying/format/) for information on the supported formats and options. -`utc` `boolean` - -Sets whether the string should be displayed in the admin UI in UTC time or local time. Defaults to `false`. - ## Methods ### `updateItem` diff --git a/fields/types/embedly/EmbedlyType.js b/fields/types/embedly/EmbedlyType.js index 2bfa7d9a3a..b0e2cad1a7 100644 --- a/fields/types/embedly/EmbedlyType.js +++ b/fields/types/embedly/EmbedlyType.js @@ -221,29 +221,24 @@ embedly.prototype.inputIsValid = function () { embedly.prototype.updateItem = function (item, data, callback) { // TODO: This could be more granular and check for actual changes to values, // see the Location field for an example - - // This field type is never editable, so to ensure that we don't inadvertently reset the fields on this item with a null value - // A conditional has been added to negate updating this item should the fromPath on the passed in data object be the same as that on the item. - if (data[this.fromPath] !== item[this.fromPath]) { - item.set(item.set(this.path, { - exists: data[this.paths.exists], - type: data[this.paths.type], - title: data[this.paths.title], - url: data[this.paths.url], - width: data[this.paths.width], - height: data[this.paths.height], - version: data[this.paths.version], - description: data[this.paths.description], - html: data[this.paths.html], - authorName: data[this.paths.authorName], - authorUrl: data[this.paths.authorUrl], - providerName: data[this.paths.providerName], - providerUrl: data[this.paths.providerUrl], - thumbnailUrl: data[this.paths.thumbnailUrl], - thumbnailWidth: data[this.paths.thumbnailWidth], - thumbnailHeight: data[this.paths.thumbnailHeight], - })); - } + item.set(item.set(this.path, { + exists: data[this.paths.exists], + type: data[this.paths.type], + title: data[this.paths.title], + url: data[this.paths.url], + width: data[this.paths.width], + height: data[this.paths.height], + version: data[this.paths.version], + description: data[this.paths.description], + html: data[this.paths.html], + authorName: data[this.paths.authorName], + authorUrl: data[this.paths.authorUrl], + providerName: data[this.paths.providerName], + providerUrl: data[this.paths.providerUrl], + thumbnailUrl: data[this.paths.thumbnailUrl], + thumbnailWidth: data[this.paths.thumbnailWidth], + thumbnailHeight: data[this.paths.thumbnailHeight], + })); process.nextTick(callback); }; diff --git a/fields/types/embedly/Readme.md b/fields/types/embedly/Readme.md index 5d5367d71b..1f7ea2f923 100644 --- a/fields/types/embedly/Readme.md +++ b/fields/types/embedly/Readme.md @@ -8,7 +8,7 @@ It stores the retrieved data (which includes the provider, media type, full URL, The API call to retrieve the data is implemented as a pre-save hook, and is only triggered if the `from path` value has changed. -See the [Embed.ly configuration documentation](/configuration#embedly) for details on how to set up Embed.ly in KeystoneJS. +See the [Embed.ly configuration documentation](http://keystonejs.com/docs/configuration#services-embedly) for details on how to set up Embed.ly in KeystoneJS. ```js { type: Types.Embedly, from: 'path' } diff --git a/fields/types/file/FileField.js b/fields/types/file/FileField.js index 7b0981ec3b..89d36408f0 100644 --- a/fields/types/file/FileField.js +++ b/fields/types/file/FileField.js @@ -14,7 +14,6 @@ import { } from '../../../admin/client/App/elemental'; import FileChangeMessage from '../../components/FileChangeMessage'; import HiddenFileInput from '../../components/HiddenFileInput'; -import ImageThumbnail from '../../components/ImageThumbnail'; let uploadInc = 1000; @@ -32,7 +31,6 @@ module.exports = Field.create({ label: PropTypes.string, note: PropTypes.string, path: PropTypes.string.isRequired, - thumb: PropTypes.bool, value: PropTypes.shape({ filename: PropTypes.string, // TODO: these are present but not used in the UI, @@ -75,13 +73,6 @@ module.exports = Field.create({ ? this.state.userSelectedFile.name : this.props.value.filename; }, - getFileUrl () { - return this.props.value && this.props.value.url; - }, - isImage () { - const href = this.props.value ? this.props.value.url : undefined; - return href && href.match(/\.(jpeg|jpg|gif|png|svg)$/i) != null; - }, // ============================== // METHODS @@ -135,7 +126,7 @@ module.exports = Field.create({ return (
{(this.hasFile() && !this.state.removeExisting) ? ( - + {this.getFilename()} ) : null} @@ -199,44 +190,23 @@ module.exports = Field.create({ return null; } }, - renderImagePreview () { - const imageSource = this.getFileUrl(); - return ( - - - - ); - }, renderUI () { - const { label, note, path, thumb } = this.props; - const isImage = this.isImage(); - const hasFile = this.hasFile(); - - const previews = ( -
- {isImage && thumb && this.renderImagePreview()} - {hasFile && this.renderFileNameAndChangeMessage()} -
- ); + const { label, note, path } = this.props; const buttons = ( -
+
- {hasFile && this.renderClearButton()} + {this.hasFile() && this.renderClearButton()}
); + return (
{this.shouldRenderField() ? (
- {previews} + {this.hasFile() && this.renderFileNameAndChangeMessage()} {buttons} ) : (
- {hasFile + {this.hasFile() ? this.renderFileNameAndChangeMessage() : no file}
diff --git a/fields/types/file/Readme.md b/fields/types/file/Readme.md index 4b69d00337..8333868f94 100644 --- a/fields/types/file/Readme.md +++ b/fields/types/file/Readme.md @@ -2,8 +2,6 @@ The File fields stores a file using Keystone Storage and a Storage Adapter (e.g. `FS`, `S3`, etc). You have to configure a `Storage` instance first then provide it in the options for the field, e.g. -Storage adapters are built per field. Look up the documentation on the individual adapters. - ```js var storage = new keystone.Storage({ adapter: keystone.Storage.Adapters.FS, @@ -32,6 +30,10 @@ The field stores a nested `Object` in the model. The nested schema is based on t Different adapters may add additional paths to the field schema - see the documentation for the Adapter you're using for more information. +## Options + +> TODO + ## Updates ```js @@ -108,6 +110,10 @@ To reset the field value _without_ deleting the stored file, provide an empty / ## Methods +### `format` + +> TODO + ### `upload` This method uploads a file using your storage provider. You can call it directly on the list: @@ -143,8 +149,13 @@ There is no way to upload directly from a buffer at the moment, you must upload ### `remove` -Calls the `removeFile` on the storage adapter provided. +> TODO ### `reset` -Resets all fields in the storage schema. +> TODO + + +## Filtering + +> TODO diff --git a/fields/types/geopoint/Readme.md b/fields/types/geopoint/Readme.md index 2dbd084844..f7492569f4 100644 --- a/fields/types/geopoint/Readme.md +++ b/fields/types/geopoint/Readme.md @@ -2,9 +2,7 @@ Stores an `Array` of `Number` values in the model. -Displayed as two text input fields in the Admin UI of latitude and longitude. - -If you are updating the database, it requires the two numbers to be in [longitude, latitude] order. +Displayed as two text input fields in the Admin UI. ## Example ```js diff --git a/fields/types/geopoint/test/explorer.js b/fields/types/geopoint/test/explorer.js index ed5435be84..1ccf071633 100644 --- a/fields/types/geopoint/test/explorer.js +++ b/fields/types/geopoint/test/explorer.js @@ -1,6 +1,6 @@ module.exports = { - Field: require('../GeoPointField'), - Filter: require('../GeoPointFilter'), + Field: require('../GeopointField'), + Filter: require('../GeopointFilter'), section: 'Miscellaneous', spec: { label: 'Geopoint', diff --git a/fields/types/localfile/Readme.md b/fields/types/localfile/Readme.md index df2c9edfa6..ee3e90a575 100644 --- a/fields/types/localfile/Readme.md +++ b/fields/types/localfile/Readme.md @@ -1,7 +1,5 @@ # LocalFile Field -> Warning: the LocalFile Field has been deprecated. Please use the [File](/api/field/File) and a storage adapter going forward. - `Object` — Displayed as a file upload field in the Admin UI Stores files on the local file system. diff --git a/fields/types/localfiles/readme.md b/fields/types/localfiles/readme.md deleted file mode 100644 index 02a699fc88..0000000000 --- a/fields/types/localfiles/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# LocalFiles - -> Warning: the LocalFiles Field has been deprecated. Please use the [File](/api/field/File) and a storage adapter going forward. diff --git a/fields/types/location/LocationType.js b/fields/types/location/LocationType.js index e7cc0bbac0..db952c454a 100644 --- a/fields/types/location/LocationType.js +++ b/fields/types/location/LocationType.js @@ -320,8 +320,8 @@ location.prototype.updateItem = function (item, data, callback) { if (doGoogleLookup) { var googleUpdateMode = this.getValueFromData(data, '_improve_overwrite') ? 'overwrite' : true; this.googleLookup(item, false, googleUpdateMode, function (err, location, result) { - // TODO: we are currently log the error but otherwise discard it; should probably be returned.. needs consideration - if (err) console.error(err); + // TODO: we are currently discarding the error; it should probably be + // sent back in the response, needs consideration callback(); }); return; @@ -431,8 +431,7 @@ location.prototype.googleLookup = function (item, region, update, callback) { _.forEach(result.address_components, function (val) { if (_.indexOf(val.types, 'street_number') >= 0) { - location.street1 = location.street1 || []; - location.street1.unshift(val.long_name); + location.street1 = [val.long_name]; } if (_.indexOf(val.types, 'route') >= 0) { location.street1 = location.street1 || []; @@ -451,23 +450,6 @@ location.prototype.googleLookup = function (item, region, update, callback) { if (_.indexOf(val.types, 'postal_code') >= 0) { location.postcode = val.short_name; } - - // These address_components could arguable all map to our 'number' field - // .. https://developers.google.com/maps/documentation/geocoding/intro#GeocodingResponses - - // `subpremise` - "Indicates a first-order entity below a named location, usually a singular building within a collection of buildings with a common name" - // In practice this is often the unit/apartment number or level and is not always included - if (_.indexOf(val.types, 'subpremise') >= 0) { - location.number = val.short_name; - } - - // These are all optional (rarely used?) and probably shouldn't replace the number if already set (due to subpremise) - // `floor` - Indicates the floor of a building address. - // `post_box` - Indicates a specific postal box. - // `room` - Indicates the room of a building address. - if (_.indexOf(val.types, 'floor') >= 0 || _.indexOf(val.types, 'post_box') >= 0 || _.indexOf(val.types, 'room') >= 0) { - location.number = location.number || val.short_name; - } }); if (Array.isArray(location.street1)) { diff --git a/fields/types/location/Readme.md b/fields/types/location/Readme.md index 1cc0834cf8..cb9ba0b33b 100644 --- a/fields/types/location/Readme.md +++ b/fields/types/location/Readme.md @@ -40,20 +40,6 @@ Google Places integration requires the `google api key` option to be set for Key > Important: as per the MongoDB convention, the order for the geo array must be lng, lat which is the opposite of the order used by Google's API. -`enableImprove` `boolean` - -Options sets `enableMapsAPI` to true. If it is not set, `enableMapsAPI` is set to true if `google server api key` is set in keystone. - -`required` `Array or String or Boolean` - -Required works differently for location than for most other properties. There are three different types of require. - -If passed an `array`, it uses it to set which parts of the location field are required. - -If passed a comma-separated-value `string`, it will transform it into an array of required parts of the location field. - -If any positive value is passed in, the location field becomes required for validation, including either of the above options. - ## Underscore methods `googleLookup(region, update, callback)` - autodetect the full address and lng, lat from the stored value. diff --git a/fields/types/markdown/MarkdownField.js b/fields/types/markdown/MarkdownField.js index bb304f4c96..6e69b415a0 100644 --- a/fields/types/markdown/MarkdownField.js +++ b/fields/types/markdown/MarkdownField.js @@ -109,15 +109,6 @@ var renderMarkdown = function (component) { $(component.refs.markdownTextarea).markdown(options); }; -// Simple escaping of html tags and replacing newlines for displaying the raw markdown string within an html doc -var escapeHtmlForRender = function (html) { - return html - .replace(/\&/g, '&') - .replace(/\/g, '>') - .replace(/\n/g, '
'); -}; - module.exports = Field.create({ displayName: 'MarkdownField', statics: { @@ -168,11 +159,15 @@ module.exports = Field.create({ }, renderValue () { - // We want to render the raw markdown string, without parsing it to html - // The markdown string *itself* may include html though so we need to escape it first - const innerHtml = (this.props.value && this.props.value.md) - ? escapeHtmlForRender(this.props.value.md) - : ''; + // TODO: victoriafrench - is this the correct way to do this? the object + // should be creating a default md where one does not exist imo. + + const innerHtml = ( + this.props.value !== undefined + && this.props.value.md !== undefined + ) + ? this.props.value.md.replace(/\n/g, '
') + : ''; return ( max) { - messages.push('Password must not be longer than ' + max + ' characters.'); + if (max && typeof passwordValue === 'string' && passwordValue.length > max) { + detail += 'password must not be longer than ' + max + ' characters\n'; } for (var prop in complexity) { - if (complexity[prop] && typeof pass === 'string') { - var complexityCheck = (regexChunk[prop]).test(pass); + if (complexity[prop] && typeof passwordValue === 'string') { + var complexityCheck = (regexChunk[prop]).test(passwordValue); if (!complexityCheck) { - messages.push(detailMsg[prop]); + detail += detailMsg[prop] + '\n'; } } } + result = detail.length === 0; - if (pass && typeof pass === 'string' && rejectCommon && dumbPasswords.check(pass)) { - messages.push('Password must not be a common, frequently-used password.'); - } - - return { - result: messages.length === 0, - detail: messages.join(' \n'), - }; + utils.defer(callback, result, detail); }; /** diff --git a/fields/types/password/Readme.md b/fields/types/password/Readme.md index 6f07c04a03..8d24c8160c 100644 --- a/fields/types/password/Readme.md +++ b/fields/types/password/Readme.md @@ -4,7 +4,7 @@ Stores a `String` in the model. Displayed as a password field in the Admin UI, with a 'change' button. -Passwords are automatically encrypted with `bcrypt`, and expose a method to compare a string to the encrypted hash. +Passwords are automatically encrypted with bcrypt, and expose a method to compare a string to the encrypted hash. > Note: The encryption happens with a **pre-save hook** added to the **schema**, so passwords set will not be encrypted until an item has been saved to the database. @@ -18,62 +18,35 @@ Passwords are automatically encrypted with `bcrypt`, and expose a method to comp `workFactor` `Number` -Supplied as the `bcrypt` cost parameter; controls the computational cost of generating and validating a hash. -Higher values are slower but, since they take longer to generate, more secure against brute force attacks. - -Defaults to `10`. -At this level, a modern laptop (Late 2016 MacBook Pro, 3.3 GHz Intel Core i7) can produce around ~4 hashes/second. - -The `bcrypt` algorithim applies this value as a power of two. -As such, passwords with a workfactor of `11` will take twice as long to store and validate as those with a workfactor of `10`. - -Values lower than `4` are ignored by the underlying implementation (a value `10` is substituted). - -`min` `Number` - -Defines the minimum allowed password length in characters. - -Defaults to `8` in accordance with the [NIST Digital Identity Guidelines](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf). - -`max` `Number` - -Defines the maximum allowed password length in characters. - -The `bcrypt` algorithm, used by this field, operates on a 72 byte value. -Most implementation (including [the one we use](https://www.npmjs.com/package/bcrypt-nodejs)), silently truncate the string provided if it exceeds this limit. -The `max` length option defaults to 72 characters in an attempt to align with this limit. - -> Note: If multi-byte (ie. non-ASCII) characters are allowed, it will be possible to exceed the 72 byte limit without triggering the 72 character validation limit. - -Can be set to `false` to disable the max length check. - -> Note: Disabling `max` or setting its value to >72 prevents validation errors but does not address the underlying algorithmic limitation. - -`rejectCommon` `Boolean` - -Controls whether values should be validated against a list of known-common passwords. - -Defaults to `true` in accordance with the [NIST Digital Identity Guidelines](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf). - -Implemented with the [`dumb-passwords` package](https://www.npmjs.com/package/dumb-passwords) -which validates against 10,000 common passwords complied by [security analyst Mark Burnett](https://xato.net/10-000-top-passwords-6d6380716fe0). +The bcrypt workfactor to use when generating the hash, higher numbers are slower but more secure (defaults to `10`). `complexity` `Object` Allows to set complexity requirements: * `digitChar` `Boolean` - when set to `true`, requires at least one digit -* `spChar` `Boolean` - when set to `true`, requires at least one from the following special characters: `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `+` +* `spChar` `Boolean` - when set to `true`, requires at least one from the following special characters: !, @, #, $, %, ^, &, \*, (, ), + * `asciiChar` `Boolean` - when set to `true`, allows only ASCII characters (from range U+0020--U+007E) * `lowChar` `Boolean` - when set to `true`, requires at least one lower case character * `upperChar` `Boolean` - when set to `true`, requires at least one upper case character -Example: +### Example ```js { type: Types.Password, complexity: { digitChar: true, asciiChar: true } } ``` +`max` `Number` + +Sets the maximum password length; defaults to 72, in accordance with [bcrypt](https://www.google.com/search?q=bcrypt+max+length), which truncates the password to the first 72 bytes. + +Can be set to `false` to disable the max length. + +> Note: Disabling `max` or setting its value to >72 does not override the bcrypt specification. + +`min` `Number` + +Defines the minimum password length; disabled by default. ## Underscore methods diff --git a/fields/types/password/test/type.js b/fields/types/password/test/type.js index 2cadc166ab..9aeee4669a 100644 --- a/fields/types/password/test/type.js +++ b/fields/types/password/test/type.js @@ -142,22 +142,22 @@ exports.testFieldType = function (List) { }); describe('validateInput', function () { - it('should validate a matching password and confirm value', function (done) { + it('should validate a matching password- and confirm value', function (done) { List.fields.password.validateInput({ - password: 'vasjdhb273r8ywbfeuygr2834ryfhwubsudfih', - password_confirm: 'vasjdhb273r8ywbfeuygr2834ryfhwubsudfih', + password: 'asdf', + password_confirm: 'asdf', }, function (result) { demand(result).be.true(); done(); }); }); - it('should invalidate empty string input', function (done) { + it('should validate emtpy string input', function (done) { List.fields.password.validateInput({ password: '', password_confirm: '', }, function (result) { - demand(result).be.false(); + demand(result).be.true(); done(); }); }); @@ -179,12 +179,12 @@ exports.testFieldType = function (List) { }); }); - it('should invalidate undefined confirmation value', function (done) { + it('should validate undefined confirmation value', function (done) { List.fields.password.validateInput({ password: 'something', password_confirm: undefined, }, function (result) { - demand(result).be.false(); + demand(result).be.true(); done(); }); }); @@ -346,7 +346,7 @@ exports.testFieldType = function (List) { digitChar: 'nodigits', }, function (result, detail) { demand(result).be.false(); - demand(detail).be('enter at least one digit'); + demand(detail).be('enter at least one digit\n'); done(); }); }); @@ -356,7 +356,7 @@ exports.testFieldType = function (List) { spChar: 'nospecialchars', }, function (result, detail) { demand(result).be.false(); - demand(detail).be('enter at least one special character'); + demand(detail).be('enter at least one special character\n'); done(); }); }); @@ -366,7 +366,7 @@ exports.testFieldType = function (List) { asciiChar: 'םגפשבך', }, function (result, detail) { demand(result).be.false(); - demand(detail).be('Password must be longer than 8 characters. \nonly ASCII characters are allowed'); + demand(detail).be('only ASCII characters are allowed\n'); done(); }); }); @@ -376,7 +376,7 @@ exports.testFieldType = function (List) { lowChar: 'NOLOWERCASE', }, function (result, detail) { demand(result).be.false(); - demand(detail).be('use at least one lower case character'); + demand(detail).be('use at least one lower case character\n'); done(); }); }); @@ -386,7 +386,7 @@ exports.testFieldType = function (List) { upperChar: 'nouppercase', }, function (result, detail) { demand(result).be.false(); - demand(detail).be('use at least one upper case character'); + demand(detail).be('use at least one upper case character\n'); done(); }); }); @@ -587,7 +587,7 @@ exports.testFieldType = function (List) { }, }); } catch (err) { - demand(err.message).eql('FieldType.Password: options - maximum password length cannot be less than the minimum length.'); + demand(err.message).eql('FieldType.Password: options - min must be set at a lower value than max.'); done(); } }); diff --git a/fields/types/relationship/RelationshipField.js b/fields/types/relationship/RelationshipField.js index e4bdf88358..e757bd8494 100644 --- a/fields/types/relationship/RelationshipField.js +++ b/fields/types/relationship/RelationshipField.js @@ -68,7 +68,7 @@ module.exports = Field.create({ } // check if filtering by id and item was already saved - if (fieldName === '_id' && Keystone.item) { + if (fieldName === ':_id' && Keystone.item) { filters[key] = Keystone.item.id; return; } @@ -190,13 +190,8 @@ module.exports = Field.create({ }, renderSelect (noedit) { - const inputName = this.getInputName(this.props.path); - const emptyValueInput = (this.props.many && (!this.state.value || !this.state.value.length)) - ? : null; return (
- {/* This input ensures that an empty value is submitted when no related items are selected */} - {emptyValueInput} {/* This input element fools Safari's autocorrect in certain situations that completely break react-select */} Warning: the S3 File Field has been deprecated. Please use the [File](/api/field/File) and a storage adapter going forward. - - `Object` — Displayed as an file upload field in the Admin UI. Automatically manages files stored in [Amazon S3](http://aws.amazon.com/s3), including uploading and deleting. diff --git a/fields/types/s3file/S3FileType.js b/fields/types/s3file/S3FileType.js index a153649cff..e38eaee147 100644 --- a/fields/types/s3file/S3FileType.js +++ b/fields/types/s3file/S3FileType.js @@ -4,13 +4,10 @@ Deprecated. Using this field will now throw an error, and this code will be removed soon. See https://github.com/keystonejs/keystone/wiki/File-Fields-Upgrade-Guide - -TODO: this is used by keystone/admin/server/api/s3.js to generate headers, and should be factored out */ /* eslint-disable */ -var _ = require('lodash'); -var assign = require('object-assign'); + var loggedWarning = false; /** @@ -71,7 +68,7 @@ Object.defineProperty(s3file.prototype, 's3config', { */ s3file.prototype.addToSchema = function (schema) { - var knox = require('knox-s3'); + var knox = require('knox'); var field = this; var paths = this.paths = { @@ -336,7 +333,7 @@ s3file.prototype.generateHeaders = function (item, file, callback) { */ s3file.prototype.uploadFile = function (item, file, update, callback) { - var knox = require('knox-s3'); + var knox = require('knox'); var field = this; var path = field.options.s3path ? field.options.s3path + '/' : ''; var prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : ''; diff --git a/fields/types/select/Readme.md b/fields/types/select/Readme.md index bafa8f7aec..ac5a34ef64 100644 --- a/fields/types/select/Readme.md +++ b/fields/types/select/Readme.md @@ -2,7 +2,6 @@ Stores a `String` or `Number` in the model. Displayed as a select field in the Admin UI. -Does not allow for multiple items to be selected. If you want to provide multiple values, you can use `TextArray` or `NumberArray`, although neither will have the same constrained input. You can limit the options using a pre-save hook. ```js { type: Types.Select, options: 'first, second, third' } @@ -32,13 +31,12 @@ Ensures a value has been provided. Empty strings are never valid, even if specif ## Options -### `number` -`Boolean` when `true`, causes the value of the field to be stored as a `Number` instead of a `String` +### `number` `Boolean` +when `true`, causes the value of the field to be stored as a `Number` instead of a `String` ```js - { type: Types.Select, numeric: true, options: [{ value: 1, label: 'One' }, { value: 2, label: 'Two' }] } + { type: Types.Select, numeric: true, options: [{ value: 1, label: 'One' }, { value: 2, label: 'Two' } ``` - ### `emptyOption` `Boolean` when `undefined || true`, includes a blank value as the first option in the `