diff --git a/app/components/gh-post-settings-menu.js b/app/components/gh-post-settings-menu.js index cfc0eaa818..6967a67d6b 100644 --- a/app/components/gh-post-settings-menu.js +++ b/app/components/gh-post-settings-menu.js @@ -20,6 +20,7 @@ export default Component.extend(SettingsMenuMixin, { session: service(), settings: service(), ui: service(), + rcServices: service('rc-services'), post: null, @@ -36,14 +37,24 @@ export default Component.extend(SettingsMenuMixin, { ogTitleScratch: alias('post.ogTitleScratch'), twitterDescriptionScratch: alias('post.twitterDescriptionScratch'), twitterTitleScratch: alias('post.twitterTitleScratch'), + rcDescriptionScratch: alias('post.rcDescriptionScratch'), + rcTitleScratch: alias('post.rcTitleScratch'), + roomNameScratch: alias('post.roomNameScratch'), slugValue: boundOneWay('post.slug'), - + allowAnnouncements: boundOneWay('settings.isAnnounced'), + allowAuthorRooms: boundOneWay('settings.isAuthorsRooms'), + announce: boundOneWay('allowAnnouncements'), + + roomName: or('roomNameScratch', 'settings.roomName'), facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription'), facebookImage: or('post.ogImage', 'post.featureImage'), facebookTitle: or('ogTitleScratch', 'seoTitle'), twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription'), twitterImage: or('post.twitterImage', 'post.featureImage'), twitterTitle: or('twitterTitleScratch', 'seoTitle'), + rcDescription: or('rcDescriptionScratch', 'customExcerptScratch', 'seoDescription'), + rcImage: or('post.rcImage', 'post.featureImage'), + rcTitle: or('rcTitleScratch', 'seoTitle'), seoTitle: computed('metaTitleScratch', 'post.titleScratch', function () { return this.metaTitleScratch || this.post.titleScratch || '(Untitled)'; @@ -116,6 +127,11 @@ export default Component.extend(SettingsMenuMixin, { post.validate({attribute: 'publishedAtBlog'}); } + if (errors.has('roomName')) { + post.set('roomNameScratch', post.get('roomName')); + post.validate({attribute: 'roomName'}); + } + // remove throbbers this.set('_showThrobbers', false); } @@ -163,6 +179,50 @@ export default Component.extend(SettingsMenuMixin, { }); }, + /** + * triggered by user manually changing announce-setting + */ + toggleAnnounce() { + let post = this.post; + let announce = this.announce; + this.toggleProperty('announce'); + post.set('announce', !announce); + post.set('announceChanged', true); + }, + + /** + * triggered by user manually changing room-name + */ + validateRoom(newRoom) { + let oldRoom = this.roomName; + let post = this.post; + let errMessage = 'Room does not exist'; + + // reset errors and validation + post.get('errors').remove('roomName'); + + if (!newRoom) { + newRoom = oldRoom; + } + this.rcServices.getRoom(newRoom) + .then((room) => { + const existingRCRoom = room.data[0].exist && room.data[0].roomname === newRoom; + + if (!existingRCRoom) { + throw errMessage; + } + post.set('roomName', newRoom); + post.set('roomId', room.data[0].rid); + }) + .catch((e) => { + if (e === errMessage){ + post.get('errors').add('roomName', errMessage); + return; + } + throw e; + }); + }, + /** * triggered by user manually changing slug */ @@ -402,6 +462,52 @@ export default Component.extend(SettingsMenuMixin, { }); }, + setRcTitle(rcTitle) { + // Grab the post and current stored twitter title + let post = this.post; + let currentTitle = post.get('rcTitle'); + + // If the title entered matches the stored twitter title, do nothing + if (currentTitle === rcTitle) { + return; + } + + // If the title entered is different, set it as the new twitter title + post.set('rcTitle', rcTitle); + + // Make sure the twitter title is valid and if so, save it into the post + return post.validate({property: 'rcTitle'}).then(() => { + if (post.get('isNew')) { + return; + } + + return this.savePost.perform(); + }); + }, + + setRcDescription(rcDescription) { + // Grab the post and current stored twitter description + let post = this.post; + let currentDescription = post.get('rcDescription'); + + // If the description entered matches the stored twitter description, do nothing + if (currentDescription === rcDescription) { + return; + } + + // If the description entered is different, set it as the new twitter description + post.set('rcDescription', rcDescription); + + // Make sure the twitter description is valid and if so, save it into the post + return post.validate({property: 'rcDescription'}).then(() => { + if (post.get('isNew')) { + return; + } + + return this.savePost.perform(); + }); + }, + setCoverImage(image) { this.set('post.featureImage', image); @@ -480,6 +586,32 @@ export default Component.extend(SettingsMenuMixin, { }); }, + setRcImage(image) { + this.set('post.rcImage', image); + + if (this.get('post.isNew')) { + return; + } + + this.savePost.perform().catch((error) => { + this.showError(error); + this.post.rollbackAttributes(); + }); + }, + + clearRcImage() { + this.set('post.rcImage', ''); + + if (this.get('post.isNew')) { + return; + } + + this.savePost.perform().catch((error) => { + this.showError(error); + this.post.rollbackAttributes(); + }); + }, + changeAuthors(newAuthors) { let post = this.post; diff --git a/app/components/modal-invite-new-user.js b/app/components/modal-invite-new-user.js index ff811bee36..14bb87cc82 100644 --- a/app/components/modal-invite-new-user.js +++ b/app/components/modal-invite-new-user.js @@ -10,6 +10,7 @@ const {Promise} = RSVP; export default ModalComponent.extend(ValidationEngine, { notifications: service(), store: service(), + rcService: service('rc-services'), classNames: 'modal-content invite-new-user', @@ -48,28 +49,28 @@ export default ModalComponent.extend(ValidationEngine, { }, validate() { - let email = this.email; + let username = this.username; - // TODO: either the validator should check the email's existence or + // TODO: either the validator should check the username's existence or // the API should return an appropriate error when attempting to save return new Promise((resolve, reject) => this._super().then(() => RSVP.hash({ - users: this.store.findAll('user', {reload: true}), - invites: this.store.findAll('invite', {reload: true}) + rc_users: this.rcService.getUser(username), + users: this.store.findAll('user', {reload: true}) }).then((data) => { - let existingUser = data.users.findBy('email', email); - let existingInvite = data.invites.findBy('email', email); + let existingRCUser = data.rc_users.data[0].exist; + let existingUser = data.users.findBy('rc_username', username); - if (existingUser || existingInvite) { - this.errors.clear('email'); + if (existingUser || !existingRCUser) { + this.errors.clear('username'); if (existingUser) { - this.errors.add('email', 'A user with that email address already exists.'); + this.errors.add('username', 'A user with that username already exists.'); } else { - this.errors.add('email', 'A user with that email address was already invited.'); + this.errors.add('username', 'Username doesnot exist'); } // TODO: this shouldn't be needed, ValidationEngine doesn't mark // properties as validated when validating an entire object - this.hasValidated.addObject('email'); + this.hasValidated.addObject('username'); reject(); } else { resolve(); @@ -77,7 +78,7 @@ export default ModalComponent.extend(ValidationEngine, { }), () => { // TODO: this shouldn't be needed, ValidationEngine doesn't mark // properties as validated when validating an entire object - this.hasValidated.addObject('email'); + this.hasValidated.addObject('username'); reject(); })); }, @@ -95,35 +96,27 @@ export default ModalComponent.extend(ValidationEngine, { }), sendInvitation: task(function* () { - let email = this.email; + let username = this.username; let role = this.role; let notifications = this.notifications; - let notificationText = `Invitation sent! (${email})`; - let invite; try { yield this.validate(); - invite = this.store.createRecord('invite', { - email, - role - }); + const user = yield this.rcService.addUser(username, role); - yield invite.save(); - - // If sending the invitation email fails, the API will still return a status of 201 - // but the invite's status in the response object will be 'invited-pending'. - if (invite.get('status') === 'pending') { - notifications.showAlert('Invitation email was not sent. Please try resending.', {type: 'error', key: 'invite.send.failed'}); + // Check and notify if user is added + if (user.invitation && user.invitation[0].message === 'User Added') { + notifications.showNotification(user.invitation[0].message, {key: 'invite.send.success'}); } else { - notifications.showNotification(notificationText, {key: 'invite.send.success'}); + notifications.showAlert('Unable to add user', {type: 'error', key: 'invite.send.failed'}); } this.send('closeModal'); } catch (error) { // validation will reject and cause this to be called with no error if (error) { - invite.deleteRecord(); + // invite.deleteRecord(); notifications.showAPIError(error, {key: 'invite.send'}); this.send('closeModal'); } diff --git a/app/controllers/settings/general.js b/app/controllers/settings/general.js index c9270671bd..4b0e25eeb8 100644 --- a/app/controllers/settings/general.js +++ b/app/controllers/settings/general.js @@ -21,6 +21,7 @@ export default Controller.extend({ session: service(), settings: service(), ui: service(), + rcServices: service('rc_services'), availableTimezones: null, iconExtensions: null, @@ -28,12 +29,14 @@ export default Controller.extend({ imageExtensions: IMAGE_EXTENSIONS, imageMimeTypes: IMAGE_MIME_TYPES, + _scratchRoom: null, _scratchFacebook: null, _scratchTwitter: null, init() { this._super(...arguments); this.iconExtensions = ICON_EXTENSIONS; + this._scratchRoom = this.get('settings.roomName'); }, privateRSSUrl: computed('config.blogUrl', 'settings.publicHash', function () { @@ -102,6 +105,35 @@ export default Controller.extend({ } }, + toggleIsAnnounced(isAnnounced) { + let settings = this.settings; + + settings.set('isAnnounced', isAnnounced); + + // Change isAuthorsRooms to false when isAnnounce is disabled + if (!isAnnounced) { + settings.set('isAuthorsRooms', false); + } + }, + + toggleIsAuthorsRooms(isAuthorsRooms) { + let settings = this.settings; + + settings.set('isAuthorsRooms', isAuthorsRooms); + }, + + toggleIsComments(isComments) { + let settings = this.settings; + + settings.set('isComments', isComments); + }, + + toggleInviteOnly(inviteOnly) { + let settings = this.settings; + + settings.set('inviteOnly', inviteOnly); + }, + toggleLeaveSettingsModal(transition) { let leaveTransition = this.leaveSettingsTransition; @@ -141,6 +173,42 @@ export default Controller.extend({ return transition.retry(); }, + validateRoom() { + let newRoom = this._scratchRoom; + let oldRoom = this.get('settings.roomName'); + let errMessage = 'Room does not exist'; + + // reset errors and validation + this.get('settings.errors').remove('room'); + this.get('settings.hasValidated').removeObject('room'); + + if (!newRoom) { + newRoom = oldRoom; + } + this.rcServices.getRoom(newRoom) + .then((room) => { + const existingRCRoom = room.data[0].exist && room.data[0].roomname === newRoom; + + if (!existingRCRoom) { + throw errMessage; + } + run.schedule('afterRender', this, function () { + this.set('settings.roomName', newRoom); + this.set('settings.roomId', room.data[0].rid); + }); + }) + .catch((e) => { + if (e === errMessage){ + this.get('settings.errors').add('room', errMessage); + return; + } + throw e; + }) + .finally(() => { + this.get('settings.hasValidated').pushObject('room'); + }); + }, + validateFacebookUrl() { let newUrl = this._scratchFacebook; let oldUrl = this.get('settings.facebook'); diff --git a/app/models/post.js b/app/models/post.js index 72273826d9..bd545caee7 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -71,6 +71,8 @@ export default Model.extend(Comparable, ValidationEngine, { ghostPaths: service(), clock: service(), settings: service(), + notifications: service(), + rcServices: service('rc-services'), displayName: 'post', validationType: 'post', @@ -79,6 +81,7 @@ export default Model.extend(Comparable, ValidationEngine, { excerpt: attr('string'), customExcerpt: attr('string'), featured: attr('boolean', {defaultValue: false}), + announce: attr('boolean', {defaultValue: false}), featureImage: attr('string'), canonicalUrl: attr('string'), codeinjectionFoot: attr('string', {defaultValue: ''}), @@ -90,6 +93,9 @@ export default Model.extend(Comparable, ValidationEngine, { twitterImage: attr('string'), twitterTitle: attr('string'), twitterDescription: attr('string'), + rcImage: attr('string'), + rcTitle: attr('string'), + rcDescription: attr('string'), html: attr('string'), locale: attr('string'), metaDescription: attr('string'), @@ -104,6 +110,8 @@ export default Model.extend(Comparable, ValidationEngine, { updatedBy: attr('number'), url: attr('string'), uuid: attr('string'), + roomName: attr('string'), + roomId: attr('string'), authors: hasMany('user', { embedded: 'always', @@ -120,6 +128,7 @@ export default Model.extend(Comparable, ValidationEngine, { return this.get('authors.firstObject'); }), + announceChanged: false, scratch: null, titleScratch: null, @@ -134,6 +143,7 @@ export default Model.extend(Comparable, ValidationEngine, { publishedAtBlogDate: '', publishedAtBlogTime: '', + roomNameScratch: boundOneWay('roomName'), canonicalUrlScratch: boundOneWay('canonicalUrl'), customExcerptScratch: boundOneWay('customExcerpt'), codeinjectionFootScratch: boundOneWay('codeinjectionFoot'), @@ -144,6 +154,8 @@ export default Model.extend(Comparable, ValidationEngine, { ogTitleScratch: boundOneWay('ogTitle'), twitterDescriptionScratch: boundOneWay('twitterDescription'), twitterTitleScratch: boundOneWay('twitterTitle'), + rcDescriptionScratch: boundOneWay('rcDescription'), + rcTitleScratch: boundOneWay('rcTitle'), isPublished: equal('status', 'published'), isDraft: equal('status', 'draft'), @@ -312,9 +324,22 @@ export default Model.extend(Comparable, ValidationEngine, { // // the publishedAtBlog{Date/Time} strings are set separately so they can be // validated, grab that time if it exists and set the publishedAtUTC + // + // Add announcing room and announce. beforeSave() { let publishedAtBlogTZ = this.publishedAtBlogTZ; let publishedAtUTC = publishedAtBlogTZ ? publishedAtBlogTZ.utc() : null; this.set('publishedAtUTC', publishedAtUTC); + + let roomName = this.roomName; + const roomId = roomName ? this.roomId : this.get('settings.roomId'); + roomName = roomName ? roomName : this.get('settings.roomName'); + this.set('roomName', roomName); + this.set('roomId', roomId); + + if (!this.announceChanged) { + const announce = this.get('settings.isAnnounced'); + this.set('announce', announce); + } } }); diff --git a/app/models/setting.js b/app/models/setting.js index 538d5a19b7..bd4d8bd0b6 100644 --- a/app/models/setting.js +++ b/app/models/setting.js @@ -17,9 +17,16 @@ export default Model.extend(ValidationEngine, { codeinjectionFoot: attr('string'), facebook: attr('facebook-url-user'), twitter: attr('twitter-url-user'), + roomName: attr('string'), + roomId: attr('string'), labs: attr('string'), navigation: attr('navigation-settings'), + serverUrl: attr('string'), + isAnnounced: attr('boolean'), isPrivate: attr('boolean'), + isAuthorsRooms: attr('boolean'), + isComments: attr('boolean'), + inviteOnly: attr('boolean'), publicHash: attr('string'), password: attr('string'), slack: attr('slack-settings'), diff --git a/app/models/user.js b/app/models/user.js index 5865a55310..720b1ab48a 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -12,6 +12,8 @@ export default Model.extend(ValidationEngine, { validationType: 'user', name: attr('string'), + rc_id: attr('string'), + rc_username: attr('string'), slug: attr('string'), email: attr('string'), profileImage: attr('string'), diff --git a/app/services/rc-services.js b/app/services/rc-services.js new file mode 100644 index 0000000000..1527e2c07e --- /dev/null +++ b/app/services/rc-services.js @@ -0,0 +1,35 @@ +import Service, {inject as service} from '@ember/service'; + +export default Service.extend({ + ajax: service(), + ghostPaths: service(), + + init() { + this._super(...arguments); + }, + + getRoom(room) { + const query = {rname: room}; + let url = this.get('ghostPaths.url').api('rcapi'); + return this.ajax.request(url, {data: query}); + }, + + getUser(username) { + const query = {uname: username}; + let url = this.get('ghostPaths.url').api('rcapi'); + return this.ajax.request(url, {data: query}); + }, + + addUser(username, role) { + let authUrl = this.get('ghostPaths.url').api('authentication', 'adduser'); + return this.ajax.post(authUrl, { + dataType: 'json', + data: { + user: [{ + rc_username: username, + role: role + }] + } + }); + } +}); diff --git a/app/services/settings.js b/app/services/settings.js index 3ecd8c0e9c..47bdcba59c 100644 --- a/app/services/settings.js +++ b/app/services/settings.js @@ -27,7 +27,7 @@ export default Service.extend(_ProxyMixin, ValidationEngine, { _loadSettings() { if (!this._loadingPromise) { this._loadingPromise = this.store - .queryRecord('setting', {type: 'blog,theme,private,members'}) + .queryRecord('setting', {type: 'blog,rc_settings,theme,private,members'}) .then((settings) => { this._loadingPromise = null; return settings; diff --git a/app/styles/layouts/editor.css b/app/styles/layouts/editor.css index dc8ad7a0df..a4c4a8a5b2 100644 --- a/app/styles/layouts/editor.css +++ b/app/styles/layouts/editor.css @@ -269,6 +269,64 @@ } +/* Announcement Card Preview */ +.gh-rc-preview { + overflow: hidden; + border-width: 1px; + border-style: solid; + border-color: #e1e8ed; + color: #292f33; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + font-size: 1.4rem; + line-height: 1.3em; + background: #fff; + border-radius: 0.42857em; + + -webkit-font-smoothing: antialiased; +} + +.gh-rc-preview-image { + width: 100%; + height: 160px; + background-size: cover; + background-position: center center; +} + +.gh-rc-preview-content { + padding: 12px 14px; +} + +.gh-rc-preview-title { + max-height: 1.3em; + overflow: hidden; + margin: 0 0 0.15em; + font-weight: bold; + text-overflow: ellipsis; + white-space: nowrap; +} + +.gh-rc-preview-description { + overflow: hidden; + margin-top: 0.32333em; +} + +.gh-rc-preview-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0.35em; +} + +.gh-rc-preview-footer-left { + max-height: 1.3em; + overflow: hidden; + color: #8899a6; + text-transform: lowercase; + text-overflow: ellipsis; + white-space: nowrap; +} + + /* NEW editor /* ---------------------------------------------------------- */ diff --git a/app/templates/components/gh-post-settings-menu.hbs b/app/templates/components/gh-post-settings-menu.hbs index 9e9c09c65c..816e1c4a9e 100644 --- a/app/templates/components/gh-post-settings-menu.hbs +++ b/app/templates/components/gh-post-settings-menu.hbs @@ -44,6 +44,41 @@ {{gh-url-preview slug=slugValue tagName="p" classNames="description"}} + {{#if (and allowAnnouncements (eq post.displayName 'post'))}} +
+ + {{gh-text-input + class="post-setting-room-name" + id="room-name" + name="post-setting-room-name" + readonly=(not allowAuthorRooms) + placeholder=(truncate roomName 40) + value=(readonly roomName) + focus-out=(action "validateRoom" roomNameScratch) + input=(action (mut roomNameScratch) value="target.value") + stopEnterKeyDownPropagation=true + data-test-field="room-name"}} + {{gh-error-message errors=post.errors property="roomName" data-test-error="room-name"}} + {{#if allowAuthorRooms}} +

Set Room Name to Announce

+ {{else}} +

Cannot Change the Room Name

+ {{/if}} +
+ +
+
+ {{/if}}
{{#if (or post.isDraft post.isPublished post.pastScheduledTime)}} @@ -102,6 +137,13 @@ Extra content for search engines {{svg-jar "arrow-right"}} + +
{{/if}} + {{#if (eq subview "announcement-data")}} +
+ +

Announcement Card

+
+
+ +
+ +
+ {{gh-image-uploader-with-preview + image=post.rcImage + text="Add Message image" + allowUnsplash=true + update=(action "setRcImage") + remove=(action "clearRcImage") + }} + {{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="rcTitle"}} + + {{gh-text-input + class="post-setting-rc-title" + id="rc-title" + name="post-setting-rc-title" + placeholder=(truncate rcTitle 40) + value=(readonly rcTitleScratch) + input=(action (mut rcTitleScratch) value="target.value") + focus-out=(action "setRcTitle" rcTitleScratch) + stopEnterKeyDownPropagation=true + data-test-field="announcement-title"}} + {{gh-error-message errors=post.errors property="rcTitle" data-test-error="announcement-title"}} + {{/gh-form-group}} + + {{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="rcDescription"}} + + {{gh-textarea + class="post-setting-rc-description" + id="rc-description" + name="post-setting-rc-description" + placeholder=(truncate rcDescription 155) + stopEnterKeyDownPropagation="true" + value=(readonly rcDescriptionScratch) + input=(action (mut rcDescriptionScratch) value="target.value") + focus-out=(action "setRcDescription" rcDescriptionScratch) + data-test-field="announcement-description"}} + {{gh-error-message errors=post.errors property="rcDescription" data-test-error="rc-description"}} + {{/gh-form-group}} + +
+ +
+ {{#if rcImage}} +
+ {{/if}} +
+
{{rcTitle}}
+
{{truncate rcDescription 155}}
+ +
+
+
+ +
+
+ {{/if}} + {{#if (eq subview "twitter-data")}}
diff --git a/app/templates/components/modal-invite-new-user.hbs b/app/templates/components/modal-invite-new-user.hbs index 95dfeecfbf..a6bdef2080 100644 --- a/app/templates/components/modal-invite-new-user.hbs +++ b/app/templates/components/modal-invite-new-user.hbs @@ -1,5 +1,5 @@ {{!-- disable mouseDown so it doesn't trigger focus-out validations --}} @@ -8,25 +8,24 @@ +
Rocket-Chat
+
+
+
Rocket.Chat server Url
+
Set the url where rocket chat server is running
+ {{#liquid-if chatServerUrl}} +
+ {{#gh-form-group errors=settings.errors hasValidated=settings.hasValidated property="serverUrl"}} + {{gh-text-input + value=(readonly settings.serverUrl) + input=(action (mut settings.serverUrl) value="target.value") + focus-out=(action "validate" target=settings) + data-test-server-url-input=true + }} + {{gh-error-message errors=settings.errors property="serverUrl"}} +

All the api call will use this url. Verify it before changing.

+ {{/gh-form-group}} +
+ {{/liquid-if}} +
+
+ +
+
+
+
+
Article Announcement
+
+ Allow the announcement of articles when publish. +
+ + {{#if settings.isAnnounced}} +
+ {{#gh-form-group errors=settings.errors hasValidated=settings.hasValidated property="room"}} + {{gh-text-input + value=(readonly settings.roomName) + input=(action (mut _scratchRoom) value="target.value") + focus-out=(action "validateRoom") + data-test-room-input=true + }} + {{gh-error-message errors=settings.errors property="room" data-test-room-error=true}} +

Channel name for announcement

+ {{/gh-form-group}} +
+ {{/if}} +
+
+
+ +
+
+
+
+
+
Authors Room for announcement
+
+ Allow authors to change the room for announcing their articles. +
+
+
+
+ +
+
+
+
+
+
Comments on Articles
+
+ Create a Discussion in RC on Articles when published, and show on footer. +
+
+
+
+ +
+
+
+
+
+
Only Owner, Admin can add users
+
+ If enabled, then only invited users can access. +
+
+
+
+ +
+
+
+
Social accounts
diff --git a/app/templates/setup/three.hbs b/app/templates/setup/three.hbs index 202ab80cbf..721ab07da3 100644 --- a/app/templates/setup/three.hbs +++ b/app/templates/setup/three.hbs @@ -1,5 +1,5 @@
-

Invite staff users

+

Add users

Ghost works best when shared with others. Collaborate, get feedback on your posts & work together on ideas.

diff --git a/app/templates/setup/two.hbs b/app/templates/setup/two.hbs index 7257ea4067..74dfbd8f4f 100644 --- a/app/templates/setup/two.hbs +++ b/app/templates/setup/two.hbs @@ -93,7 +93,7 @@ {{#if task.isRunning}} {{svg-jar "spinner" class="gh-icon-spinner gh-btn-icon-no-margin"}} {{else}} - Last step: Invite staff users {{svg-jar "arrow-right-small" class="gh-btn-icon-right"}} + Last step: Add users {{svg-jar "arrow-right-small" class="gh-btn-icon-right"}} {{/if}} {{/gh-task-button}} diff --git a/app/templates/staff/index.hbs b/app/templates/staff/index.hbs index 478721ccf9..b45bc571b1 100644 --- a/app/templates/staff/index.hbs +++ b/app/templates/staff/index.hbs @@ -1,6 +1,6 @@
-

Staff users

+

Users

{{!-- Do not show Invite user button to authors --}} {{#unless currentUser.isAuthorOrContributor}}
diff --git a/app/validators/invite-user.js b/app/validators/invite-user.js index e3c6a2deb9..39fdf2fcb3 100644 --- a/app/validators/invite-user.js +++ b/app/validators/invite-user.js @@ -3,16 +3,13 @@ import validator from 'validator'; import {isBlank} from '@ember/utils'; export default BaseValidator.create({ - properties: ['email', 'role'], + properties: ['username', 'role'], + + username(model) { + let username = model.get('username'); - email(model) { - let email = model.get('email'); - - if (isBlank(email)) { - model.get('errors').add('email', 'Please enter an email.'); - this.invalidate(); - } else if (!validator.isEmail(email)) { - model.get('errors').add('email', 'Invalid Email.'); + if (isBlank(username)) { + model.get('errors').add('username', 'Please enter a username.'); this.invalidate(); } }, diff --git a/app/validators/post.js b/app/validators/post.js index 3219855edd..d09cf246d1 100644 --- a/app/validators/post.js +++ b/app/validators/post.js @@ -7,6 +7,7 @@ export default BaseValidator.create({ properties: [ 'title', 'authors', + 'roomName', 'customExcerpt', 'canonicalUrl', 'codeinjectionHead', @@ -17,6 +18,8 @@ export default BaseValidator.create({ 'ogDescription', 'twitterTitle', 'twitterDescription', + 'rcTitle', + 'rcDescription', 'publishedAtBlogTime', 'publishedAtBlogDate' ], @@ -120,6 +123,20 @@ export default BaseValidator.create({ this.invalidate(); } }, + + rcTitle(model) { + if (!validator.isLength(model.rcTitle || '', 0, 300)) { + model.errors.add('rcTitle', 'Message Title cannot be longer than 300 characters.'); + this.invalidate(); + } + }, + + rcDescription(model) { + if (!validator.isLength(model.rcDescription || '', 0, 500)) { + model.errors.add('rcDescription', 'Message 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) {