mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-28 05:14:12 +03:00
Fixed editor cards being accessible when their availability checks failed
no issue Using the slash menu it was possible to insert cards that shouldn't have been accessible based on their availability checks. This was happening because we were only hiding the visibility of the cards in the template rather than completely removing them from the slash command matching logic. - added `{{card-menu-items}}` helper that combines the availability matching and snippet section addition to return a complete array of sections+items that match the current system state and post type - added `@menuItems` argument set to the output of `{{card-menu-items}}` to the two card menu components so they are working against a pre-filtered list of menu items - lets us remove duplication of code that handled pushing snippets section into the menus - removed availability check conditionals from `<KoenigMenuContent>` as the menu items passed in are now pre-filtered
This commit is contained in:
parent
111c5742c9
commit
7e98f1b9f4
@ -67,11 +67,10 @@
|
||||
@editor={{this.editor}}
|
||||
@editorRange={{this.selectedRange}}
|
||||
@snippets={{this.snippets}}
|
||||
@deleteSnippet={{this.deleteSnippet}}
|
||||
@replaceWithCardSection={{action "replaceWithCardSection"}}
|
||||
@replaceWithPost={{action "replaceWithPost"}}
|
||||
@openSelectorComponent={{action "openSelectorComponent"}}
|
||||
@postType={{@postType}}
|
||||
@menuItems={{card-menu-items postType=@postType snippets=this.snippets deleteSnippet=this.deleteSnippet}}
|
||||
/>
|
||||
|
||||
{{!-- slash menu popup --}}
|
||||
@ -79,11 +78,10 @@
|
||||
@editor={{this.editor}}
|
||||
@editorRange={{this.selectedRange}}
|
||||
@snippets={{this.snippets}}
|
||||
@deleteSnippet={{this.deleteSnippet}}
|
||||
@replaceWithCardSection={{action "replaceWithCardSection"}}
|
||||
@replaceWithPost={{action "replaceWithPost"}}
|
||||
@openSelectorComponent={{action "openSelectorComponent"}}
|
||||
@postType={{@postType}}
|
||||
@menuItems={{card-menu-items postType=@postType snippets=this.snippets deleteSnippet=this.deleteSnippet}}
|
||||
/>
|
||||
|
||||
{{!-- all component cards wormholed into the editor canvas --}}
|
||||
|
@ -1,30 +1,26 @@
|
||||
{{#each @itemSections as |section sectionIndex|}}
|
||||
{{#if section.items}}
|
||||
{{#if (or (not section.developerExperiment) (enable-developer-experiments))}}
|
||||
<div class="flex flex-column justify-center flex-shrink-0 {{unless (eq sectionIndex 0) "mt3 bt b--whitegrey"}} mb2 pl4 pr4 pt3 midlightgrey ttu f-supersmall fw5 tracked-3" style="min-width: calc(100% - 3.2rem);">
|
||||
{{section.title}}
|
||||
</div>
|
||||
{{#each section.items as |item|}}
|
||||
{{#if (card-is-available item postType=@postType)}}
|
||||
<div
|
||||
class="{{if (eq item @selectedItem) "kg-cardmenu-card-selected"}} {{kg-style "cardmenu-card"}}"
|
||||
data-kg="cardmenu-card"
|
||||
role="menuitem"
|
||||
title="{{item.label}}"
|
||||
{{on "click" (fn @itemClicked item)}}
|
||||
{{did-update this.scrollIntoView (eq item @selectedItem)}}
|
||||
>
|
||||
<div class="{{kg-style "cardmenu-icon"}} {{item.iconClass}}" aria-hidden="true">{{svg-jar item.icon class="w7 h7"}}</div>
|
||||
<div class="{{kg-style "cardmenu-text"}}">
|
||||
<div class="{{kg-style "cardmenu-label"}}">{{item.label}}</div>
|
||||
<div class="{{kg-style "cardmenu-desc"}}">{{item.desc}}</div>
|
||||
</div>
|
||||
{{#if item.deleteClicked}}
|
||||
<span class="kg-cardmenu-action-icon red" {{on "click" item.deleteClicked}}>{{svg-jar "trash"}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="flex flex-column justify-center flex-shrink-0 {{unless (eq sectionIndex 0) "mt3 bt b--whitegrey"}} mb2 pl4 pr4 pt3 midlightgrey ttu f-supersmall fw5 tracked-3" style="min-width: calc(100% - 3.2rem);">
|
||||
{{section.title}}
|
||||
</div>
|
||||
{{#each section.items as |item|}}
|
||||
<div
|
||||
class="{{if (eq item @selectedItem) "kg-cardmenu-card-selected"}} {{kg-style "cardmenu-card"}}"
|
||||
data-kg="cardmenu-card"
|
||||
role="menuitem"
|
||||
title="{{item.label}}"
|
||||
{{on "click" (fn @itemClicked item)}}
|
||||
{{did-update this.scrollIntoView (eq item @selectedItem)}}
|
||||
>
|
||||
<div class="{{kg-style "cardmenu-icon"}} {{item.iconClass}}" aria-hidden="true">{{svg-jar item.icon class="w7 h7"}}</div>
|
||||
<div class="{{kg-style "cardmenu-text"}}">
|
||||
<div class="{{kg-style "cardmenu-label"}}">{{item.label}}</div>
|
||||
<div class="{{kg-style "cardmenu-desc"}}">{{item.desc}}</div>
|
||||
</div>
|
||||
{{#if item.deleteClicked}}
|
||||
<span class="kg-cardmenu-action-icon red" {{on "click" item.deleteClicked}}>{{svg-jar "trash"}}</span>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
@ -10,7 +10,7 @@
|
||||
<input type="text" placeholder="Search for a card..." class="gh-input koenig-cardmenu-search-input">
|
||||
</div> --}}
|
||||
<KoenigMenuContent
|
||||
@itemSections={{this.itemSections}}
|
||||
@itemSections={{@menuItems}}
|
||||
@itemClicked={{action "itemClicked"}}
|
||||
@postType={{@postType}}
|
||||
/>
|
||||
|
@ -1,8 +1,6 @@
|
||||
import Component from '@ember/component';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import mobiledocParsers from 'mobiledoc-kit/parsers/mobiledoc';
|
||||
import snippetIcon from '../utils/snippet-icon';
|
||||
import {CARD_MENU} from '../options/cards';
|
||||
import {action, computed} from '@ember/object';
|
||||
import {attributeBindings, classNames} from '@ember-decorators/component';
|
||||
import {htmlSafe} from '@ember/template';
|
||||
@ -38,42 +36,6 @@ export default class KoenigPlusMenu extends Component {
|
||||
return htmlSafe(`top: ${this.top}px`);
|
||||
}
|
||||
|
||||
@computed('snippets.[]')
|
||||
get itemSections() {
|
||||
let {snippets} = this;
|
||||
let itemSections = [...CARD_MENU];
|
||||
|
||||
// TODO: move or create util, duplicated with koenig-slash-menu
|
||||
if (snippets?.length) {
|
||||
let snippetsSection = {
|
||||
title: 'Snippets',
|
||||
items: [],
|
||||
rowLength: 1
|
||||
};
|
||||
|
||||
snippets.forEach((snippet) => {
|
||||
let snippetItem = {
|
||||
label: snippet.name,
|
||||
icon: snippetIcon(snippet),
|
||||
type: 'snippet',
|
||||
matches: [snippet.name.toLowerCase()]
|
||||
};
|
||||
if (this.deleteSnippet) {
|
||||
snippetItem.deleteClicked = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.deleteSnippet(snippet);
|
||||
};
|
||||
}
|
||||
snippetsSection.items.push(snippetItem);
|
||||
});
|
||||
|
||||
itemSections.push(snippetsSection);
|
||||
}
|
||||
|
||||
return itemSections;
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import Component from '@glimmer/component';
|
||||
import mobiledocParsers from 'mobiledoc-kit/parsers/mobiledoc';
|
||||
import snippetIcon from '../utils/snippet-icon';
|
||||
import {CARD_MENU} from '../options/cards';
|
||||
import {action} from '@ember/object';
|
||||
import {isArray} from '@ember/array';
|
||||
import {isEmpty} from '@ember/utils';
|
||||
@ -82,40 +80,12 @@ export default class KoenigSlashMenuComponent extends Component {
|
||||
|
||||
@action
|
||||
updateItemSections() {
|
||||
let {snippets} = this.args;
|
||||
let itemSections = [...CARD_MENU];
|
||||
|
||||
if (snippets?.length) {
|
||||
let snippetsSection = {
|
||||
title: 'Snippets',
|
||||
items: [],
|
||||
rowLength: 1
|
||||
};
|
||||
|
||||
snippets.forEach((snippet) => {
|
||||
let snippetItem = {
|
||||
label: snippet.name,
|
||||
icon: snippetIcon(snippet),
|
||||
type: 'snippet',
|
||||
matches: query => snippet.name.toLowerCase().indexOf(query) > -1
|
||||
};
|
||||
if (this.args.deleteSnippet) {
|
||||
snippetItem.deleteClicked = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.args.deleteSnippet(snippet);
|
||||
};
|
||||
}
|
||||
snippetsSection.items.push(snippetItem);
|
||||
});
|
||||
|
||||
itemSections.push(snippetsSection);
|
||||
}
|
||||
let itemSections = [...this.args.menuItems];
|
||||
|
||||
let itemMatcher = createItemMatcher(this.query);
|
||||
|
||||
let matchedItems = itemSections.map((section) => {
|
||||
// show icons where there's a match of the begining of one of the
|
||||
// show icons where there's a match of the beginning of one of the
|
||||
// "item.matches" strings
|
||||
let matches = section.items.filter(itemMatcher);
|
||||
if (matches.length > 0) {
|
||||
|
@ -1,36 +0,0 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import {get} from '@ember/object';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
import {isArray} from '@ember/array';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class CardIsAvailableHelper extends Helper {
|
||||
@service feature;
|
||||
@service settings;
|
||||
|
||||
@inject config;
|
||||
|
||||
compute([card], {postType} = {}) {
|
||||
let cardIsAvailable = true;
|
||||
|
||||
if (typeof card.isAvailable === 'string') {
|
||||
cardIsAvailable = get(this, card.isAvailable);
|
||||
}
|
||||
|
||||
if (isArray(card.isAvailable)) {
|
||||
cardIsAvailable = card.isAvailable.every((key) => {
|
||||
return get(this, key);
|
||||
});
|
||||
}
|
||||
|
||||
if (card.developerExperiment) {
|
||||
cardIsAvailable = cardIsAvailable && this.config.enableDeveloperExperiments;
|
||||
}
|
||||
|
||||
if (postType && card.postType) {
|
||||
cardIsAvailable = cardIsAvailable && card.postType === postType;
|
||||
}
|
||||
|
||||
return cardIsAvailable;
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import snippetIcon from '../utils/snippet-icon';
|
||||
import {CARD_MENU} from '../options/cards';
|
||||
import {get} from '@ember/object';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
import {isArray} from '@ember/array';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class CardMenuItems extends Helper {
|
||||
@service feature;
|
||||
@service settings;
|
||||
|
||||
@inject config;
|
||||
|
||||
compute(positional, {postType, snippets, deleteSnippet} = {}) {
|
||||
let itemSections = JSON.parse(JSON.stringify(CARD_MENU));
|
||||
|
||||
itemSections = itemSections.filter((section) => {
|
||||
return !section.developerExperiment || this.config.enableDeveloperExperiments;
|
||||
});
|
||||
|
||||
itemSections.forEach((section) => {
|
||||
section.items = section.items.filter((card) => {
|
||||
let cardIsAvailable = true;
|
||||
|
||||
if (typeof card.isAvailable === 'string') {
|
||||
cardIsAvailable = get(this, card.isAvailable);
|
||||
}
|
||||
|
||||
if (isArray(card.isAvailable)) {
|
||||
cardIsAvailable = card.isAvailable.every((key) => {
|
||||
return get(this, key);
|
||||
});
|
||||
}
|
||||
|
||||
if (card.developerExperiment) {
|
||||
cardIsAvailable = cardIsAvailable && this.config.enableDeveloperExperiments;
|
||||
}
|
||||
|
||||
if (postType && card.postType) {
|
||||
cardIsAvailable = cardIsAvailable && card.postType === postType;
|
||||
}
|
||||
|
||||
return cardIsAvailable;
|
||||
});
|
||||
});
|
||||
|
||||
if (snippets?.length) {
|
||||
let snippetsSection = {
|
||||
title: 'Snippets',
|
||||
items: [],
|
||||
rowLength: 1
|
||||
};
|
||||
|
||||
snippets.forEach((snippet) => {
|
||||
let snippetItem = {
|
||||
label: snippet.name,
|
||||
icon: snippetIcon(snippet),
|
||||
type: 'snippet',
|
||||
matches: query => snippet.name.toLowerCase().indexOf(query) > -1
|
||||
};
|
||||
if (deleteSnippet) {
|
||||
snippetItem.deleteClicked = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
deleteSnippet(snippet);
|
||||
};
|
||||
}
|
||||
snippetsSection.items.push(snippetItem);
|
||||
});
|
||||
|
||||
itemSections.push(snippetsSection);
|
||||
}
|
||||
|
||||
return itemSections;
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export {default} from 'koenig-editor/helpers/card-is-available';
|
@ -0,0 +1 @@
|
||||
export {default} from 'koenig-editor/helpers/card-menu-items';
|
Loading…
Reference in New Issue
Block a user