diff --git a/demo-projects/todo/index.js b/demo-projects/todo/index.js index 36e3450eed4..c4e1cb9dc31 100644 --- a/demo-projects/todo/index.js +++ b/demo-projects/todo/index.js @@ -1,9 +1,20 @@ const { Keystone } = require('@keystonejs/keystone'); const { MongooseAdapter } = require('@keystonejs/adapter-mongoose'); -const { Text } = require('@keystonejs/fields'); +const { Text, CalendarDay, Virtual } = require('@keystonejs/fields'); const { GraphQLApp } = require('@keystonejs/app-graphql'); const { AdminUIApp } = require('@keystonejs/app-admin-ui'); const { StaticApp } = require('@keystonejs/app-static'); +const { format, parseISO } = require('date-fns'); + +class _CalendarDayImpl extends CalendarDay.implementation { + gqlOutputFieldResolvers() { + return { + [`${this.path}`]: item => + item[this.path] && format(parseISO(item[this.path]), this.config.gqlFormat), + }; + } +} +const _CalendarDay = { ...CalendarDay, implementation: _CalendarDayImpl }; const keystone = new Keystone({ name: 'Keystone To-Do List', @@ -14,6 +25,17 @@ keystone.createList('Todo', { schemaDoc: 'A list of things which need to be done', fields: { name: { type: Text, schemaDoc: 'This is the thing you need to do', isRequired: true }, + date: { type: CalendarDay, adminDoc: 'For testing only' }, + niceDate: { + type: Virtual, + resolver: item => item.date && format(parseISO(item.date), 'do MMMM, yyyy'), + }, + nicerDate: { + type: Virtual, + resolver: (item, { formatAs = 'do MMMM, yyyy' }) => + item.date && format(parseISO(item.date), formatAs), + param: 'formatAs: String', + }, }, }); diff --git a/docs/tutorials/add-lists.md b/docs/tutorials/add-lists.md index a058b6d0ea1..d80792a2e77 100644 --- a/docs/tutorials/add-lists.md +++ b/docs/tutorials/add-lists.md @@ -69,11 +69,11 @@ module.exports = { // added fields deadline: { type: CalendarDay, - format: 'Do MMMM YYYY', + format: 'do MMMM yyyy', yearRangeFrom: '2019', yearRangeTo: '2029', isRequired: false, - defaultValue: new Date().toISOString('YYYY-MM-DD').substring(0, 10), + defaultValue: new Date().toISOString('YYYY-MM-DD').substring(0, 10), // Today's date }, assignee: { type: Text, diff --git a/packages/arch/packages/day-picker/src/TextDayTimePicker.js b/packages/arch/packages/day-picker/src/TextDayTimePicker.js index 73cfde63bbe..e2332957b13 100644 --- a/packages/arch/packages/day-picker/src/TextDayTimePicker.js +++ b/packages/arch/packages/day-picker/src/TextDayTimePicker.js @@ -35,7 +35,7 @@ function formatDateTime(date) { // why are we using moment when it's so large and provides a mutable API? // because chrono uses it and consistency is nice and // will probably make bugs with conversion less likely - return date ? moment.parseZone(date).format('h:mm A Do MMMM YYYY Z') : ''; + return date ? moment.parseZone(date).format('h:mm A do MMMM YYYY Z') : ''; } function parseDate(value) { diff --git a/packages/fields/src/types/CalendarDay/Implementation.js b/packages/fields/src/types/CalendarDay/Implementation.js index d24a3f1fed0..7b40b4f9482 100644 --- a/packages/fields/src/types/CalendarDay/Implementation.js +++ b/packages/fields/src/types/CalendarDay/Implementation.js @@ -36,7 +36,9 @@ export class CalendarDay extends Implementation { gqlOutputFields() { return [`${this.path}: String`]; } - + gqlOutputFieldResolvers() { + return { [`${this.path}`]: item => item[this.path] }; + } gqlQueryInputFields() { return [ ...this.equalityInputFields('String'), diff --git a/packages/fields/src/types/CalendarDay/README.md b/packages/fields/src/types/CalendarDay/README.md index abdf2b58450..8ff2dc02359 100644 --- a/packages/fields/src/types/CalendarDay/README.md +++ b/packages/fields/src/types/CalendarDay/README.md @@ -52,7 +52,7 @@ All date values must be in the 10 character ISO8601 format:`YYYY-MM-DD`. ### Filters -All filter fields expect values in the ISO8601 (`YYYY-MM-DD`) format. +All filter fields expect values in the ISO8601 (`yyyy-MM-dd`) format. | Field name | Type | Description | | :--------------- | :--------- | :----------------------------------------- | @@ -69,7 +69,7 @@ All filter fields expect values in the ISO8601 (`YYYY-MM-DD`) format. ### Mongoose adapter -In Mongoose the field is added using the `String` schema type. +In Mongoose the field is stored using the `String` schema type. The `isRequired` config option is enforced by KeystoneJS only. diff --git a/packages/fields/src/types/CalendarDay/views/Cell.js b/packages/fields/src/types/CalendarDay/views/Cell.js index ded38dd7e6a..c30292442a2 100644 --- a/packages/fields/src/types/CalendarDay/views/Cell.js +++ b/packages/fields/src/types/CalendarDay/views/Cell.js @@ -1,15 +1,4 @@ import { format, parseISO } from 'date-fns'; -const CalendarDayCell = ({ data, field: { config } }) => { - if (!data) { - return null; - } - - if (!config.format) { - return data; - } - - return format(parseISO(data), config.format); -}; - -export default CalendarDayCell; +export default ({ data, field: { format: formatString } }) => + data ? (formatString ? format(parseISO(data), formatString) : data) : null; diff --git a/packages/fields/src/types/CalendarDay/views/Controller.js b/packages/fields/src/types/CalendarDay/views/Controller.js index d699814394e..e20987daba0 100644 --- a/packages/fields/src/types/CalendarDay/views/Controller.js +++ b/packages/fields/src/types/CalendarDay/views/Controller.js @@ -1,6 +1,12 @@ import FieldController from '../../../Controller'; export default class CalendarDayController extends FieldController { + constructor({ format, yearRangeFrom, yearRangeTo, ...config }, ...args) { + super({ ...config }, ...args); + this.format = format; + this.yearRangeFrom = yearRangeFrom; + this.yearRangeTo = yearRangeTo; + } getFilterGraphQL = ({ type, value }) => { const key = type === 'is' ? `${this.path}` : `${this.path}_${type}`; return { [key]: value }; diff --git a/packages/fields/src/types/CalendarDay/views/Field.js b/packages/fields/src/types/CalendarDay/views/Field.js index 5126cf60ce7..4c4225d97c2 100644 --- a/packages/fields/src/types/CalendarDay/views/Field.js +++ b/packages/fields/src/types/CalendarDay/views/Field.js @@ -8,14 +8,20 @@ import 'react-day-picker/dist/style.css'; import { DayPicker } from 'react-day-picker'; import { parseISO, compareAsc, formatISO, isValid } from 'date-fns'; -const CalendarDayField = ({ autoFocus, field, value, errors, onChange, isDisabled }) => { - const htmlID = `ks-daypicker-${field.path}`; - const handleDayClick = day => onChange(formatISO(day, { representation: 'date' })); +const CalendarDayField = ({ + autoFocus, + field: { format, path, label, isRequired, adminDoc }, + value, + errors, + onChange, + isDisabled, +}) => { + const htmlID = `ks-daypicker-${path}`; return ( - - + + { - // There is a strange bug where after interacting with the day picker - // and then pressing enter on the input the value is changed to the start - // of the month. I think this is bug with the day picker. - // The following is a work-around: - if (e.key === 'Enter') { - e.preventDefault(); - } - }} - onChange={e => { - // Tiny bit of date format normalisation for convenience - const normalisedValue = e.target.value.replace('/', '-').replace('\\', '-'); - const parsedValue = parseISO(normalisedValue); - if (normalisedValue.length === 10 && isValid(parsedValue)) { - handleDayClick(parsedValue); - } else { - onChange(normalisedValue); - } - }} + date={value} + format={format} + onChange={onChange} disabled={isDisabled} css={{ color: isValid(parseISO(value)) ? undefined : 'darkred' }} value={value} diff --git a/packages/fields/src/types/DateTime/README.md b/packages/fields/src/types/DateTime/README.md index 974c8617bc5..de9a9e448ca 100644 --- a/packages/fields/src/types/DateTime/README.md +++ b/packages/fields/src/types/DateTime/README.md @@ -17,7 +17,7 @@ keystone.createList('User', { fields: { lastOnline: { type: DateTime, - format: 'MM/DD/YYYY h:mm A', + format: 'MM/dd/yyyy h:mm a', yearRangeFrom: 1901, yearRangeTo: 2018, yearPickerType: 'auto', @@ -39,7 +39,7 @@ keystone.createList('User', { #### `format` -Defines the format of the string that the component generates. For example, `MM/DD/YYYY h:mm A`. +Defines the format of the string that the component generates. For example, `MM/dd/yyyy h:mm A`. #### `yearRangeFrom` diff --git a/packages/list-plugins/README.md b/packages/list-plugins/README.md index 183a224cb20..5d9903bb11d 100644 --- a/packages/list-plugins/README.md +++ b/packages/list-plugins/README.md @@ -27,7 +27,7 @@ keystone.createList('ListWithPlugin', { | ---------------- | -------- | ------------------- | ----------------------------------------- | | `createdAtField` | `String` | `createdAt` | Name of the `createdAt` field. | | `updatedAtField` | `String` | `updatedAt` | Name of the `createdAt` field. | -| `format` | `String` | `MM/DD/YYYY h:mm A` | Format of the generated `DateTime` field. | +| `format` | `String` | `MM/dd/yyyy h:mm a` | Format of the generated `DateTime` field. | | `access` | `Object` | See: access | Change default access controls. | ### `access` diff --git a/packages/list-plugins/atTracking.md b/packages/list-plugins/atTracking.md index 8ed11240679..637b0d4d6d3 100644 --- a/packages/list-plugins/atTracking.md +++ b/packages/list-plugins/atTracking.md @@ -27,7 +27,7 @@ keystone.createList('ListWithPlugin', { | ---------------- | -------- | ------------------- | ----------------------------------------- | | `createdAtField` | `String` | `createdAt` | Name of the `createdAt` field. | | `updatedAtField` | `String` | `updatedAt` | Name of the `updatedAt` field. | -| `format` | `String` | `MM/DD/YYYY h:mm A` | Format of the generated `DateTime` field. | +| `format` | `String` | `MM/dd/yyyy h:mm a` | Format of the generated `DateTime` field. | | `access` | `Object` | See: access | Change default access controls. | ### `access`