mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 11:55:03 +03:00
New tags input, drop selectize & jquery-ui deps (#892)
closes https://github.com/TryGhost/Ghost/issues/6458 - swap `ember-sortable` for `ember-drag-drop` in navigation UI - extract PSM tag input into new `{{gh-psm-tags-input}}` - add new `{{gh-token-input}}` that wraps `ember-power-select` and `ember-drag-drop` to replicate the previous selectize based tags input - enhance `{{gh-psm-tags-input}}` behaviour to highlight selected primary tag and show "primary/internal" in selected tag titles - 🔥 remove `selectize` - 🔥 remove `jquery-ui` - 🔥 remove unused `{{gh-navigation}}` component
This commit is contained in:
parent
9adbcd1fd0
commit
0106a21e3c
@ -1,38 +0,0 @@
|
|||||||
import Component from '@ember/component';
|
|
||||||
import {run} from '@ember/runloop';
|
|
||||||
|
|
||||||
export default Component.extend({
|
|
||||||
tagName: 'section',
|
|
||||||
classNames: 'gh-view',
|
|
||||||
|
|
||||||
didInsertElement() {
|
|
||||||
let navContainer = this.$('.js-gh-blognav');
|
|
||||||
let navElements = '.gh-blognav-item:not(.gh-blognav-item:last-child)';
|
|
||||||
// needed because jqueryui sortable doesn't trigger babel's autoscoping
|
|
||||||
let _this = this;
|
|
||||||
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
navContainer.sortable({
|
|
||||||
handle: '.gh-blognav-grab',
|
|
||||||
items: navElements,
|
|
||||||
|
|
||||||
start(event, ui) {
|
|
||||||
run(() => {
|
|
||||||
ui.item.data('start-index', ui.item.index());
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
update(event, ui) {
|
|
||||||
run(() => {
|
|
||||||
_this.sendAction('moveItem', ui.item.data('start-index'), ui.item.index());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
willDestroyElement() {
|
|
||||||
this._super(...arguments);
|
|
||||||
this.$('.ui-sortable').sortable('destroy');
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,16 +1,14 @@
|
|||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import SortableItem from 'ember-sortable/mixins/sortable-item';
|
|
||||||
import ValidationState from 'ghost-admin/mixins/validation-state';
|
import ValidationState from 'ghost-admin/mixins/validation-state';
|
||||||
import {alias, readOnly} from '@ember/object/computed';
|
import {alias, readOnly} from '@ember/object/computed';
|
||||||
import {computed} from '@ember/object';
|
import {computed} from '@ember/object';
|
||||||
import {run} from '@ember/runloop';
|
import {run} from '@ember/runloop';
|
||||||
|
|
||||||
export default Component.extend(ValidationState, SortableItem, {
|
export default Component.extend(ValidationState, {
|
||||||
classNames: 'gh-blognav-item',
|
classNames: 'gh-blognav-item',
|
||||||
classNameBindings: ['errorClass', 'navItem.isNew::gh-blognav-item--sortable'],
|
classNameBindings: ['errorClass', 'navItem.isNew::gh-blognav-item--sortable'],
|
||||||
|
|
||||||
new: false,
|
new: false,
|
||||||
handle: '.gh-blognav-grab',
|
|
||||||
|
|
||||||
model: alias('navItem'),
|
model: alias('navItem'),
|
||||||
errors: readOnly('navItem.errors'),
|
errors: readOnly('navItem.errors'),
|
||||||
|
@ -5,7 +5,6 @@ import formatMarkdown from 'ghost-admin/utils/format-markdown';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {alias, or} from '@ember/object/computed';
|
import {alias, or} from '@ember/object/computed';
|
||||||
import {computed} from '@ember/object';
|
import {computed} from '@ember/object';
|
||||||
import {guidFor} from '@ember/object/internals';
|
|
||||||
import {htmlSafe} from '@ember/string';
|
import {htmlSafe} from '@ember/string';
|
||||||
import {run} from '@ember/runloop';
|
import {run} from '@ember/runloop';
|
||||||
import {inject as service} from '@ember/service';
|
import {inject as service} from '@ember/service';
|
||||||
@ -138,13 +137,6 @@ export default Component.extend(SettingsMenuMixin, {
|
|||||||
return seoURL;
|
return seoURL;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// live-query of all tags for tag input autocomplete
|
|
||||||
availableTags: computed(function () {
|
|
||||||
return this.get('store').filter('tag', {limit: 'all'}, () => {
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
showError(error) {
|
showError(error) {
|
||||||
// TODO: remove null check once ValidationEngine has been removed
|
// TODO: remove null check once ValidationEngine has been removed
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -530,56 +522,6 @@ export default Component.extend(SettingsMenuMixin, {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addTag(tagName, index) {
|
|
||||||
let currentTags = this.get('model.tags');
|
|
||||||
let currentTagNames = currentTags.map((tag) => {
|
|
||||||
return tag.get('name').toLowerCase();
|
|
||||||
});
|
|
||||||
let availableTagNames,
|
|
||||||
tagToAdd;
|
|
||||||
|
|
||||||
tagName = tagName.trim();
|
|
||||||
|
|
||||||
// abort if tag is already selected
|
|
||||||
if (currentTagNames.includes(tagName.toLowerCase())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.get('availableTags').then((availableTags) => {
|
|
||||||
availableTagNames = availableTags.map((tag) => {
|
|
||||||
return tag.get('name').toLowerCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
// find existing tag or create new
|
|
||||||
if (availableTagNames.includes(tagName.toLowerCase())) {
|
|
||||||
tagToAdd = availableTags.find((tag) => {
|
|
||||||
return tag.get('name').toLowerCase() === tagName.toLowerCase();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
tagToAdd = this.get('store').createRecord('tag', {
|
|
||||||
name: tagName
|
|
||||||
});
|
|
||||||
|
|
||||||
// we need to set a UUID so that selectize has a unique value
|
|
||||||
// it will be ignored when sent to the server
|
|
||||||
tagToAdd.set('uuid', guidFor(tagToAdd));
|
|
||||||
}
|
|
||||||
|
|
||||||
// push tag onto post relationship
|
|
||||||
if (tagToAdd) {
|
|
||||||
this.get('model.tags').insertAt(index, tagToAdd);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
removeTag(tag) {
|
|
||||||
this.get('model.tags').removeObject(tag);
|
|
||||||
|
|
||||||
if (tag.get('isNew')) {
|
|
||||||
tag.destroyRecord();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deletePost() {
|
deletePost() {
|
||||||
if (this.get('deletePost')) {
|
if (this.get('deletePost')) {
|
||||||
this.get('deletePost')();
|
this.get('deletePost')();
|
||||||
|
87
ghost/admin/app/components/gh-psm-tags-input.js
Normal file
87
ghost/admin/app/components/gh-psm-tags-input.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
import {computed} from '@ember/object';
|
||||||
|
import {inject as service} from '@ember/service';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
|
||||||
|
store: service(),
|
||||||
|
|
||||||
|
// public attrs
|
||||||
|
post: null,
|
||||||
|
tagName: '',
|
||||||
|
|
||||||
|
// live-query of all tags for tag input autocomplete
|
||||||
|
availableTags: computed(function () {
|
||||||
|
return this.get('store').filter('tag', {limit: 'all'}, () => true);
|
||||||
|
}),
|
||||||
|
|
||||||
|
availableTagNames: computed('availableTags.@each.name', function () {
|
||||||
|
return this.get('availableTags').map((tag) => {
|
||||||
|
return tag.get('name').toLowerCase();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
matchTags(tagName, term) {
|
||||||
|
return tagName.toLowerCase() === term.trim().toLowerCase();
|
||||||
|
},
|
||||||
|
|
||||||
|
hideCreateOptionOnMatchingTag(term) {
|
||||||
|
return !this.get('availableTagNames').includes(term.toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTags(newTags) {
|
||||||
|
let currentTags = this.get('post.tags');
|
||||||
|
|
||||||
|
// destroy new+unsaved tags that are no longer selected
|
||||||
|
currentTags.forEach(function (tag) {
|
||||||
|
if (!newTags.includes(tag) && tag.get('isNew')) {
|
||||||
|
tag.destroyRecord();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// update tags
|
||||||
|
return this.set('post.tags', newTags);
|
||||||
|
},
|
||||||
|
|
||||||
|
createTag(tagName) {
|
||||||
|
let currentTags = this.get('post.tags');
|
||||||
|
let currentTagNames = currentTags.map((tag) => {
|
||||||
|
return tag.get('name').toLowerCase();
|
||||||
|
});
|
||||||
|
let tagToAdd;
|
||||||
|
|
||||||
|
tagName = tagName.trim();
|
||||||
|
|
||||||
|
// abort if tag is already selected
|
||||||
|
if (currentTagNames.includes(tagName.toLowerCase())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add existing tag or create new one
|
||||||
|
return this._findTagByName(tagName).then((matchedTag) => {
|
||||||
|
tagToAdd = matchedTag;
|
||||||
|
|
||||||
|
// create new tag if no match
|
||||||
|
if (!tagToAdd) {
|
||||||
|
tagToAdd = this.get('store').createRecord('tag', {
|
||||||
|
name: tagName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// push tag onto post relationship
|
||||||
|
return currentTags.pushObject(tagToAdd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
_findTagByName(name) {
|
||||||
|
return this.get('availableTags').then((availableTags) => {
|
||||||
|
return availableTags.find((tag) => {
|
||||||
|
return tag.get('name').toLowerCase() === name.toLowerCase();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -1,125 +0,0 @@
|
|||||||
/* eslint-disable camelcase */
|
|
||||||
import EmberSelectizeComponent from 'ember-cli-selectize/components/ember-selectize';
|
|
||||||
import {computed} from '@ember/object';
|
|
||||||
import {A as emberA, isArray as isEmberArray} from '@ember/array';
|
|
||||||
import {get} from '@ember/object';
|
|
||||||
import {isBlank} from '@ember/utils';
|
|
||||||
import {run} from '@ember/runloop';
|
|
||||||
|
|
||||||
export default EmberSelectizeComponent.extend({
|
|
||||||
|
|
||||||
selectizeOptions: computed(function () {
|
|
||||||
let options = this._super(...arguments);
|
|
||||||
|
|
||||||
options.onChange = run.bind(this, '_onChange');
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event callback that is triggered when user creates a tag
|
|
||||||
* - modified to pass the caret position to the action
|
|
||||||
*/
|
|
||||||
_create(input, callback) {
|
|
||||||
let caret = this._selectize.caretPos;
|
|
||||||
|
|
||||||
// Delete user entered text
|
|
||||||
this._selectize.setTextboxValue('');
|
|
||||||
// Send create action
|
|
||||||
|
|
||||||
// allow the observers and computed properties to run first
|
|
||||||
run.schedule('actions', this, function () {
|
|
||||||
this.sendAction('create-item', input, caret);
|
|
||||||
});
|
|
||||||
// We cancel the creation here, so it's up to you to include the created element
|
|
||||||
// in the content and selection property
|
|
||||||
callback(null);
|
|
||||||
},
|
|
||||||
|
|
||||||
_addSelection(obj) {
|
|
||||||
let _valuePath = this.get('_valuePath');
|
|
||||||
let val = get(obj, _valuePath);
|
|
||||||
let caret = this._selectize.caretPos;
|
|
||||||
|
|
||||||
// caret position is always 1 more than the desired index as this method
|
|
||||||
// is called after selectize has inserted the item and the caret has moved
|
|
||||||
// to the right
|
|
||||||
caret = caret - 1;
|
|
||||||
|
|
||||||
this.get('selection').insertAt(caret, obj);
|
|
||||||
|
|
||||||
run.schedule('actions', this, function () {
|
|
||||||
this.sendAction('add-item', obj);
|
|
||||||
this.sendAction('add-value', val);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_onChange(args) {
|
|
||||||
let selection = get(this, 'selection');
|
|
||||||
let valuePath = get(this, '_valuePath');
|
|
||||||
let reorderedSelection = emberA([]);
|
|
||||||
|
|
||||||
if (!args || !selection || !isEmberArray(selection) || args.length !== get(selection, 'length')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// exit if we're not dealing with the same objects as the selection
|
|
||||||
let objectsHaveChanged = selection.any(function (obj) {
|
|
||||||
return args.indexOf(get(obj, valuePath)) === -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (objectsHaveChanged) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// exit if the order is still the same
|
|
||||||
let orderIsSame = selection.every(function (obj, idx) {
|
|
||||||
return get(obj, valuePath) === args[idx];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (orderIsSame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have a re-order, update the selection
|
|
||||||
args.forEach((value) => {
|
|
||||||
let obj = selection.find(function (item) {
|
|
||||||
return `${get(item, valuePath)}` === value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (obj) {
|
|
||||||
reorderedSelection.addObject(obj);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set('selection', reorderedSelection);
|
|
||||||
},
|
|
||||||
|
|
||||||
_preventOpeningWhenBlank() {
|
|
||||||
let openOnFocus = this.get('openOnFocus');
|
|
||||||
|
|
||||||
if (!openOnFocus) {
|
|
||||||
run.schedule('afterRender', this, function () {
|
|
||||||
let selectize = this._selectize;
|
|
||||||
if (selectize) {
|
|
||||||
selectize.on('dropdown_open', function () {
|
|
||||||
if (isBlank(selectize.$control_input.val())) {
|
|
||||||
selectize.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
selectize.on('type', function (filter) {
|
|
||||||
if (isBlank(filter)) {
|
|
||||||
selectize.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
didInsertElement() {
|
|
||||||
this._super(...arguments);
|
|
||||||
this._preventOpeningWhenBlank();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
189
ghost/admin/app/components/gh-token-input.js
Normal file
189
ghost/admin/app/components/gh-token-input.js
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/* global key */
|
||||||
|
import Component from '@ember/component';
|
||||||
|
import Ember from 'ember';
|
||||||
|
import {A} from '@ember/array';
|
||||||
|
import {
|
||||||
|
advanceSelectableOption,
|
||||||
|
defaultMatcher,
|
||||||
|
filterOptions
|
||||||
|
} from 'ember-power-select/utils/group-utils';
|
||||||
|
import {computed} from '@ember/object';
|
||||||
|
import {get} from '@ember/object';
|
||||||
|
import {htmlSafe} from '@ember/string';
|
||||||
|
import {isBlank} from '@ember/utils';
|
||||||
|
import {task} from 'ember-concurrency';
|
||||||
|
|
||||||
|
const {Handlebars} = Ember;
|
||||||
|
|
||||||
|
const BACKSPACE = 8;
|
||||||
|
const TAB = 9;
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
|
||||||
|
// public attrs
|
||||||
|
closeOnSelect: false,
|
||||||
|
labelField: 'name',
|
||||||
|
matcher: defaultMatcher,
|
||||||
|
searchField: 'name',
|
||||||
|
tagName: '',
|
||||||
|
triggerComponent: 'gh-token-input/trigger',
|
||||||
|
|
||||||
|
optionsWithoutSelected: computed('options.[]', 'selected.[]', function () {
|
||||||
|
return this.get('optionsWithoutSelectedTask').perform();
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
handleKeydown(select, event) {
|
||||||
|
// On backspace with empty text, remove the last token but deviate
|
||||||
|
// from default behaviour by not updating search to match last token
|
||||||
|
if (event.keyCode === BACKSPACE && isBlank(event.target.value)) {
|
||||||
|
let lastSelection = select.selected[select.selected.length - 1];
|
||||||
|
|
||||||
|
if (lastSelection) {
|
||||||
|
this.get('onchange')(select.selected.slice(0, -1), select);
|
||||||
|
select.actions.search('');
|
||||||
|
select.actions.open(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent default
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab should work the same as Enter if there's a highlighted option
|
||||||
|
if (event.keyCode === TAB && !isBlank(event.target.value) && select.highlighted) {
|
||||||
|
if (!select.selected || select.selected.indexOf(select.highlighted) === -1) {
|
||||||
|
select.actions.choose(select.highlighted, event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to default
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onfocus() {
|
||||||
|
key.setScope('gh-token-input');
|
||||||
|
|
||||||
|
if (this.get('onfocus')) {
|
||||||
|
this.get('onfocus')(...arguments);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onblur() {
|
||||||
|
key.setScope('default');
|
||||||
|
|
||||||
|
if (this.get('onblur')) {
|
||||||
|
this.get('onblur')(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsWithoutSelectedTask: task(function* () {
|
||||||
|
let options = yield this.get('options');
|
||||||
|
let selected = yield this.get('selected');
|
||||||
|
return options.filter((o) => !selected.includes(o));
|
||||||
|
}),
|
||||||
|
|
||||||
|
shouldShowCreateOption(term, options) {
|
||||||
|
if (this.get('showCreateWhen')) {
|
||||||
|
return this.get('showCreateWhen')(term, options);
|
||||||
|
} else {
|
||||||
|
return this.hideCreateOptionOnSameTerm(term, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hideCreateOptionOnSameTerm(term, options) {
|
||||||
|
let searchField = this.get('searchField');
|
||||||
|
let existingOption = options.findBy(searchField, term);
|
||||||
|
return !existingOption;
|
||||||
|
},
|
||||||
|
|
||||||
|
addCreateOption(term, options) {
|
||||||
|
if (this.shouldShowCreateOption(term, options)) {
|
||||||
|
options.unshift(this.buildSuggestionForTerm(term));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
searchAndSuggest(term, select) {
|
||||||
|
return this.get('searchAndSuggestTask').perform(term, select);
|
||||||
|
},
|
||||||
|
|
||||||
|
searchAndSuggestTask: task(function* (term, select) {
|
||||||
|
let newOptions = (yield this.get('optionsWithoutSelected')).toArray();
|
||||||
|
|
||||||
|
if (term.length === 0) {
|
||||||
|
return newOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchAction = this.get('search');
|
||||||
|
if (searchAction) {
|
||||||
|
let results = yield searchAction(term, select);
|
||||||
|
|
||||||
|
if (results.toArray) {
|
||||||
|
results = results.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addCreateOption(term, results);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
newOptions = this.filter(A(newOptions), term);
|
||||||
|
this.addCreateOption(term, newOptions);
|
||||||
|
|
||||||
|
return newOptions;
|
||||||
|
}),
|
||||||
|
|
||||||
|
selectOrCreate(selection, select) {
|
||||||
|
let suggestion = selection.find((option) => {
|
||||||
|
return option.__isSuggestion__;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (suggestion) {
|
||||||
|
this.get('oncreate')(suggestion.__value__, select);
|
||||||
|
} else {
|
||||||
|
this.get('onchange')(selection, select);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear select search
|
||||||
|
select.actions.search('');
|
||||||
|
},
|
||||||
|
|
||||||
|
filter(options, searchText) {
|
||||||
|
let matcher;
|
||||||
|
if (this.get('searchField')) {
|
||||||
|
matcher = (option, text) => this.matcher(get(option, this.get('searchField')), text);
|
||||||
|
} else {
|
||||||
|
matcher = (option, text) => this.matcher(option, text);
|
||||||
|
}
|
||||||
|
return filterOptions(options || [], searchText, matcher);
|
||||||
|
},
|
||||||
|
|
||||||
|
buildSuggestionForTerm(term) {
|
||||||
|
return {
|
||||||
|
__isSuggestion__: true,
|
||||||
|
__value__: term,
|
||||||
|
text: this.buildSuggestionLabel(term)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
buildSuggestionLabel(term) {
|
||||||
|
let buildSuggestion = this.get('buildSuggestion');
|
||||||
|
if (buildSuggestion) {
|
||||||
|
return buildSuggestion(term);
|
||||||
|
}
|
||||||
|
return htmlSafe(`Add <strong>"${Handlebars.Utils.escapeExpression(term)}"...</strong>`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// always select the first item in the list that isn't the "Add x" option
|
||||||
|
defaultHighlighted(select) {
|
||||||
|
let {results} = select;
|
||||||
|
let option = advanceSelectableOption(results, undefined, 1);
|
||||||
|
|
||||||
|
if (results.length > 1 && option.__isSuggestion__) {
|
||||||
|
option = advanceSelectableOption(results, option, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
58
ghost/admin/app/components/gh-token-input/select-multiple.js
Normal file
58
ghost/admin/app/components/gh-token-input/select-multiple.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import $ from 'jquery';
|
||||||
|
import PowerSelectMultiple from 'ember-power-select/components/power-select-multiple';
|
||||||
|
import {bind} from '@ember/runloop';
|
||||||
|
|
||||||
|
const endActions = 'click.ghToken mouseup.ghToken touchend.ghToken';
|
||||||
|
|
||||||
|
// triggering focus on the search input within ESA's onfocus event breaks the
|
||||||
|
// drag-n-drop functionality in ember-drag-drop so we watch for events that
|
||||||
|
// could be the start of a drag and disable the default focus behaviour until
|
||||||
|
// we get another event signalling the end of a drag
|
||||||
|
|
||||||
|
export default PowerSelectMultiple.extend({
|
||||||
|
|
||||||
|
_canFocus: true,
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
if (this._allowFocusListener) {
|
||||||
|
$(window).off(endActions, this._allowFocusListener);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
optionMouseDown(event) {
|
||||||
|
if (event.which === 1 && !event.ctrlKey) {
|
||||||
|
this._denyFocus(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
optionTouchStart(event) {
|
||||||
|
this._denyFocus(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFocus() {
|
||||||
|
if (this._canFocus) {
|
||||||
|
this._super(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_denyFocus() {
|
||||||
|
if (this._canFocus) {
|
||||||
|
this._canFocus = false;
|
||||||
|
|
||||||
|
this._allowFocusListener = bind(this, this._allowFocus);
|
||||||
|
|
||||||
|
$(window).on(endActions, this._allowFocusListener);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_allowFocus() {
|
||||||
|
this._canFocus = true;
|
||||||
|
|
||||||
|
$(window).off(endActions, this._allowFocusListener);
|
||||||
|
this._allowFocusListener = null;
|
||||||
|
}
|
||||||
|
});
|
9
ghost/admin/app/components/gh-token-input/select.js
Normal file
9
ghost/admin/app/components/gh-token-input/select.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// NOTE: This is only here because we wanted to override the `eventType` attr.
|
||||||
|
// DO NOT add any functionality here, this will hopefully disappear after an
|
||||||
|
// upstream PR
|
||||||
|
|
||||||
|
import PowerSelect from 'ember-power-select/components/power-select';
|
||||||
|
|
||||||
|
export default PowerSelect.extend({
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,5 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: ''
|
||||||
|
});
|
35
ghost/admin/app/components/gh-token-input/tag-token.js
Normal file
35
ghost/admin/app/components/gh-token-input/tag-token.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import DraggableObject from 'ember-drag-drop/components/draggable-object';
|
||||||
|
import {computed} from '@ember/object';
|
||||||
|
import {readOnly} from '@ember/object/computed';
|
||||||
|
|
||||||
|
export default DraggableObject.extend({
|
||||||
|
|
||||||
|
attributeBindings: ['title'],
|
||||||
|
classNames: ['tag-token'],
|
||||||
|
classNameBindings: [
|
||||||
|
'primary:tag-token--primary',
|
||||||
|
'internal:tag-token--internal'
|
||||||
|
],
|
||||||
|
|
||||||
|
content: readOnly('option'),
|
||||||
|
internal: readOnly('option.isInternal'),
|
||||||
|
|
||||||
|
primary: computed('idx', 'internal', function () {
|
||||||
|
return !this.get('internal') && this.get('idx') === 0;
|
||||||
|
}),
|
||||||
|
|
||||||
|
title: computed('option.name', 'primary', 'internal', function () {
|
||||||
|
let name = this.get('option.name');
|
||||||
|
|
||||||
|
if (this.get('internal')) {
|
||||||
|
return `${name} (internal)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.get('primary')) {
|
||||||
|
return `${name} (primary tag)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
28
ghost/admin/app/components/gh-token-input/trigger.js
Normal file
28
ghost/admin/app/components/gh-token-input/trigger.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import EmberPowerSelectMultipleTrigger from 'ember-power-select/components/power-select-multiple/trigger';
|
||||||
|
import {copy} from '@ember/object/internals';
|
||||||
|
|
||||||
|
export default EmberPowerSelectMultipleTrigger.extend({
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
handleOptionMouseDown(event) {
|
||||||
|
let action = this.get('extra.optionMouseDown');
|
||||||
|
if (action) {
|
||||||
|
return action(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOptionTouchStart(event) {
|
||||||
|
let action = this.get('extra.optionTouchStart');
|
||||||
|
if (action) {
|
||||||
|
return action(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reorderItems() {
|
||||||
|
// ember-drag-drop's sortable-objects has two-way bindings and will
|
||||||
|
// update EPS' selected value directly. We have to create a copy
|
||||||
|
// after sorting in order to force the onchange action to be triggered
|
||||||
|
this.get('select').actions.select(copy(this.get('select.selected')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -112,10 +112,6 @@ export default Controller.extend({
|
|||||||
navItems.removeObject(item);
|
navItems.removeObject(item);
|
||||||
},
|
},
|
||||||
|
|
||||||
reorderItems(navItems) {
|
|
||||||
this.set('model.navigation', navItems);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUrl(url, navItem) {
|
updateUrl(url, navItem) {
|
||||||
if (!navItem) {
|
if (!navItem) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
/* eslint-disable camelcase */
|
|
||||||
import Model from 'ember-data/model';
|
import Model from 'ember-data/model';
|
||||||
import ValidationEngine from 'ghost-admin/mixins/validation-engine';
|
import ValidationEngine from 'ghost-admin/mixins/validation-engine';
|
||||||
import attr from 'ember-data/attr';
|
import attr from 'ember-data/attr';
|
||||||
import {computed} from '@ember/object';
|
|
||||||
import {equal} from '@ember/object/computed';
|
import {equal} from '@ember/object/computed';
|
||||||
import {guidFor} from '@ember/object/internals';
|
|
||||||
import {observer} from '@ember/object';
|
import {observer} from '@ember/object';
|
||||||
import {inject as service} from '@ember/service';
|
import {inject as service} from '@ember/service';
|
||||||
|
|
||||||
@ -30,13 +27,6 @@ export default Model.extend(ValidationEngine, {
|
|||||||
|
|
||||||
feature: service(),
|
feature: service(),
|
||||||
|
|
||||||
// HACK: ugly hack to main compatibility with selectize as used in the
|
|
||||||
// PSM tags input
|
|
||||||
// TODO: remove once we've switched over to EPS for the tags input
|
|
||||||
uuid: computed(function () {
|
|
||||||
return guidFor(this);
|
|
||||||
}),
|
|
||||||
|
|
||||||
setVisibility() {
|
setVisibility() {
|
||||||
let internalRegex = /^#.?/;
|
let internalRegex = /^#.?/;
|
||||||
this.set('visibility', internalRegex.test(this.get('name')) ? 'internal' : 'public');
|
this.set('visibility', internalRegex.test(this.get('name')) ? 'internal' : 'public');
|
||||||
|
@ -20,7 +20,7 @@ function K() {
|
|||||||
|
|
||||||
let shortcuts = {};
|
let shortcuts = {};
|
||||||
|
|
||||||
shortcuts.esc = {action: 'closeMenus', scope: 'all'};
|
shortcuts.esc = {action: 'closeMenus', scope: 'default'};
|
||||||
shortcuts[`${ctrlOrCmd}+s`] = {action: 'save', scope: 'all'};
|
shortcuts[`${ctrlOrCmd}+s`] = {action: 'save', scope: 'all'};
|
||||||
|
|
||||||
export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
|
export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
@import "components/pagination.css";
|
@import "components/pagination.css";
|
||||||
@import "components/badges.css";
|
@import "components/badges.css";
|
||||||
@import "components/settings-menu.css";
|
@import "components/settings-menu.css";
|
||||||
@import "components/selectize.css";
|
|
||||||
@import "components/power-select.css";
|
@import "components/power-select.css";
|
||||||
@import "components/power-calendar.css";
|
@import "components/power-calendar.css";
|
||||||
@import "components/publishmenu.css";
|
@import "components/publishmenu.css";
|
||||||
@ -82,7 +81,6 @@ hr {
|
|||||||
input,
|
input,
|
||||||
.gh-input,
|
.gh-input,
|
||||||
.gh-select,
|
.gh-select,
|
||||||
.selectize-input,
|
|
||||||
.gh-select select {
|
.gh-select select {
|
||||||
color: var(--darkgrey);
|
color: var(--darkgrey);
|
||||||
border-color: color(var(--lightgrey));
|
border-color: color(var(--lightgrey));
|
||||||
@ -189,21 +187,6 @@ input,
|
|||||||
background: var(--lightgrey);
|
background: var(--lightgrey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectize-dropdown,
|
|
||||||
.selectize-input,
|
|
||||||
.selectize-input input {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown .option {
|
|
||||||
color: var(--lightgrey);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input,
|
|
||||||
.selectize-control.single .selectize-input.input-active {
|
|
||||||
background: var(--lightgrey);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-select select {
|
.gh-select select {
|
||||||
color: var(--darkgrey);
|
color: var(--darkgrey);
|
||||||
background: var(--lightgrey);
|
background: var(--lightgrey);
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
@import "components/pagination.css";
|
@import "components/pagination.css";
|
||||||
@import "components/badges.css";
|
@import "components/badges.css";
|
||||||
@import "components/settings-menu.css";
|
@import "components/settings-menu.css";
|
||||||
@import "components/selectize.css";
|
|
||||||
@import "components/power-select.css";
|
@import "components/power-select.css";
|
||||||
@import "components/power-calendar.css";
|
@import "components/power-calendar.css";
|
||||||
@import "components/publishmenu.css";
|
@import "components/publishmenu.css";
|
||||||
|
@ -151,11 +151,3 @@
|
|||||||
.closed > .dropdown-menu {
|
.closed > .dropdown-menu {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Selectize
|
|
||||||
/* ---------------------------------------------------------- */
|
|
||||||
|
|
||||||
.selectize-dropdown {
|
|
||||||
z-index: 200;
|
|
||||||
}
|
|
||||||
|
@ -152,3 +152,41 @@
|
|||||||
.ember-power-select-option[aria-current="true"] {
|
.ember-power-select-option[aria-current="true"] {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Multiple */
|
||||||
|
.ember-power-select-multiple-trigger {
|
||||||
|
background: #fff;
|
||||||
|
padding: 5px;
|
||||||
|
border: rgb(214, 227, 235) 1px solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ember-power-select-multiple-option {
|
||||||
|
margin: 2px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 0;
|
||||||
|
background: #3eb0ef;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ember-power-select-trigger-multiple-input {
|
||||||
|
height: 24px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ember-power-select-status-icon {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Token input */
|
||||||
|
.gh-token-input .ember-power-select-options {
|
||||||
|
max-height: 172px; /* 5.5 options */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tag input */
|
||||||
|
.tag-token--primary {
|
||||||
|
background: color(#3eb0ef lightness(-20%));
|
||||||
|
}
|
||||||
|
@ -1,405 +0,0 @@
|
|||||||
.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
|
|
||||||
visibility: visible !important;
|
|
||||||
border: 0 none !important;
|
|
||||||
background: #f2f2f2 !important;
|
|
||||||
background: rgba(0, 0, 0, 0.06) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.plugin-drag_drop .ui-sortable-placeholder:after {
|
|
||||||
content: "!";
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.plugin-drag_drop .ui-sortable-helper {
|
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown-header {
|
|
||||||
position: relative;
|
|
||||||
padding: 5px 8px;
|
|
||||||
border-bottom: 1px solid #d0d0d0;
|
|
||||||
background: #f8f8f8;
|
|
||||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown-header-close {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 8px;
|
|
||||||
margin-top: -12px;
|
|
||||||
color: #303030;
|
|
||||||
font-size: 20px !important;
|
|
||||||
line-height: 20px;
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown-header-close:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown.plugin-optgroup_columns .optgroup {
|
|
||||||
float: left;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-top: 0 none;
|
|
||||||
border-right: 1px solid #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
|
|
||||||
border-right: 0 none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
|
|
||||||
border-top: 0 none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.plugin-remove_button [data-value] {
|
|
||||||
position: relative;
|
|
||||||
padding-right: 20px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.plugin-remove_button [data-value] .remove {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 17px;
|
|
||||||
border-radius: 0 2px 2px 0;
|
|
||||||
color: inherit;
|
|
||||||
vertical-align: middle;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.plugin-remove_button [data-value] .remove:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.plugin-remove_button [data-value].active .remove {
|
|
||||||
border-left-color: #00578d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.plugin-remove_button .disabled [data-value] .remove {
|
|
||||||
border-left-color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown,
|
|
||||||
.selectize-input,
|
|
||||||
.selectize-input input {
|
|
||||||
color: #303030;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input,
|
|
||||||
.selectize-control.single .selectize-input.input-active {
|
|
||||||
display: inline-block;
|
|
||||||
background: #fff;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: inline-block;
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 10px 12px;
|
|
||||||
width: 100%;
|
|
||||||
height: 39px;
|
|
||||||
border: color(var(--lightgrey) l(-5%) s(-10%)) 1px solid;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
color: color(var(--midgrey) l(-18%));
|
|
||||||
transition: border-color 0.15s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input.focus {
|
|
||||||
border-color: color(var(--lightgrey) l(-15%) s(-10%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input.has-items {
|
|
||||||
padding: 6px 10px 3px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input.full {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input.disabled,
|
|
||||||
.selectize-input.disabled * {
|
|
||||||
cursor: default !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input.dropdown-active {
|
|
||||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input > * {
|
|
||||||
display: -moz-inline-stack;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: baseline;
|
|
||||||
zoom: 1;
|
|
||||||
|
|
||||||
*display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input > div {
|
|
||||||
margin: 0 3px 3px 0;
|
|
||||||
padding: 1px 4px;
|
|
||||||
background: var(--blue);
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Active tag - selected state when tag is clicked */
|
|
||||||
.selectize-control.multi .selectize-input > div.active {
|
|
||||||
background: color(var(--blue) lightness(-10%));
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input.disabled > div,
|
|
||||||
.selectize-control.multi .selectize-input.disabled > div.active {
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
background: #d2d2d2;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input > input {
|
|
||||||
display: inline-block !important;
|
|
||||||
margin: 0 1px !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
min-height: 0 !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
max-height: none !important;
|
|
||||||
border: 0 none !important;
|
|
||||||
background: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
text-indent: 0 !important;
|
|
||||||
line-height: inherit !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input > input:-ms-clear {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input > input:focus {
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-input:after {
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
clear: left;
|
|
||||||
}
|
|
||||||
.selectize-input.dropdown-active:before {
|
|
||||||
content: " ";
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
display: block;
|
|
||||||
height: 1px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1000;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: -1px 0 0 0;
|
|
||||||
border: 1px solid #b1b1b1;
|
|
||||||
border-top: 0 none;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
|
||||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown [data-selectable] {
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown [data-selectable] .highlight {
|
|
||||||
background: #fff3b8;
|
|
||||||
border-radius: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown [data-selectable],
|
|
||||||
.selectize-dropdown .optgroup-header,
|
|
||||||
.selectize-dropdown .dropdown-empty-message {
|
|
||||||
padding: 7px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown .optgroup-header {
|
|
||||||
background: #fff;
|
|
||||||
color: #303030;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown .active {
|
|
||||||
background: color(var(--blue) alpha(-85%));
|
|
||||||
color: var(--darkgrey);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown .active.create {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown .create {
|
|
||||||
color: rgba(48, 48, 48, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown-content {
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.single .selectize-input,
|
|
||||||
.selectize-control.single .selectize-input input {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.single .selectize-input.input-active,
|
|
||||||
.selectize-control.single .selectize-input.input-active input {
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.single .selectize-input:after {
|
|
||||||
content: " ";
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 15px;
|
|
||||||
display: block;
|
|
||||||
margin-top: -3px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-width: 5px 5px 0 5px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: #808080 transparent transparent transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.single .selectize-input.dropdown-active:after {
|
|
||||||
margin-top: -4px;
|
|
||||||
border-width: 0 5px 5px 5px;
|
|
||||||
border-color: transparent transparent #808080 transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.rtl.single .selectize-input:after {
|
|
||||||
right: auto;
|
|
||||||
left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.rtl .selectize-input > input {
|
|
||||||
margin: 0 4px 0 -2px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control .selectize-input.disabled {
|
|
||||||
background-color: #fafafa;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input.has-items {
|
|
||||||
padding-right: 5px;
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input.disabled [data-value] {
|
|
||||||
background: none;
|
|
||||||
box-shadow: none;
|
|
||||||
color: #999;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input.disabled [data-value],
|
|
||||||
.selectize-control.multi .selectize-input.disabled [data-value] .remove {
|
|
||||||
border-color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input.disabled [data-value] .remove {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input [data-value] {
|
|
||||||
background: var(--blue);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.multi .selectize-input [data-value].active {
|
|
||||||
background: color(var(--blue) lightness(-10%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.single .selectize-input {
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-control.single .selectize-input,
|
|
||||||
.selectize-dropdown.single {
|
|
||||||
border-color: #b8b8b8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optgroup:first-of-type .optgroup-header {
|
|
||||||
margin-bottom: 7px;
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown .optgroup-header {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
padding-top: 7px;
|
|
||||||
background: #fff;
|
|
||||||
color: var(--midgrey);
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown .optgroup-header:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 52%;
|
|
||||||
left: calc(100% + 3px);
|
|
||||||
display: block;
|
|
||||||
width: calc(189px - 100%);
|
|
||||||
height: 1px;
|
|
||||||
border-bottom: #dfe1e3 1px solid;
|
|
||||||
}
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
.selectize-dropdown .optgroup-header:after {
|
|
||||||
width: calc(224px - 100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
.selectize-dropdown .optgroup-header:after {
|
|
||||||
width: calc(80vw - 45px - 100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectize-dropdown .option {
|
|
||||||
line-height: 1.35em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-empty-message {
|
|
||||||
position: relative;
|
|
||||||
color: var(--midgrey);
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
@ -188,10 +188,6 @@
|
|||||||
color: color(var(--red) lightness(-10%));
|
color: color(var(--red) lightness(-10%));
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-menu-content .selectize-input {
|
|
||||||
padding: 7px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-setting-custom-excerpt {
|
.post-setting-custom-excerpt {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
line-height: 1.35em;
|
line-height: 1.35em;
|
||||||
|
@ -319,12 +319,6 @@ body > .ember-view:not(.default-liquid-destination) {
|
|||||||
transform: translate3d(80vw, 0, 0);
|
transform: translate3d(80vw, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-nav-search-input .selectize-input,
|
|
||||||
.gh-nav-search-input .selectize-input input,
|
|
||||||
.gh-nav-search-input .selectize-dropdown {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-nav-list {
|
.gh-nav-list {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
@ -67,17 +67,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="tag-input">Tags</label>
|
<label for="tag-input">Tags</label>
|
||||||
{{gh-selectize
|
{{gh-psm-tags-input post=model triggerId="tag-input"}}
|
||||||
id="tag-input"
|
|
||||||
multiple=true
|
|
||||||
selection=model.tags
|
|
||||||
content=availableTags
|
|
||||||
optionValuePath="content.uuid"
|
|
||||||
optionLabelPath="content.name"
|
|
||||||
openOnFocus=false
|
|
||||||
create-item="addTag"
|
|
||||||
remove-item="removeTag"
|
|
||||||
plugins="remove_button, drag_drop"}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="customExcerpt"}}
|
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="customExcerpt"}}
|
||||||
|
12
ghost/admin/app/templates/components/gh-psm-tags-input.hbs
Normal file
12
ghost/admin/app/templates/components/gh-psm-tags-input.hbs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{{gh-token-input
|
||||||
|
extra=(hash
|
||||||
|
tokenComponent="gh-token-input/tag-token"
|
||||||
|
)
|
||||||
|
onchange=(action "updateTags")
|
||||||
|
oncreate=(action "createTag")
|
||||||
|
options=availableTags
|
||||||
|
renderInPlace=true
|
||||||
|
selected=post.tags
|
||||||
|
showCreateWhen=(action "hideCreateOptionOnMatchingTag")
|
||||||
|
triggerId=triggerId
|
||||||
|
}}
|
54
ghost/admin/app/templates/components/gh-token-input.hbs
Normal file
54
ghost/admin/app/templates/components/gh-token-input.hbs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{{#gh-token-input/select-multiple
|
||||||
|
afterOptionsComponent=afterOptionsComponent
|
||||||
|
allowClear=allowClear
|
||||||
|
ariaDescribedBy=ariaDescribedBy
|
||||||
|
ariaInvalid=ariaInvalid
|
||||||
|
ariaLabel=ariaLabel
|
||||||
|
ariaLabelledBy=ariaLabelledBy
|
||||||
|
beforeOptionsComponent=beforeOptionsComponent
|
||||||
|
class=(concat "gh-token-input " class)
|
||||||
|
closeOnSelect=closeOnSelect
|
||||||
|
defaultHighlighted=defaultHighlighted
|
||||||
|
destination=destination
|
||||||
|
dir=dir
|
||||||
|
disabled=disabled
|
||||||
|
dropdownClass=dropdownClass
|
||||||
|
extra=extra
|
||||||
|
horizontalPosition=horizontalPosition
|
||||||
|
initiallyOpened=initiallyOpened
|
||||||
|
loadingMessage=loadingMessage
|
||||||
|
matcher=matcher
|
||||||
|
matchTriggerWidth=matchTriggerWidth
|
||||||
|
noMatchesMessage=noMatchesMessage
|
||||||
|
onblur=(action "onblur")
|
||||||
|
onchange=(action selectOrCreate)
|
||||||
|
onclose=onclose
|
||||||
|
onfocus=(action "onfocus")
|
||||||
|
oninput=oninput
|
||||||
|
onkeydown=(action "handleKeydown")
|
||||||
|
onopen=onopen
|
||||||
|
options=optionsWithoutSelected
|
||||||
|
optionsComponent=(or optionsComponent "power-select-vertical-collection-options")
|
||||||
|
placeholder=placeholder
|
||||||
|
registerAPI=registerAPI
|
||||||
|
renderInPlace=renderInPlace
|
||||||
|
search=(action searchAndSuggest)
|
||||||
|
searchEnabled=searchEnabled
|
||||||
|
searchField=searchField
|
||||||
|
searchMessage=searchMessage
|
||||||
|
searchPlaceholder=searchPlaceholder
|
||||||
|
selected=selected
|
||||||
|
selectedItemComponent=selectedItemComponent
|
||||||
|
tabindex=tabindex
|
||||||
|
triggerClass=triggerClass
|
||||||
|
triggerComponent=triggerComponent
|
||||||
|
triggerId=triggerId
|
||||||
|
verticalPosition=verticalPosition
|
||||||
|
as |option term|
|
||||||
|
}}
|
||||||
|
{{#if option.__isSuggestion__}}
|
||||||
|
{{gh-token-input/suggested-option option=option term=term}}
|
||||||
|
{{else}}
|
||||||
|
{{get option labelField}}
|
||||||
|
{{/if}}
|
||||||
|
{{/gh-token-input/select-multiple}}
|
@ -0,0 +1,125 @@
|
|||||||
|
{{!--
|
||||||
|
NOTE: changes from ember-power-select:
|
||||||
|
- `extra` has our custom drag-tracking actions assigned to it
|
||||||
|
--}}
|
||||||
|
{{#if (hasBlock "inverse")}}
|
||||||
|
{{#gh-token-input/select
|
||||||
|
afterOptionsComponent=afterOptionsComponent
|
||||||
|
allowClear=allowClear
|
||||||
|
ariaDescribedBy=ariaDescribedBy
|
||||||
|
ariaInvalid=ariaInvalid
|
||||||
|
ariaLabel=ariaLabel
|
||||||
|
ariaLabelledBy=ariaLabelledBy
|
||||||
|
beforeOptionsComponent=beforeOptionsComponent
|
||||||
|
buildSelection=(action "buildSelection")
|
||||||
|
calculatePosition=calculatePosition
|
||||||
|
class=class
|
||||||
|
closeOnSelect=closeOnSelect
|
||||||
|
defaultHighlighted=defaultHighlighted
|
||||||
|
destination=destination
|
||||||
|
dir=dir
|
||||||
|
disabled=disabled
|
||||||
|
dropdownClass=dropdownClass
|
||||||
|
extra=(assign extra (hash
|
||||||
|
optionMouseDown=(action "optionMouseDown")
|
||||||
|
optionTouchStart=(action "optionTouchStart")
|
||||||
|
))
|
||||||
|
horizontalPosition=horizontalPosition
|
||||||
|
initiallyOpened=initiallyOpened
|
||||||
|
loadingMessage=loadingMessage
|
||||||
|
matcher=matcher
|
||||||
|
matchTriggerWidth=matchTriggerWidth
|
||||||
|
noMatchesMessage=noMatchesMessage
|
||||||
|
onblur=onblur
|
||||||
|
onchange=onchange
|
||||||
|
onclose=onclose
|
||||||
|
onfocus=(action "handleFocus")
|
||||||
|
oninput=oninput
|
||||||
|
onkeydown=(action "handleKeydown")
|
||||||
|
onopen=(action "handleOpen")
|
||||||
|
options=options
|
||||||
|
optionsComponent=optionsComponent
|
||||||
|
groupComponent=groupComponent
|
||||||
|
placeholder=placeholder
|
||||||
|
registerAPI=(readonly registerAPI)
|
||||||
|
renderInPlace=renderInPlace
|
||||||
|
required=required
|
||||||
|
scrollTo=scrollTo
|
||||||
|
search=search
|
||||||
|
searchEnabled=searchEnabled
|
||||||
|
searchField=searchField
|
||||||
|
searchMessage=searchMessage
|
||||||
|
searchPlaceholder=searchPlaceholder
|
||||||
|
selected=selected
|
||||||
|
selectedItemComponent=selectedItemComponent
|
||||||
|
tabindex=computedTabIndex
|
||||||
|
tagName=tagName
|
||||||
|
triggerClass=concatenatedTriggerClass
|
||||||
|
triggerComponent=(component triggerComponent tabindex=tabindex)
|
||||||
|
triggerId=triggerId
|
||||||
|
verticalPosition=verticalPosition
|
||||||
|
as |option select|}}
|
||||||
|
{{yield option select}}
|
||||||
|
{{else}}
|
||||||
|
{{yield to="inverse"}}
|
||||||
|
{{/gh-token-input/select}}
|
||||||
|
{{else}}
|
||||||
|
{{#gh-token-input/select
|
||||||
|
afterOptionsComponent=afterOptionsComponent
|
||||||
|
allowClear=allowClear
|
||||||
|
ariaDescribedBy=ariaDescribedBy
|
||||||
|
ariaInvalid=ariaInvalid
|
||||||
|
ariaLabel=ariaLabel
|
||||||
|
ariaLabelledBy=ariaLabelledBy
|
||||||
|
beforeOptionsComponent=beforeOptionsComponent
|
||||||
|
buildSelection=(action "buildSelection")
|
||||||
|
calculatePosition=calculatePosition
|
||||||
|
class=class
|
||||||
|
closeOnSelect=closeOnSelect
|
||||||
|
defaultHighlighted=defaultHighlighted
|
||||||
|
destination=destination
|
||||||
|
dir=dir
|
||||||
|
disabled=disabled
|
||||||
|
dropdownClass=dropdownClass
|
||||||
|
extra=(assign extra (hash
|
||||||
|
optionMouseDown=(action "optionMouseDown")
|
||||||
|
optionTouchStart=(action "optionTouchStart")
|
||||||
|
))
|
||||||
|
horizontalPosition=horizontalPosition
|
||||||
|
initiallyOpened=initiallyOpened
|
||||||
|
loadingMessage=loadingMessage
|
||||||
|
matcher=matcher
|
||||||
|
matchTriggerWidth=matchTriggerWidth
|
||||||
|
noMatchesMessage=noMatchesMessage
|
||||||
|
onblur=onblur
|
||||||
|
onchange=onchange
|
||||||
|
onclose=onclose
|
||||||
|
onfocus=(action "handleFocus")
|
||||||
|
oninput=oninput
|
||||||
|
onkeydown=(action "handleKeydown")
|
||||||
|
onopen=(action "handleOpen")
|
||||||
|
options=options
|
||||||
|
optionsComponent=optionsComponent
|
||||||
|
groupComponent=groupComponent
|
||||||
|
placeholder=placeholder
|
||||||
|
registerAPI=(readonly registerAPI)
|
||||||
|
renderInPlace=renderInPlace
|
||||||
|
required=required
|
||||||
|
scrollTo=scrollTo
|
||||||
|
search=search
|
||||||
|
searchEnabled=searchEnabled
|
||||||
|
searchField=searchField
|
||||||
|
searchMessage=searchMessage
|
||||||
|
searchPlaceholder=searchPlaceholder
|
||||||
|
selected=selected
|
||||||
|
selectedItemComponent=selectedItemComponent
|
||||||
|
tabindex=computedTabIndex
|
||||||
|
tagName=tagName
|
||||||
|
triggerClass=concatenatedTriggerClass
|
||||||
|
triggerComponent=(component triggerComponent tabindex=tabindex)
|
||||||
|
triggerId=triggerId
|
||||||
|
verticalPosition=verticalPosition
|
||||||
|
as |option select|}}
|
||||||
|
{{yield option select}}
|
||||||
|
{{/gh-token-input/select}}
|
||||||
|
{{/if}}
|
103
ghost/admin/app/templates/components/gh-token-input/select.hbs
Normal file
103
ghost/admin/app/templates/components/gh-token-input/select.hbs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{{!--
|
||||||
|
NOTE: the only thing changed here is `eventType="click"` on dropdown.trigger
|
||||||
|
so it doesn't interfere with the drag-n-drop sorting
|
||||||
|
|
||||||
|
When upgrading ember-power-select ensure the full component template is
|
||||||
|
copied across here
|
||||||
|
--}}
|
||||||
|
{{#basic-dropdown
|
||||||
|
classNames=(readonly classNames)
|
||||||
|
horizontalPosition=(readonly horizontalPosition)
|
||||||
|
calculatePosition=calculatePosition
|
||||||
|
destination=(readonly destination)
|
||||||
|
initiallyOpened=(readonly initiallyOpened)
|
||||||
|
matchTriggerWidth=(readonly matchTriggerWidth)
|
||||||
|
onClose=(action "onClose")
|
||||||
|
onOpen=(action "onOpen")
|
||||||
|
registerAPI=(action "registerAPI")
|
||||||
|
renderInPlace=(readonly renderInPlace)
|
||||||
|
verticalPosition=(readonly verticalPosition)
|
||||||
|
disabled=(readonly disabled)
|
||||||
|
as |dropdown|}}
|
||||||
|
|
||||||
|
{{#dropdown.trigger
|
||||||
|
tagName=(readonly _triggerTagName)
|
||||||
|
ariaDescribedBy=(readonly ariaDescribedBy)
|
||||||
|
ariaInvalid=(readonly ariaInvalid)
|
||||||
|
ariaLabel=(readonly ariaLabel)
|
||||||
|
ariaLabelledBy=(readonly ariaLabelledBy)
|
||||||
|
ariaRequired=(readonly required)
|
||||||
|
class=(readonly concatenatedTriggerClasses)
|
||||||
|
id=(readonly triggerId)
|
||||||
|
eventType="click"
|
||||||
|
onKeyDown=(action "onTriggerKeydown")
|
||||||
|
onFocus=(action "onTriggerFocus")
|
||||||
|
onBlur=(action "onTriggerBlur")
|
||||||
|
tabindex=(readonly tabindex)}}
|
||||||
|
{{#component triggerComponent
|
||||||
|
allowClear=(readonly allowClear)
|
||||||
|
buildSelection=(readonly buildSelection)
|
||||||
|
extra=(readonly extra)
|
||||||
|
listboxId=(readonly optionsId)
|
||||||
|
loadingMessage=(readonly loadingMessage)
|
||||||
|
onFocus=(action "onFocus")
|
||||||
|
onBlur=(action "onBlur")
|
||||||
|
onInput=(action "onInput")
|
||||||
|
placeholder=(readonly placeholder)
|
||||||
|
placeholderComponent=(readonly placeholderComponent)
|
||||||
|
onKeydown=(action "onKeydown")
|
||||||
|
searchEnabled=(readonly searchEnabled)
|
||||||
|
searchField=(readonly searchField)
|
||||||
|
select=(readonly publicAPI)
|
||||||
|
selectedItemComponent=(readonly selectedItemComponent)
|
||||||
|
as |opt term|}}
|
||||||
|
{{yield opt term}}
|
||||||
|
{{/component}}
|
||||||
|
{{/dropdown.trigger}}
|
||||||
|
|
||||||
|
{{#dropdown.content _contentTagName=_contentTagName class=(readonly concatenatedDropdownClasses)}}
|
||||||
|
{{component beforeOptionsComponent
|
||||||
|
extra=(readonly extra)
|
||||||
|
listboxId=(readonly optionsId)
|
||||||
|
onInput=(action "onInput")
|
||||||
|
onKeydown=(action "onKeydown")
|
||||||
|
searchEnabled=(readonly searchEnabled)
|
||||||
|
onFocus=(action "onFocus")
|
||||||
|
onBlur=(action "onBlur")
|
||||||
|
placeholder=(readonly placeholder)
|
||||||
|
placeholderComponent=(readonly placeholderComponent)
|
||||||
|
searchPlaceholder=(readonly searchPlaceholder)
|
||||||
|
select=(readonly publicAPI)}}
|
||||||
|
{{#if mustShowSearchMessage}}
|
||||||
|
{{component searchMessageComponent
|
||||||
|
searchMessage=(readonly searchMessage)
|
||||||
|
select=(readonly publicAPI)
|
||||||
|
}}
|
||||||
|
{{else if mustShowNoMessages}}
|
||||||
|
{{#if (hasBlock "inverse")}}
|
||||||
|
{{yield to="inverse"}}
|
||||||
|
{{else if noMatchesMessage}}
|
||||||
|
<ul class="ember-power-select-options" role="listbox">
|
||||||
|
<li class="ember-power-select-option ember-power-select-option--no-matches-message" role="option">
|
||||||
|
{{noMatchesMessage}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
{{#component optionsComponent
|
||||||
|
class="ember-power-select-options"
|
||||||
|
extra=(readonly extra)
|
||||||
|
groupIndex=""
|
||||||
|
loadingMessage=(readonly loadingMessage)
|
||||||
|
id=(readonly optionsId)
|
||||||
|
options=(readonly publicAPI.results)
|
||||||
|
optionsComponent=(readonly optionsComponent)
|
||||||
|
groupComponent=(readonly groupComponent)
|
||||||
|
select=(readonly publicAPI)
|
||||||
|
as |option term|}}
|
||||||
|
{{yield option term}}
|
||||||
|
{{/component}}
|
||||||
|
{{/if}}
|
||||||
|
{{component afterOptionsComponent select=(readonly publicAPI) extra=(readonly extra)}}
|
||||||
|
{{/dropdown.content}}
|
||||||
|
{{/basic-dropdown}}
|
@ -0,0 +1 @@
|
|||||||
|
{{option.text}}
|
@ -0,0 +1,56 @@
|
|||||||
|
{{#sortable-objects tagName="ul"
|
||||||
|
id=(concat "ember-power-select-multiple-options-" select.uniqueId)
|
||||||
|
class="ember-power-select-multiple-options"
|
||||||
|
sortableObjectList=select.selected
|
||||||
|
enableSort=true
|
||||||
|
useSwap=false
|
||||||
|
sortEndAction=(action "reorderItems")
|
||||||
|
}}
|
||||||
|
{{#each select.selected as |opt idx|}}
|
||||||
|
{{#component (or extra.tokenComponent draggable-object)
|
||||||
|
tagName="li"
|
||||||
|
class="ember-power-select-multiple-option"
|
||||||
|
select=select
|
||||||
|
option=(readonly opt)
|
||||||
|
idx=idx
|
||||||
|
isSortable=true
|
||||||
|
mouseDown=(action "handleOptionMouseDown")
|
||||||
|
touchStart=(action "handleOptionTouchStart")
|
||||||
|
}}
|
||||||
|
{{#if selectedItemComponent}}
|
||||||
|
{{component selectedItemComponent option=(readonly opt) select=(readonly select)}}
|
||||||
|
{{else}}
|
||||||
|
{{yield opt select}}
|
||||||
|
{{/if}}
|
||||||
|
{{#unless select.disabled}}
|
||||||
|
<span role="button"
|
||||||
|
aria-label="remove element"
|
||||||
|
class="ember-power-select-multiple-remove-btn"
|
||||||
|
data-selected-index={{idx}}>
|
||||||
|
×
|
||||||
|
</span>
|
||||||
|
{{/unless}}
|
||||||
|
{{/component}}
|
||||||
|
{{else}}
|
||||||
|
{{#if (and placeholder (not searchEnabled))}}
|
||||||
|
<span class="ember-power-select-placeholder">{{placeholder}}</span>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#if searchEnabled}}
|
||||||
|
<input type="search" class="ember-power-select-trigger-multiple-input"
|
||||||
|
tabindex="0" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||||
|
id="ember-power-select-trigger-multiple-input-{{select.uniqueId}}"
|
||||||
|
value={{select.searchText}}
|
||||||
|
aria-controls={{listboxId}}
|
||||||
|
style={{triggerMultipleInputStyle}}
|
||||||
|
placeholder={{maybePlaceholder}}
|
||||||
|
disabled={{select.disabled}}
|
||||||
|
oninput={{action "onInput"}}
|
||||||
|
onFocus={{onFocus}}
|
||||||
|
onBlur={{onBlur}}
|
||||||
|
tabindex={{tabindex}}
|
||||||
|
onkeydown={{action "onKeydown"}}>
|
||||||
|
{{/if}}
|
||||||
|
{{/sortable-objects}}
|
||||||
|
<span class="ember-power-select-status-icon"></span>
|
@ -10,11 +10,13 @@
|
|||||||
<div class="gh-setting-header">Navigation</div>
|
<div class="gh-setting-header">Navigation</div>
|
||||||
<div class="gh-blognav-container">
|
<div class="gh-blognav-container">
|
||||||
<form id="settings-navigation" class="gh-blognav" novalidate="novalidate">
|
<form id="settings-navigation" class="gh-blognav" novalidate="novalidate">
|
||||||
{{#sortable-group onChange=(action 'reorderItems') as |group|}}
|
{{#sortable-objects sortableObjectList=model.navigation useSwap=false}}
|
||||||
{{#each model.navigation as |navItem|}}
|
{{#each model.navigation as |navItem|}}
|
||||||
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addNavItem" deleteItem="deleteNavItem" updateUrl="updateUrl" group=group}}
|
{{#draggable-object content=navItem dragHandle=".gh-blognav-grab" isSortable=true}}
|
||||||
|
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addNavItem" deleteItem="deleteNavItem" updateUrl="updateUrl"}}
|
||||||
|
{{/draggable-object}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/sortable-group}}
|
{{/sortable-objects}}
|
||||||
{{gh-navitem navItem=newNavItem baseUrl=blogUrl addItem="addNavItem" updateUrl="updateUrl"}}
|
{{gh-navitem navItem=newNavItem baseUrl=blogUrl addItem="addNavItem" updateUrl="updateUrl"}}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,14 +4,10 @@
|
|||||||
"devicejs": "0.2.7",
|
"devicejs": "0.2.7",
|
||||||
"Faker": "3.1.0",
|
"Faker": "3.1.0",
|
||||||
"google-caja": "6005.0.0",
|
"google-caja": "6005.0.0",
|
||||||
"jquery-ui": "1.11.4",
|
|
||||||
"jquery.simulate.drag-sortable": "0.1.0",
|
|
||||||
"jqueryui-touch-punch": "furf/jquery-ui-touch-punch#4bc009145202d9c7483ba85f3a236a8f3470354d",
|
|
||||||
"keymaster": "1.6.3",
|
"keymaster": "1.6.3",
|
||||||
"normalize.css": "3.0.3",
|
"normalize.css": "3.0.3",
|
||||||
"pretender": "1.1.0",
|
"pretender": "1.1.0",
|
||||||
"rangyinputs": "1.2.0",
|
"rangyinputs": "1.2.0",
|
||||||
"selectize": "~0.12.1",
|
|
||||||
"validator-js": "3.39.0"
|
"validator-js": "3.39.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,9 +157,6 @@ module.exports = function (defaults) {
|
|||||||
import: ['simplemde.js', 'simplemde.css']
|
import: ['simplemde.js', 'simplemde.css']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'ember-cli-selectize': {
|
|
||||||
theme: false
|
|
||||||
},
|
|
||||||
svg: {
|
svg: {
|
||||||
paths: [
|
paths: [
|
||||||
'public/assets/icons'
|
'public/assets/icons'
|
||||||
@ -185,20 +182,7 @@ module.exports = function (defaults) {
|
|||||||
app.import('bower_components/rangyinputs/rangyinputs-jquery-src.js');
|
app.import('bower_components/rangyinputs/rangyinputs-jquery-src.js');
|
||||||
app.import('bower_components/keymaster/keymaster.js');
|
app.import('bower_components/keymaster/keymaster.js');
|
||||||
app.import('bower_components/devicejs/lib/device.js');
|
app.import('bower_components/devicejs/lib/device.js');
|
||||||
|
|
||||||
// jquery-ui partial build
|
|
||||||
app.import('bower_components/jquery-ui/ui/core.js');
|
|
||||||
app.import('bower_components/jquery-ui/ui/widget.js');
|
|
||||||
app.import('bower_components/jquery-ui/ui/mouse.js');
|
|
||||||
app.import('bower_components/jquery-ui/ui/draggable.js');
|
|
||||||
app.import('bower_components/jquery-ui/ui/droppable.js');
|
|
||||||
app.import('bower_components/jquery-ui/ui/sortable.js');
|
|
||||||
app.import('bower_components/google-caja/html-css-sanitizer-bundle.js');
|
app.import('bower_components/google-caja/html-css-sanitizer-bundle.js');
|
||||||
app.import('bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.js');
|
|
||||||
|
|
||||||
if (app.env !== 'production') {
|
|
||||||
app.import(`${app.bowerDirectory}/jquery.simulate.drag-sortable/jquery.simulate.drag-sortable.js`, {type: 'test'});
|
|
||||||
}
|
|
||||||
|
|
||||||
// pull things we rely on via lazy-loading into the test-support.js file so
|
// pull things we rely on via lazy-loading into the test-support.js file so
|
||||||
// that tests don't break when running via http://localhost:4200/tests
|
// that tests don't break when running via http://localhost:4200/tests
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"csscomb": "4.2.0",
|
"csscomb": "4.2.0",
|
||||||
"cssnano": "4.0.0-rc.2",
|
"cssnano": "4.0.0-rc.2",
|
||||||
"ember-ajax": "2.5.6",
|
"ember-ajax": "2.5.6",
|
||||||
|
"ember-assign-helper": "0.1.2",
|
||||||
"ember-browserify": "1.2.0",
|
"ember-browserify": "1.2.0",
|
||||||
"ember-cli": "2.16.2",
|
"ember-cli": "2.16.2",
|
||||||
"ember-cli-active-link-wrapper": "0.3.2",
|
"ember-cli-active-link-wrapper": "0.3.2",
|
||||||
@ -60,7 +61,6 @@
|
|||||||
"ember-cli-node-assets": "0.2.2",
|
"ember-cli-node-assets": "0.2.2",
|
||||||
"ember-cli-postcss": "3.5.2",
|
"ember-cli-postcss": "3.5.2",
|
||||||
"ember-cli-pretender": "1.0.1",
|
"ember-cli-pretender": "1.0.1",
|
||||||
"ember-cli-selectize": "0.5.12",
|
|
||||||
"ember-cli-shims": "1.1.0",
|
"ember-cli-shims": "1.1.0",
|
||||||
"ember-cli-string-helpers": "1.5.0",
|
"ember-cli-string-helpers": "1.5.0",
|
||||||
"ember-cli-test-loader": "2.2.0",
|
"ember-cli-test-loader": "2.2.0",
|
||||||
@ -69,6 +69,7 @@
|
|||||||
"ember-concurrency": "0.8.10",
|
"ember-concurrency": "0.8.10",
|
||||||
"ember-data": "2.16.2",
|
"ember-data": "2.16.2",
|
||||||
"ember-data-filter": "1.13.0",
|
"ember-data-filter": "1.13.0",
|
||||||
|
"ember-drag-drop": "https://github.com/kevinansfield/ember-drag-drop.git#node-4",
|
||||||
"ember-element-resize-detector": "0.1.5",
|
"ember-element-resize-detector": "0.1.5",
|
||||||
"ember-export-application-global": "2.0.0",
|
"ember-export-application-global": "2.0.0",
|
||||||
"ember-fetch": "3.4.0",
|
"ember-fetch": "3.4.0",
|
||||||
@ -82,16 +83,15 @@
|
|||||||
"ember-native-dom-helpers": "0.5.4",
|
"ember-native-dom-helpers": "0.5.4",
|
||||||
"ember-one-way-controls": "2.0.1",
|
"ember-one-way-controls": "2.0.1",
|
||||||
"ember-power-datepicker": "0.4.0",
|
"ember-power-datepicker": "0.4.0",
|
||||||
"ember-power-select": "1.9.9",
|
"ember-power-select": "1.9.11",
|
||||||
"ember-resolver": "4.5.0",
|
"ember-resolver": "4.5.0",
|
||||||
"ember-responsive": "2.0.5",
|
"ember-responsive": "2.0.5",
|
||||||
"ember-route-action-helper": "2.0.6",
|
"ember-route-action-helper": "2.0.6",
|
||||||
"ember-simple-auth": "1.4.0",
|
"ember-simple-auth": "1.4.0",
|
||||||
"ember-sinon": "1.0.1",
|
"ember-sinon": "1.0.1",
|
||||||
"ember-sortable": "1.9.1",
|
|
||||||
"ember-source": "2.16.0",
|
"ember-source": "2.16.0",
|
||||||
"ember-test-selectors": "0.3.7",
|
"ember-test-selectors": "0.3.7",
|
||||||
"ember-truth-helpers": "1.3.0",
|
"ember-truth-helpers": "2.0.0",
|
||||||
"ember-wormhole": "0.5.2",
|
"ember-wormhole": "0.5.2",
|
||||||
"emberx-file-input": "1.1.2",
|
"emberx-file-input": "1.1.2",
|
||||||
"eslint-plugin-ember": "4.5.0",
|
"eslint-plugin-ember": "4.5.0",
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
/* jshint expr:true */
|
|
||||||
import $ from 'jquery';
|
|
||||||
import NavItem from 'ghost-admin/models/navigation-item';
|
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
|
||||||
import wait from 'ember-test-helpers/wait';
|
|
||||||
import {describe, it} from 'mocha';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
import {run} from '@ember/runloop';
|
|
||||||
import {setupComponentTest} from 'ember-mocha';
|
|
||||||
|
|
||||||
describe('Integration: Component: gh-navigation', function () {
|
|
||||||
setupComponentTest('gh-navigation', {
|
|
||||||
integration: true
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders', function () {
|
|
||||||
this.render(hbs`{{#gh-navigation}}<div class="js-gh-blognav"><div class="gh-blognav-item"></div></div>{{/gh-navigation}}`);
|
|
||||||
expect(this.$('section.gh-view')).to.have.length(1);
|
|
||||||
expect(this.$('.ui-sortable')).to.have.length(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('triggers reorder action', function () {
|
|
||||||
let navItems = [];
|
|
||||||
let expectedOldIndex = -1;
|
|
||||||
let expectedNewIndex = -1;
|
|
||||||
|
|
||||||
navItems.pushObject(NavItem.create({label: 'First', url: '/first'}));
|
|
||||||
navItems.pushObject(NavItem.create({label: 'Second', url: '/second'}));
|
|
||||||
navItems.pushObject(NavItem.create({label: 'Third', url: '/third'}));
|
|
||||||
navItems.pushObject(NavItem.create({label: '', url: '', last: true}));
|
|
||||||
this.set('navigationItems', navItems);
|
|
||||||
this.set('blogUrl', 'http://localhost:2368');
|
|
||||||
|
|
||||||
this.on('moveItem', (oldIndex, newIndex) => {
|
|
||||||
expect(oldIndex).to.equal(expectedOldIndex);
|
|
||||||
expect(newIndex).to.equal(expectedNewIndex);
|
|
||||||
});
|
|
||||||
|
|
||||||
run(() => {
|
|
||||||
this.render(hbs `
|
|
||||||
{{#gh-navigation moveItem="moveItem"}}
|
|
||||||
<form id="settings-navigation" class="gh-blognav js-gh-blognav" novalidate="novalidate">
|
|
||||||
{{#each navigationItems as |navItem|}}
|
|
||||||
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addItem" deleteItem="deleteItem" updateUrl="updateUrl"}}
|
|
||||||
{{/each}}
|
|
||||||
</form>
|
|
||||||
{{/gh-navigation}}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// check it renders the nav item rows
|
|
||||||
expect(this.$('.gh-blognav-item')).to.have.length(4);
|
|
||||||
|
|
||||||
// move second item up one
|
|
||||||
expectedOldIndex = 1;
|
|
||||||
expectedNewIndex = 0;
|
|
||||||
run(() => {
|
|
||||||
$(this.$('.gh-blognav-item')[1]).simulateDragSortable({
|
|
||||||
move: -1,
|
|
||||||
handle: '.gh-blognav-grab'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
wait().then(() => {
|
|
||||||
// move second item down one
|
|
||||||
expectedOldIndex = 1;
|
|
||||||
expectedNewIndex = 2;
|
|
||||||
run(() => {
|
|
||||||
$(this.$('.gh-blognav-item')[1]).simulateDragSortable({
|
|
||||||
move: 1,
|
|
||||||
handle: '.gh-blognav-grab'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,205 @@
|
|||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
import mockPosts from '../../../mirage/config/posts';
|
||||||
|
import mockTags from '../../../mirage/config/themes';
|
||||||
|
import wait from 'ember-test-helpers/wait';
|
||||||
|
import {click, findAll} from 'ember-native-dom-helpers';
|
||||||
|
import {clickTrigger, selectChoose, typeInSearch} from '../../../tests/helpers/ember-power-select';
|
||||||
|
import {describe, it} from 'mocha';
|
||||||
|
import {expect} from 'chai';
|
||||||
|
import {run} from '@ember/runloop';
|
||||||
|
import {setupComponentTest} from 'ember-mocha';
|
||||||
|
import {startMirage} from 'ghost-admin/initializers/ember-cli-mirage';
|
||||||
|
|
||||||
|
// NOTE: although Mirage has posts<->tags relationship and can respond
|
||||||
|
// to :post-id/?include=tags all ordering information is lost so we
|
||||||
|
// need to build the tags array manually
|
||||||
|
const assignPostWithTags = function postWithTags(context, ...slugs) {
|
||||||
|
context.get('store').findRecord('post', 1).then((post) => {
|
||||||
|
context.get('store').findAll('tag').then((tags) => {
|
||||||
|
slugs.forEach((slug) => {
|
||||||
|
post.get('tags').pushObject(tags.findBy('slug', slug));
|
||||||
|
});
|
||||||
|
|
||||||
|
context.set('post', post);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Integration: Component: gh-psm-tags-input', function() {
|
||||||
|
setupComponentTest('gh-psm-tags-input', {
|
||||||
|
integration: true
|
||||||
|
});
|
||||||
|
|
||||||
|
let server;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
server = startMirage();
|
||||||
|
server.create('user');
|
||||||
|
|
||||||
|
mockPosts(server);
|
||||||
|
mockTags(server);
|
||||||
|
|
||||||
|
server.create('post');
|
||||||
|
server.create('tag', {name: 'Tag One', slug: 'one'});
|
||||||
|
server.create('tag', {name: 'Tag Two', slug: 'two'});
|
||||||
|
server.create('tag', {name: 'Tag Three', slug: 'three'});
|
||||||
|
server.create('tag', {name: '#Internal Tag', visibility: 'internal', slug: 'internal'});
|
||||||
|
|
||||||
|
this.inject.service('store');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
server.shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows selected tags on render', async function () {
|
||||||
|
run(() => {
|
||||||
|
assignPostWithTags(this, 'one', 'three');
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
|
||||||
|
let selected = findAll('.tag-token');
|
||||||
|
expect(selected.length).to.equal(2);
|
||||||
|
expect(selected[0].textContent).to.have.string('Tag One');
|
||||||
|
expect(selected[1].textContent).to.have.string('Tag Three');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exposes all tags as options', async function () {
|
||||||
|
run(() => {
|
||||||
|
this.set('post', this.get('store').findRecord('post', 1));
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
await clickTrigger();
|
||||||
|
|
||||||
|
let options = findAll('.ember-power-select-option');
|
||||||
|
expect(options.length).to.equal(4);
|
||||||
|
expect(options[0].textContent).to.have.string('Tag One');
|
||||||
|
expect(options[1].textContent).to.have.string('Tag Two');
|
||||||
|
expect(options[2].textContent).to.have.string('Tag Three');
|
||||||
|
expect(options[3].textContent).to.have.string('#Internal Tag');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches options on lowercase tag names', async function () {
|
||||||
|
run(() => {
|
||||||
|
this.set('post', this.get('store').findRecord('post', 1));
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
await clickTrigger();
|
||||||
|
await typeInSearch('two');
|
||||||
|
|
||||||
|
let options = findAll('.ember-power-select-option');
|
||||||
|
expect(options.length).to.equal(2);
|
||||||
|
expect(options[0].textContent).to.have.string('Add "two"...');
|
||||||
|
expect(options[1].textContent).to.have.string('Tag Two');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides create option on exact matches', async function () {
|
||||||
|
run(() => {
|
||||||
|
this.set('post', this.get('store').findRecord('post', 1));
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
await clickTrigger();
|
||||||
|
await typeInSearch('Tag Two');
|
||||||
|
|
||||||
|
let options = findAll('.ember-power-select-option');
|
||||||
|
expect(options.length).to.equal(1);
|
||||||
|
expect(options[0].textContent).to.have.string('Tag Two');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('primary tags', function () {
|
||||||
|
it('adds primary tag class to first tag', async function () {
|
||||||
|
run(() => {
|
||||||
|
assignPostWithTags(this, 'one', 'three');
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
|
||||||
|
let selected = findAll('.tag-token');
|
||||||
|
expect(selected.length).to.equal(2);
|
||||||
|
expect(selected[0].classList.contains('tag-token--primary')).to.be.true;
|
||||||
|
expect(selected[1].classList.contains('tag-token--primary')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doesn\'t add primary tag class if first tag is internal', async function () {
|
||||||
|
run(() => {
|
||||||
|
assignPostWithTags(this, 'internal', 'two');
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
|
||||||
|
let selected = findAll('.tag-token');
|
||||||
|
expect(selected.length).to.equal(2);
|
||||||
|
expect(selected[0].classList.contains('tag-token--primary')).to.be.false;
|
||||||
|
expect(selected[1].classList.contains('tag-token--primary')).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateTags', function () {
|
||||||
|
it('modifies post.tags', async function () {
|
||||||
|
run(() => {
|
||||||
|
assignPostWithTags(this, 'internal', 'two');
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
await selectChoose('.ember-power-select-trigger', 'Tag One');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
this.get('post.tags').mapBy('name').join(',')
|
||||||
|
).to.equal('#Internal Tag,Tag Two,Tag One');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('destroys new tag records when not selected', async function () {
|
||||||
|
run(() => {
|
||||||
|
assignPostWithTags(this, 'internal', 'two');
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
await clickTrigger();
|
||||||
|
await typeInSearch('New');
|
||||||
|
await selectChoose('.ember-power-select-trigger', 'Add "New"...');
|
||||||
|
|
||||||
|
let tags = await this.get('store').peekAll('tag');
|
||||||
|
expect(tags.get('length')).to.equal(5);
|
||||||
|
|
||||||
|
let removeBtns = findAll('.ember-power-select-multiple-remove-btn');
|
||||||
|
await click(removeBtns[removeBtns.length - 1]);
|
||||||
|
|
||||||
|
tags = await this.get('store').peekAll('tag');
|
||||||
|
expect(tags.get('length')).to.equal(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createTag', function () {
|
||||||
|
it('creates new records', async function () {
|
||||||
|
run(() => {
|
||||||
|
assignPostWithTags(this, 'internal', 'two');
|
||||||
|
});
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
await this.render(hbs`{{gh-psm-tags-input post=post}}`);
|
||||||
|
await clickTrigger();
|
||||||
|
await typeInSearch('New One');
|
||||||
|
await selectChoose('.ember-power-select-trigger', 'Add "New One"...');
|
||||||
|
await typeInSearch('New Two');
|
||||||
|
await selectChoose('.ember-power-select-trigger', 'Add "New Two"...');
|
||||||
|
|
||||||
|
let tags = await this.get('store').peekAll('tag');
|
||||||
|
expect(tags.get('length')).to.equal(6);
|
||||||
|
|
||||||
|
expect(tags.findBy('name', 'New One').get('isNew')).to.be.true;
|
||||||
|
expect(tags.findBy('name', 'New Two').get('isNew')).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,38 +0,0 @@
|
|||||||
/* jshint expr:true */
|
|
||||||
import {describe, it} from 'mocha';
|
|
||||||
import {A as emberA} from '@ember/array';
|
|
||||||
import {expect} from 'chai';
|
|
||||||
import {run} from '@ember/runloop';
|
|
||||||
import {setupComponentTest} from 'ember-mocha';
|
|
||||||
|
|
||||||
describe('Unit: Component: gh-selectize', function () {
|
|
||||||
setupComponentTest('gh-selectize', {
|
|
||||||
// Specify the other units that are required for this test
|
|
||||||
// needs: ['component:foo', 'helper:bar'],
|
|
||||||
unit: true
|
|
||||||
});
|
|
||||||
|
|
||||||
it('re-orders selection when selectize order is changed', function () {
|
|
||||||
let component = this.subject();
|
|
||||||
|
|
||||||
let item1 = {id: '1', name: 'item 1'};
|
|
||||||
let item2 = {id: '2', name: 'item 2'};
|
|
||||||
let item3 = {id: '3', name: 'item 3'};
|
|
||||||
|
|
||||||
run(() => {
|
|
||||||
component.set('content', emberA([item1, item2, item3]));
|
|
||||||
component.set('selection', emberA([item2, item3]));
|
|
||||||
component.set('multiple', true);
|
|
||||||
component.set('optionValuePath', 'content.id');
|
|
||||||
component.set('optionLabelPath', 'content.name');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
|
|
||||||
run(() => {
|
|
||||||
component._selectize.setValue(['3', '2']);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(component.get('selection').toArray(), 'component selection').to.deep.equal([item3, item2]);
|
|
||||||
});
|
|
||||||
});
|
|
@ -154,21 +154,6 @@ describe('Unit: Controller: settings/design', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('action - reorderItems: updates navigationItems list', function () {
|
|
||||||
let ctrl = this.subject();
|
|
||||||
let navItems = [
|
|
||||||
NavItem.create({label: 'First', url: '/first'}),
|
|
||||||
NavItem.create({label: 'Second', url: '/second', last: true})
|
|
||||||
];
|
|
||||||
|
|
||||||
run(() => {
|
|
||||||
ctrl.set('model', EmberObject.create({navigation: navItems}));
|
|
||||||
expect(ctrl.get('model.navigation').mapBy('label')).to.deep.equal(['First', 'Second']);
|
|
||||||
ctrl.send('reorderItems', navItems.reverseObjects());
|
|
||||||
expect(ctrl.get('model.navigation').mapBy('label')).to.deep.equal(['Second', 'First']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('action - updateUrl: updates URL on navigationItem', function () {
|
it('action - updateUrl: updates URL on navigationItem', function () {
|
||||||
let ctrl = this.subject();
|
let ctrl = this.subject();
|
||||||
let navItems = [
|
let navItems = [
|
||||||
|
@ -3114,7 +3114,7 @@ ember-cli-babel@6.8.2, ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.10, e
|
|||||||
clone "^2.0.0"
|
clone "^2.0.0"
|
||||||
ember-cli-version-checker "^2.0.0"
|
ember-cli-version-checker "^2.0.0"
|
||||||
|
|
||||||
ember-cli-babel@^5.0.0, ember-cli-babel@^5.1.5, ember-cli-babel@^5.1.6, ember-cli-babel@^5.1.7:
|
ember-cli-babel@^5.0.0, ember-cli-babel@^5.1.5, ember-cli-babel@^5.1.6, ember-cli-babel@^5.1.7, ember-cli-babel@^5.2.4:
|
||||||
version "5.2.4"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-5.2.4.tgz#5ce4f46b08ed6f6d21e878619fb689719d6e8e13"
|
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-5.2.4.tgz#5ce4f46b08ed6f6d21e878619fb689719d6e8e13"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3393,15 +3393,6 @@ ember-cli-pretender@1.0.1:
|
|||||||
pretender "^1.4.2"
|
pretender "^1.4.2"
|
||||||
resolve "^1.2.0"
|
resolve "^1.2.0"
|
||||||
|
|
||||||
ember-cli-selectize@0.5.12:
|
|
||||||
version "0.5.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/ember-cli-selectize/-/ember-cli-selectize-0.5.12.tgz#38af65b12d01d8ff20ba252235105bed10b7111a"
|
|
||||||
dependencies:
|
|
||||||
ember-cli-babel "^5.1.6"
|
|
||||||
ember-cli-import-polyfill "^0.2.0"
|
|
||||||
ember-getowner-polyfill "^1.1.1"
|
|
||||||
ember-new-computed "^1.0.0"
|
|
||||||
|
|
||||||
ember-cli-shims@1.1.0:
|
ember-cli-shims@1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-cli-shims/-/ember-cli-shims-1.1.0.tgz#0e3b8a048be865b4f81cc81d397ff1eeb13f75b6"
|
resolved "https://registry.yarnpkg.com/ember-cli-shims/-/ember-cli-shims-1.1.0.tgz#0e3b8a048be865b4f81cc81d397ff1eeb13f75b6"
|
||||||
@ -3634,6 +3625,12 @@ ember-data@2.16.2:
|
|||||||
silent-error "^1.0.0"
|
silent-error "^1.0.0"
|
||||||
testem "^1.15.0"
|
testem "^1.15.0"
|
||||||
|
|
||||||
|
"ember-drag-drop@https://github.com/kevinansfield/ember-drag-drop.git#node-4":
|
||||||
|
version "0.4.6"
|
||||||
|
resolved "https://github.com/kevinansfield/ember-drag-drop.git#28d063ef5ccf613c5db556e4660db50b4557b68d"
|
||||||
|
dependencies:
|
||||||
|
ember-cli-babel "^5.2.4"
|
||||||
|
|
||||||
ember-element-resize-detector@0.1.5:
|
ember-element-resize-detector@0.1.5:
|
||||||
version "0.1.5"
|
version "0.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/ember-element-resize-detector/-/ember-element-resize-detector-0.1.5.tgz#4b1cdd63c0d42c42c78f0ac0cc8cff3e1e095611"
|
resolved "https://registry.yarnpkg.com/ember-element-resize-detector/-/ember-element-resize-detector-0.1.5.tgz#4b1cdd63c0d42c42c78f0ac0cc8cff3e1e095611"
|
||||||
@ -3758,7 +3755,7 @@ ember-inline-svg@0.1.11:
|
|||||||
svgo "^0.6.3"
|
svgo "^0.6.3"
|
||||||
walk-sync "^0.3.1"
|
walk-sync "^0.3.1"
|
||||||
|
|
||||||
ember-invoke-action@1.4.0, ember-invoke-action@^1.4.0:
|
ember-invoke-action@1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-invoke-action/-/ember-invoke-action-1.4.0.tgz#2899854bd755f9331ca86c902bf6d4dbf8bdfcb3"
|
resolved "https://registry.yarnpkg.com/ember-invoke-action/-/ember-invoke-action-1.4.0.tgz#2899854bd755f9331ca86c902bf6d4dbf8bdfcb3"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3848,12 +3845,6 @@ ember-native-dom-helpers@0.5.4, ember-native-dom-helpers@^0.5.3:
|
|||||||
broccoli-funnel "^1.1.0"
|
broccoli-funnel "^1.1.0"
|
||||||
ember-cli-babel "^6.6.0"
|
ember-cli-babel "^6.6.0"
|
||||||
|
|
||||||
ember-new-computed@^1.0.0, ember-new-computed@^1.0.2:
|
|
||||||
version "1.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/ember-new-computed/-/ember-new-computed-1.0.3.tgz#592af8a778e0260ce7e812687c3aedface1622bf"
|
|
||||||
dependencies:
|
|
||||||
ember-cli-babel "^5.1.5"
|
|
||||||
|
|
||||||
ember-one-way-controls@2.0.1:
|
ember-one-way-controls@2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ember-one-way-controls/-/ember-one-way-controls-2.0.1.tgz#45bd9367554f69e10fa37dfac8f1a6c72360a7f1"
|
resolved "https://registry.yarnpkg.com/ember-one-way-controls/-/ember-one-way-controls-2.0.1.tgz#45bd9367554f69e10fa37dfac8f1a6c72360a7f1"
|
||||||
@ -3884,16 +3875,16 @@ ember-power-datepicker@0.4.0:
|
|||||||
ember-cli-htmlbars "^2.0.1"
|
ember-cli-htmlbars "^2.0.1"
|
||||||
ember-power-calendar "^0.5.0"
|
ember-power-calendar "^0.5.0"
|
||||||
|
|
||||||
ember-power-select@1.9.9:
|
ember-power-select@1.9.11:
|
||||||
version "1.9.9"
|
version "1.9.11"
|
||||||
resolved "https://registry.yarnpkg.com/ember-power-select/-/ember-power-select-1.9.9.tgz#24b733f5be603a434dffdb57dffe702759448ca5"
|
resolved "https://registry.yarnpkg.com/ember-power-select/-/ember-power-select-1.9.11.tgz#d7aa04e4b6baa93adc9a7b8a8ae989e7a3751eb1"
|
||||||
dependencies:
|
dependencies:
|
||||||
ember-basic-dropdown "^0.33.1"
|
ember-basic-dropdown "^0.33.1"
|
||||||
ember-cli-babel "^6.6.0"
|
ember-cli-babel "^6.8.2"
|
||||||
ember-cli-htmlbars "^2.0.1"
|
ember-cli-htmlbars "^2.0.1"
|
||||||
ember-concurrency "^0.8.1"
|
ember-concurrency "^0.8.1"
|
||||||
ember-text-measurer "^0.3.3"
|
ember-text-measurer "^0.3.3"
|
||||||
ember-truth-helpers "^1.3.0"
|
ember-truth-helpers "^2.0.0"
|
||||||
|
|
||||||
ember-resolver@4.5.0:
|
ember-resolver@4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
@ -3972,15 +3963,6 @@ ember-sinon@1.0.1:
|
|||||||
ember-cli-babel "^6.3.0"
|
ember-cli-babel "^6.3.0"
|
||||||
sinon "^3.2.1"
|
sinon "^3.2.1"
|
||||||
|
|
||||||
ember-sortable@1.9.1:
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ember-sortable/-/ember-sortable-1.9.1.tgz#e5053866a7547c1ce369700a43cb570f8ac84519"
|
|
||||||
dependencies:
|
|
||||||
ember-cli-babel "^5.1.7"
|
|
||||||
ember-cli-htmlbars "^1.0.10"
|
|
||||||
ember-invoke-action "^1.4.0"
|
|
||||||
ember-new-computed "^1.0.2"
|
|
||||||
|
|
||||||
ember-source@2.16.0:
|
ember-source@2.16.0:
|
||||||
version "2.16.0"
|
version "2.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-source/-/ember-source-2.16.0.tgz#2becd7966278fe453046b91178ede665c2cf241a"
|
resolved "https://registry.yarnpkg.com/ember-source/-/ember-source-2.16.0.tgz#2becd7966278fe453046b91178ede665c2cf241a"
|
||||||
@ -4031,11 +4013,11 @@ ember-truth-helpers@1.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ember-cli-babel "^5.1.5"
|
ember-cli-babel "^5.1.5"
|
||||||
|
|
||||||
ember-truth-helpers@1.3.0, ember-truth-helpers@^1.3.0:
|
ember-truth-helpers@2.0.0, ember-truth-helpers@^2.0.0:
|
||||||
version "1.3.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-truth-helpers/-/ember-truth-helpers-1.3.0.tgz#6ed9f83ce9a49f52bb416d55e227426339a64c60"
|
resolved "https://registry.yarnpkg.com/ember-truth-helpers/-/ember-truth-helpers-2.0.0.tgz#f3e2eef667859197f1328bb4f83b0b35b661c1ac"
|
||||||
dependencies:
|
dependencies:
|
||||||
ember-cli-babel "^5.1.6"
|
ember-cli-babel "^6.8.2"
|
||||||
|
|
||||||
ember-try-config@^2.0.1:
|
ember-try-config@^2.0.1:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user