Hid snippet management UI from staff users without permissions

no issue

- snippets can only be created and deleted by owners/admins/editors
- added a property in the editor controller to determine if the logged in user has sufficient permissions, then only pass the appropriate save/delete snippet actions to the editor component if the check is passed
- updates koenig menus and toolbars to skip rendering of buttons if the associated action function is not available
This commit is contained in:
Kevin Ansfield 2020-10-27 14:42:36 +00:00
parent 43c3e900da
commit 01a0aaf022
8 changed files with 82 additions and 55 deletions

View File

@ -4,8 +4,8 @@ import boundOneWay from 'ghost-admin/utils/bound-one-way';
import config from 'ghost-admin/config/environment';
import isNumber from 'ghost-admin/utils/isNumber';
import moment from 'moment';
import {action, computed} from '@ember/object';
import {alias, mapBy} from '@ember/object/computed';
import {computed} from '@ember/object';
import {inject as controller} from '@ember/controller';
import {get} from '@ember/object';
import {htmlSafe} from '@ember/string';
@ -147,6 +147,14 @@ export default Controller.extend({
return this._snippets.reject(snippet => snippet.get('isNew'));
}),
canManageSnippets: computed('session.user.{isOwnerOrAdmin,isEditor}', function () {
let {user} = this.session;
if (user.get('isOwnerOrAdmin') || user.get('isEditor')) {
return true;
}
return false;
}),
_autosaveRunning: computed('_autosave.isRunning', '_timedSave.isRunning', function () {
let autosave = this.get('_autosave.isRunning');
let timedsave = this.get('_timedSave.isRunning');
@ -290,38 +298,38 @@ export default Controller.extend({
updateWordCount(counts) {
this.set('wordCount', counts);
},
saveSnippet(snippet) {
let snippetRecord = this.store.createRecord('snippet', snippet);
return snippetRecord.save().then(() => {
this.notifications.closeAlerts('snippet.save');
this.notifications.showNotification(
`Snippet saved as "${snippet.name}"`,
{type: 'success'}
);
return snippetRecord;
}).catch((error) => {
if (!snippetRecord.errors.isEmpty) {
this.notifications.showAlert(
`Snippet save failed: ${snippetRecord.errors.messages.join('. ')}`,
{type: 'error', key: 'snippet.save'}
);
}
snippetRecord.rollbackAttributes();
throw error;
});
},
toggleDeleteSnippetModal(snippet) {
this.set('snippetToDelete', snippet);
},
deleteSnippet(snippet) {
return snippet.destroyRecord();
}
},
saveSnippet: action(function (snippet) {
let snippetRecord = this.store.createRecord('snippet', snippet);
return snippetRecord.save().then(() => {
this.notifications.closeAlerts('snippet.save');
this.notifications.showNotification(
`Snippet saved as "${snippet.name}"`,
{type: 'success'}
);
return snippetRecord;
}).catch((error) => {
if (!snippetRecord.errors.isEmpty) {
this.notifications.showAlert(
`Snippet save failed: ${snippetRecord.errors.messages.join('. ')}`,
{type: 'error', key: 'snippet.save'}
);
}
snippetRecord.rollbackAttributes();
throw error;
});
}),
toggleDeleteSnippetModal: action(function (snippet) {
this.set('snippetToDelete', snippet);
}),
deleteSnippet: action(function (snippet) {
return snippet.destroyRecord();
}),
/* Public tasks ----------------------------------------------------------*/
// separate task for autosave so that it doesn't override a manual save
@ -613,11 +621,11 @@ export default Controller.extend({
let membersResponse = yield this.store.query('member', {limit: 1, filter: 'subscribed:true'});
this.set('memberCount', get(membersResponse, 'meta.pagination.total'));
}
yield this.store.query('snippet', {limit: 'all'});
} catch (error) {
this.set('memberCount', 0);
}
yield this.store.query('snippet', {limit: 'all'});
}).restartable(),
/* Public methods --------------------------------------------------------*/

5
app/helpers/noop.js Normal file
View File

@ -0,0 +1,5 @@
import {helper} from '@ember/component/helper';
export default helper(function noop() {
return () => {};
});

View File

@ -82,8 +82,8 @@
@onEditorCreated={{action "setKoenigEditor"}}
@onWordCountChange={{action "updateWordCount"}}
@snippets={{this.snippets}}
@saveSnippet={{action "saveSnippet"}}
@deleteSnippet={{action "toggleDeleteSnippetModal"}}
@saveSnippet={{if this.canManageSnippets this.saveSnippet}}
@deleteSnippet={{if this.canManageSnippets this.toggleDeleteSnippetModal}}
/>
<div class="absolute flex items-center br3 bg-white {{if editor.headerClass "right-4 bottom-4" "right-6 bottom-6"}}">
@ -139,8 +139,8 @@
<GhFullscreenModal
@modal="delete-snippet"
@model={{this.snippetToDelete}}
@confirm={{action "deleteSnippet"}}
@close={{action "toggleDeleteSnippetModal"}}
@confirm={{this.deleteSnippet}}
@close={{this.toggleDeletePostModal}}
@modifier="action wide"
/>
{{/if}}

View File

@ -12,7 +12,7 @@
@toggleSection={{action "toggleSection"}}
@toggleHeaderSection={{action "toggleHeaderSection"}}
@editLink={{action "editLink"}}
@addSnippet={{this.addSnippet}}
@addSnippet={{this.addSnippetIfPossible}}
/>
{{!-- pop-up link hover toolbar --}}
@ -86,7 +86,7 @@
deselectCard=(action "deselectCard" card)
editCard=(action "editCard" card)
deleteCard=(action "deleteCard" card)
saveAsSnippet=(action "saveCardAsSnippet" card)
saveAsSnippet=this.saveCardAsSnippetIfPossible
moveCursorToPrevSection=(action "moveCursorToPrevSection" card)
moveCursorToNextSection=(action "moveCursorToNextSection" card)
addParagraphAfterCard=(action "addParagraphAfterCard" card)

View File

@ -233,6 +233,14 @@ export default Component.extend({
}, options);
}),
addSnippetIfPossible: computed('saveSnippet', function () {
return this.saveSnippet ? this.addSnippet : undefined;
}),
saveCardAsSnippetIfPossible: computed('saveSnippet', function () {
return this.saveSnippet ? this.saveCardAsSnippet : undefined;
}),
/* lifecycle hooks ------------------------------------------------------ */
init() {
@ -618,12 +626,6 @@ export default Component.extend({
this.deleteCard(card, cursorMovement);
},
saveCardAsSnippet(card) {
let section = this.getSectionFromCard(card);
this.set('snippetRect', card.component.element.getBoundingClientRect());
this.set('snippetRange', section.toRange());
},
moveCursorToPrevSection(card) {
let section = this.getSectionFromCard(card);
@ -681,6 +683,12 @@ export default Component.extend({
this.set('snippetRange', selectedRange);
}),
saveCardAsSnippet: action(function (card) {
let section = this.getSectionFromCard(card);
this.set('snippetRect', card.component.element.getBoundingClientRect());
this.set('snippetRange', section.toRange());
}),
cancelAddSnippet: action(function () {
this.set('snippetRange', null);
this.set('snippetRect', null);

View File

@ -49,17 +49,20 @@ export default Component.extend({
};
snippets.forEach((snippet) => {
snippetsSection.items.push({
let snippetItem = {
label: snippet.name,
icon: snippetIcon(snippet),
type: 'snippet',
matches: [snippet.name.toLowerCase()],
deleteClicked: (event) => {
matches: [snippet.name.toLowerCase()]
};
if (this.deleteSnippet) {
snippetItem.deleteClicked = (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.deleteSnippet(snippet);
}
});
};
}
snippetsSection.items.push(snippetItem);
});
itemSections.push(snippetsSection);

View File

@ -68,17 +68,20 @@ export default class KoenigSlashMenuComponent extends Component {
};
snippets.forEach((snippet) => {
snippetsSection.items.push({
let snippetItem = {
label: snippet.name,
icon: snippetIcon(snippet),
type: 'snippet',
matches: [snippet.name.toLowerCase()],
deleteClicked: (event) => {
matches: [snippet.name.toLowerCase()]
};
if (this.args.deleteSnippet) {
snippetItem.deleteClicked = (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.args.deleteSnippet(snippet);
}
});
};
}
snippetsSection.items.push(snippetItem);
});
itemSections.push(snippetsSection);

View File

@ -67,7 +67,7 @@
</button>
</li>
{{#if (enable-developer-experiments)}}
{{#if (and @addSnippet (enable-developer-experiments))}}
{{#unless this.basicOnly}}
<li class="ma0 lh-solid kg-action-bar-divider bg-darkgrey-l2 h5" role="separator"></li>
<li class="ma0 lh-solid">