Ghost/ghost/admin/app/validators/post.js
Kevin Ansfield 6a3cfc2ca8 Use token input to allow selection of multiple authors in PSM
requires https://github.com/TryGhost/Ghost/pull/9426
- fixed default token component display in {{gh-token-input}}
    - if no `tokenComponent` is passed to `{{gh-token-input}}` then it should default to the ember-drag-drop `draggable-object` component but instead it didn't output anything
    - put `draggable-object` in quotes because `{{component}}` needs a component name rather than an object
    - rename `option` attribute to `content` to match the default `{{draggable-object}}` interface
- add embedded `authors` attr to the Post model
    - ensure authors is populated when starting new post
    - add validation for empty authors list
- swap author dropdown for a token input in PSM
- show all post authors in posts list
- update tests for `authors`
  - always provide through an authors array
  - fix mirage serialisation for paginated responses (embedded records were not being serialised)
- unify tags and author inputs design
  - remove highlight of primary tags
  - highlight internal tags
  - remove unnecessary/redundant title attributes on tags
  - use SVG icon for "remove option" button in token inputs
2018-03-27 18:50:52 +01:00

191 lines
6.7 KiB
JavaScript

import BaseValidator from './base';
import moment from 'moment';
import validator from 'npm:validator';
import {isBlank, isEmpty, isPresent} from '@ember/utils';
export default BaseValidator.create({
properties: [
'title',
'authors',
'customExcerpt',
'codeinjectionHead',
'codeinjectionFoot',
'metaTitle',
'metaDescription',
'ogtitle',
'ogDescription',
'twitterTitle',
'twitterDescription',
'publishedAtBlogTime',
'publishedAtBlogDate'
],
title(model) {
let title = model.get('title');
if (isBlank(title)) {
model.get('errors').add('title', 'You must specify a title for the post.');
this.invalidate();
}
if (!validator.isLength(title || '', 0, 255)) {
model.get('errors').add('title', 'Title cannot be longer than 255 characters.');
this.invalidate();
}
},
authors(model) {
let authors = model.get('authors');
if (isEmpty(authors)) {
model.get('errors').add('authors', 'At least one author is required.');
this.invalidate();
}
},
customExcerpt(model) {
let customExcerpt = model.get('customExcerpt');
if (!validator.isLength(customExcerpt || '', 0, 300)) {
model.get('errors').add('customExcerpt', 'Excerpt cannot be longer than 300 characters.');
this.invalidate();
}
},
codeinjectionFoot(model) {
let codeinjectionFoot = model.get('codeinjectionFoot');
if (!validator.isLength(codeinjectionFoot || '', 0, 65535)) {
model.get('errors').add('codeinjectionFoot', 'Footer code cannot be longer than 65535 characters.');
this.invalidate();
}
},
codeinjectionHead(model) {
let codeinjectionHead = model.get('codeinjectionHead');
if (!validator.isLength(codeinjectionHead || '', 0, 65535)) {
model.get('errors').add('codeinjectionHead', 'Header code cannot be longer than 65535 characters.');
this.invalidate();
}
},
metaTitle(model) {
let metaTitle = model.get('metaTitle');
if (!validator.isLength(metaTitle || '', 0, 300)) {
model.get('errors').add('metaTitle', 'Meta Title cannot be longer than 300 characters.');
this.invalidate();
}
},
metaDescription(model) {
let metaDescription = model.get('metaDescription');
if (!validator.isLength(metaDescription || '', 0, 500)) {
model.get('errors').add('metaDescription', 'Meta Description cannot be longer than 500 characters.');
this.invalidate();
}
},
ogTitle(model) {
let ogTitle = model.get('ogTitle');
if (!validator.isLength(ogTitle || '', 0, 300)) {
model.get('errors').add('ogTitle', 'Facebook Title cannot be longer than 300 characters.');
this.invalidate();
}
},
ogDescription(model) {
let ogDescription = model.get('ogDescription');
if (!validator.isLength(ogDescription || '', 0, 500)) {
model.get('errors').add('ogDescription', 'Facebook Description cannot be longer than 500 characters.');
this.invalidate();
}
},
twitterTitle(model) {
let twitterTitle = model.get('twitterTitle');
if (!validator.isLength(twitterTitle || '', 0, 300)) {
model.get('errors').add('twitterTitle', 'Twitter Title cannot be longer than 300 characters.');
this.invalidate();
}
},
twitterDescription(model) {
let twitterDescription = model.get('twitterDescription');
if (!validator.isLength(twitterDescription || '', 0, 500)) {
model.get('errors').add('twitterDescription', 'Twitter Description cannot be longer than 500 characters.');
this.invalidate();
}
},
// for posts which haven't been published before and where the blog date/time
// is blank we should ignore the validation
_shouldValidatePublishedAtBlog(model) {
let publishedAtUTC = model.get('publishedAtUTC');
let publishedAtBlogDate = model.get('publishedAtBlogDate');
let publishedAtBlogTime = model.get('publishedAtBlogTime');
return isPresent(publishedAtUTC)
|| isPresent(publishedAtBlogDate)
|| isPresent(publishedAtBlogTime);
},
// convenience method as .validate({property: 'x'}) doesn't accept multiple properties
publishedAtBlog(model) {
this.publishedAtBlogTime(model);
this.publishedAtBlogDate(model);
},
publishedAtBlogTime(model) {
let publishedAtBlogTime = model.get('publishedAtBlogTime');
let timeRegex = /^(([0-1]?[0-9])|([2][0-3])):([0-5][0-9])$/;
if (!timeRegex.test(publishedAtBlogTime) && this._shouldValidatePublishedAtBlog(model)) {
model.get('errors').add('publishedAtBlogTime', 'Must be in format: "15:00"');
this.invalidate();
}
},
publishedAtBlogDate(model) {
let publishedAtBlogDate = model.get('publishedAtBlogDate');
let publishedAtBlogTime = model.get('publishedAtBlogTime');
if (!this._shouldValidatePublishedAtBlog(model)) {
return;
}
// we have a time string but no date string
if (isBlank(publishedAtBlogDate) && !isBlank(publishedAtBlogTime)) {
model.get('errors').add('publishedAtBlogDate', 'Can\'t be blank');
return this.invalidate();
}
// don't validate the date if the time format is incorrect
if (isEmpty(model.get('errors').errorsFor('publishedAtBlogTime'))) {
let status = model.get('statusScratch') || model.get('status');
let now = moment();
let publishedAtUTC = model.get('publishedAtUTC');
let publishedAtBlogTZ = model.get('publishedAtBlogTZ');
let matchesExisting = publishedAtUTC && publishedAtBlogTZ.isSame(publishedAtUTC);
let isInFuture = publishedAtBlogTZ.isSameOrAfter(now.add(2, 'minutes'));
// draft/published must be in past
if ((status === 'draft' || status === 'published') && publishedAtBlogTZ.isSameOrAfter(now)) {
model.get('errors').add('publishedAtBlogDate', 'Must be in the past');
this.invalidate();
// scheduled must be at least 2 mins in the future
// ignore if it matches publishedAtUTC as that is likely an update of a scheduled post
} else if (status === 'scheduled' && !matchesExisting && !isInFuture) {
model.get('errors').add('publishedAtBlogDate', 'Must be at least 2 mins in the future');
this.invalidate();
}
}
}
});