diff --git a/app/components/gh-post-settings-menu.js b/app/components/gh-post-settings-menu.js index 072f0a0ec..bdf10ffd7 100644 --- a/app/components/gh-post-settings-menu.js +++ b/app/components/gh-post-settings-menu.js @@ -30,6 +30,7 @@ export default Component.extend(SettingsMenuMixin, { model: null, slugValue: boundOneWay('model.slug'), + customExcerptScratch: alias('model.customExcerptScratch'), metaTitleScratch: alias('model.metaTitleScratch'), metaDescriptionScratch: alias('model.metaDescriptionScratch'), @@ -245,6 +246,21 @@ export default Component.extend(SettingsMenuMixin, { } }, + setCustomExcerpt(excerpt) { + let model = this.get('model'); + let currentExcerpt = model.get('customExcerpt'); + + if (excerpt === currentExcerpt) { + return; + } + + model.set('customExcerpt', excerpt); + + return model.validate({property: 'customExcerpt'}).then(() => { + return model.save(); + }); + }, + setMetaTitle(metaTitle) { // Grab the model and current stored meta title let model = this.get('model'); diff --git a/app/mixins/editor-base-controller.js b/app/mixins/editor-base-controller.js index 0511c0639..8e6699bd9 100644 --- a/app/mixins/editor-base-controller.js +++ b/app/mixins/editor-base-controller.js @@ -158,6 +158,7 @@ export default Mixin.create({ } this.set('model.title', this.get('model.titleScratch')); + this.set('model.customExcerpt', this.get('model.customExcerptScratch')); this.set('model.metaTitle', this.get('model.metaTitleScratch')); this.set('model.metaDescription', this.get('model.metaDescriptionScratch')); diff --git a/app/models/post.js b/app/models/post.js index 34c380363..5ac0b1027 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -73,32 +73,33 @@ export default Model.extend(Comparable, ValidationEngine, { validationType: 'post', - uuid: attr('string'), - title: attr('string', {defaultValue: ''}), - slug: attr('string'), - mobiledoc: attr('json-string', {defaultValue: () => BLANK_DOC}), - html: attr('string'), - featureImage: attr('string'), - featured: attr('boolean', {defaultValue: false}), - page: attr('boolean', {defaultValue: false}), - plaintext: attr('string'), - status: attr('string', {defaultValue: 'draft'}), - locale: attr('string'), - metaTitle: attr('string'), - metaDescription: attr('string'), author: belongsTo('user', {async: true}), authorId: attr('string'), - updatedAtUTC: attr('moment-utc'), - updatedBy: attr(), - publishedAtUTC: attr('moment-utc'), - publishedBy: belongsTo('user', {async: true}), createdAtUTC: attr('moment-utc'), createdBy: attr(), + customExcerpt: attr(), + featured: attr('boolean', {defaultValue: false}), + featureImage: attr('string'), + html: attr('string'), + locale: attr('string'), + metaDescription: attr('string'), + metaTitle: attr('string'), + mobiledoc: attr('json-string', {defaultValue: () => BLANK_DOC}), + page: attr('boolean', {defaultValue: false}), + plaintext: attr('string'), + publishedAtUTC: attr('moment-utc'), + publishedBy: belongsTo('user', {async: true}), + slug: attr('string'), + status: attr('string', {defaultValue: 'draft'}), tags: hasMany('tag', { embedded: 'always', async: false }), + title: attr('string', {defaultValue: ''}), + updatedAtUTC: attr('moment-utc'), + updatedBy: attr(), url: attr('string'), + uuid: attr('string'), scratch: null, titleScratch: null, @@ -114,6 +115,7 @@ export default Model.extend(Comparable, ValidationEngine, { publishedAtBlogDate: '', publishedAtBlogTime: '', + customExcerptScratch: boundOneWay('customExcerpt'), metaTitleScratch: boundOneWay('metaTitle'), metaDescriptionScratch: boundOneWay('metaDescription'), diff --git a/app/styles/components/settings-menu.css b/app/styles/components/settings-menu.css index 703ac488c..b28333b2b 100644 --- a/app/styles/components/settings-menu.css +++ b/app/styles/components/settings-menu.css @@ -179,6 +179,15 @@ stroke: color(var(--red) lightness(-10%)); } +.settings-menu-content .selectize-input { + padding: 7px 12px; +} + +.post-setting-custom-excerpt { + font-size: 1.5rem; + line-height: 1.35em; +} + /* Background /* ---------------------------------------------------------- */ diff --git a/app/templates/components/gh-post-settings-menu.hbs b/app/templates/components/gh-post-settings-menu.hbs index 44d1414dd..9f97549be 100644 --- a/app/templates/components/gh-post-settings-menu.hbs +++ b/app/templates/components/gh-post-settings-menu.hbs @@ -73,6 +73,12 @@ plugins="remove_button, drag_drop"}} + {{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="customExcerpt"}} + + {{gh-textarea customExcerptScratch class="post-setting-custom-excerpt" id="custom-excerpt" name="post-setting-custom-excerpt" focusOut=(action "setCustomExcerpt" customExcerptScratch) stopEnterKeyDownPropagation="true" update=(action (mut customExcerptScratch)) data-test-field="custom-excerpt"}} + {{gh-error-message errors=model.errors property="customExcerpt" data-test-error="custom-excerpt"}} + {{/gh-form-group}} + {{#unless session.user.isAuthor}}
diff --git a/app/validators/post.js b/app/validators/post.js index 60be7de63..b947aee19 100644 --- a/app/validators/post.js +++ b/app/validators/post.js @@ -3,7 +3,7 @@ import moment from 'moment'; import {isEmpty, isPresent} from 'ember-utils'; export default BaseValidator.create({ - properties: ['title', 'metaTitle', 'metaDescription', 'publishedAtBlogTime', 'publishedAtBlogDate'], + properties: ['title', 'customExcerpt', 'metaTitle', 'metaDescription', 'publishedAtBlogTime', 'publishedAtBlogDate'], title(model) { let title = model.get('title'); @@ -19,6 +19,15 @@ export default BaseValidator.create({ } }, + 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(); + } + }, + metaTitle(model) { let metaTitle = model.get('metaTitle'); diff --git a/tests/acceptance/editor-test.js b/tests/acceptance/editor-test.js index dc50ed7ca..d4bc01426 100644 --- a/tests/acceptance/editor-test.js +++ b/tests/acceptance/editor-test.js @@ -548,5 +548,37 @@ describe('Acceptance: Editor', function() { 'url after autosave' ).to.equal('/editor/1'); }); + + it('saves post settings fields', async function () { + let post = server.create('post'); + + await visit(`/editor/${post.id}`); + + // TODO: implement tests for other fields + + // changing custom excerpt auto-saves + await click(testSelector('psm-trigger')); + await fillIn(testSelector('field', 'custom-excerpt'), 'Testing excerpt'); + await triggerEvent(testSelector('field', 'custom-excerpt'), 'blur'); + + expect( + server.db.posts[0].custom_excerpt, + 'saved excerpt' + ).to.equal('Testing excerpt'); + + // excerpt has validation + await fillIn(testSelector('field', 'custom-excerpt'), Array(302).join('a')); + await triggerEvent(testSelector('field', 'custom-excerpt'), 'blur'); + + expect( + find(testSelector('error', 'custom-excerpt')).text().trim(), + 'excerpt too long error' + ).to.match(/cannot be longer than 300/); + + expect( + server.db.posts[0].custom_excerpt, + 'saved excerpt after validation error' + ).to.equal('Testing excerpt'); + }); }); });