mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +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 SortableItem from 'ember-sortable/mixins/sortable-item';
|
||||
import ValidationState from 'ghost-admin/mixins/validation-state';
|
||||
import {alias, readOnly} from '@ember/object/computed';
|
||||
import {computed} from '@ember/object';
|
||||
import {run} from '@ember/runloop';
|
||||
|
||||
export default Component.extend(ValidationState, SortableItem, {
|
||||
export default Component.extend(ValidationState, {
|
||||
classNames: 'gh-blognav-item',
|
||||
classNameBindings: ['errorClass', 'navItem.isNew::gh-blognav-item--sortable'],
|
||||
|
||||
new: false,
|
||||
handle: '.gh-blognav-grab',
|
||||
|
||||
model: alias('navItem'),
|
||||
errors: readOnly('navItem.errors'),
|
||||
|
@ -5,7 +5,6 @@ import formatMarkdown from 'ghost-admin/utils/format-markdown';
|
||||
import moment from 'moment';
|
||||
import {alias, or} from '@ember/object/computed';
|
||||
import {computed} from '@ember/object';
|
||||
import {guidFor} from '@ember/object/internals';
|
||||
import {htmlSafe} from '@ember/string';
|
||||
import {run} from '@ember/runloop';
|
||||
import {inject as service} from '@ember/service';
|
||||
@ -138,13 +137,6 @@ export default Component.extend(SettingsMenuMixin, {
|
||||
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) {
|
||||
// TODO: remove null check once ValidationEngine has been removed
|
||||
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() {
|
||||
if (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);
|
||||
},
|
||||
|
||||
reorderItems(navItems) {
|
||||
this.set('model.navigation', navItems);
|
||||
},
|
||||
|
||||
updateUrl(url, navItem) {
|
||||
if (!navItem) {
|
||||
return;
|
||||
|
@ -1,10 +1,7 @@
|
||||
/* eslint-disable camelcase */
|
||||
import Model from 'ember-data/model';
|
||||
import ValidationEngine from 'ghost-admin/mixins/validation-engine';
|
||||
import attr from 'ember-data/attr';
|
||||
import {computed} from '@ember/object';
|
||||
import {equal} from '@ember/object/computed';
|
||||
import {guidFor} from '@ember/object/internals';
|
||||
import {observer} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
@ -30,13 +27,6 @@ export default Model.extend(ValidationEngine, {
|
||||
|
||||
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() {
|
||||
let internalRegex = /^#.?/;
|
||||
this.set('visibility', internalRegex.test(this.get('name')) ? 'internal' : 'public');
|
||||
|
@ -20,7 +20,7 @@ function K() {
|
||||
|
||||
let shortcuts = {};
|
||||
|
||||
shortcuts.esc = {action: 'closeMenus', scope: 'all'};
|
||||
shortcuts.esc = {action: 'closeMenus', scope: 'default'};
|
||||
shortcuts[`${ctrlOrCmd}+s`] = {action: 'save', scope: 'all'};
|
||||
|
||||
export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
|
||||
|
@ -28,7 +28,6 @@
|
||||
@import "components/pagination.css";
|
||||
@import "components/badges.css";
|
||||
@import "components/settings-menu.css";
|
||||
@import "components/selectize.css";
|
||||
@import "components/power-select.css";
|
||||
@import "components/power-calendar.css";
|
||||
@import "components/publishmenu.css";
|
||||
@ -82,7 +81,6 @@ hr {
|
||||
input,
|
||||
.gh-input,
|
||||
.gh-select,
|
||||
.selectize-input,
|
||||
.gh-select select {
|
||||
color: var(--darkgrey);
|
||||
border-color: color(var(--lightgrey));
|
||||
@ -189,21 +187,6 @@ input,
|
||||
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 {
|
||||
color: var(--darkgrey);
|
||||
background: var(--lightgrey);
|
||||
|
@ -28,7 +28,6 @@
|
||||
@import "components/pagination.css";
|
||||
@import "components/badges.css";
|
||||
@import "components/settings-menu.css";
|
||||
@import "components/selectize.css";
|
||||
@import "components/power-select.css";
|
||||
@import "components/power-calendar.css";
|
||||
@import "components/publishmenu.css";
|
||||
|
@ -151,11 +151,3 @@
|
||||
.closed > .dropdown-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Selectize
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.selectize-dropdown {
|
||||
z-index: 200;
|
||||
}
|
||||
|
@ -152,3 +152,41 @@
|
||||
.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%));
|
||||
}
|
||||
|
||||
.settings-menu-content .selectize-input {
|
||||
padding: 7px 12px;
|
||||
}
|
||||
|
||||
.post-setting-custom-excerpt {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.35em;
|
||||
|
@ -319,12 +319,6 @@ body > .ember-view:not(.default-liquid-destination) {
|
||||
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 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
@ -67,17 +67,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tag-input">Tags</label>
|
||||
{{gh-selectize
|
||||
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"}}
|
||||
{{gh-psm-tags-input post=model triggerId="tag-input"}}
|
||||
</div>
|
||||
|
||||
{{#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-blognav-container">
|
||||
<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|}}
|
||||
{{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}}
|
||||
{{/sortable-group}}
|
||||
{{/sortable-objects}}
|
||||
{{gh-navitem navItem=newNavItem baseUrl=blogUrl addItem="addNavItem" updateUrl="updateUrl"}}
|
||||
</form>
|
||||
</div>
|
||||
|
@ -4,14 +4,10 @@
|
||||
"devicejs": "0.2.7",
|
||||
"Faker": "3.1.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",
|
||||
"normalize.css": "3.0.3",
|
||||
"pretender": "1.1.0",
|
||||
"rangyinputs": "1.2.0",
|
||||
"selectize": "~0.12.1",
|
||||
"validator-js": "3.39.0"
|
||||
}
|
||||
}
|
||||
|
@ -157,9 +157,6 @@ module.exports = function (defaults) {
|
||||
import: ['simplemde.js', 'simplemde.css']
|
||||
}
|
||||
},
|
||||
'ember-cli-selectize': {
|
||||
theme: false
|
||||
},
|
||||
svg: {
|
||||
paths: [
|
||||
'public/assets/icons'
|
||||
@ -185,20 +182,7 @@ module.exports = function (defaults) {
|
||||
app.import('bower_components/rangyinputs/rangyinputs-jquery-src.js');
|
||||
app.import('bower_components/keymaster/keymaster.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/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
|
||||
// that tests don't break when running via http://localhost:4200/tests
|
||||
|
@ -42,6 +42,7 @@
|
||||
"csscomb": "4.2.0",
|
||||
"cssnano": "4.0.0-rc.2",
|
||||
"ember-ajax": "2.5.6",
|
||||
"ember-assign-helper": "0.1.2",
|
||||
"ember-browserify": "1.2.0",
|
||||
"ember-cli": "2.16.2",
|
||||
"ember-cli-active-link-wrapper": "0.3.2",
|
||||
@ -60,7 +61,6 @@
|
||||
"ember-cli-node-assets": "0.2.2",
|
||||
"ember-cli-postcss": "3.5.2",
|
||||
"ember-cli-pretender": "1.0.1",
|
||||
"ember-cli-selectize": "0.5.12",
|
||||
"ember-cli-shims": "1.1.0",
|
||||
"ember-cli-string-helpers": "1.5.0",
|
||||
"ember-cli-test-loader": "2.2.0",
|
||||
@ -69,6 +69,7 @@
|
||||
"ember-concurrency": "0.8.10",
|
||||
"ember-data": "2.16.2",
|
||||
"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-export-application-global": "2.0.0",
|
||||
"ember-fetch": "3.4.0",
|
||||
@ -82,16 +83,15 @@
|
||||
"ember-native-dom-helpers": "0.5.4",
|
||||
"ember-one-way-controls": "2.0.1",
|
||||
"ember-power-datepicker": "0.4.0",
|
||||
"ember-power-select": "1.9.9",
|
||||
"ember-power-select": "1.9.11",
|
||||
"ember-resolver": "4.5.0",
|
||||
"ember-responsive": "2.0.5",
|
||||
"ember-route-action-helper": "2.0.6",
|
||||
"ember-simple-auth": "1.4.0",
|
||||
"ember-sinon": "1.0.1",
|
||||
"ember-sortable": "1.9.1",
|
||||
"ember-source": "2.16.0",
|
||||
"ember-test-selectors": "0.3.7",
|
||||
"ember-truth-helpers": "1.3.0",
|
||||
"ember-truth-helpers": "2.0.0",
|
||||
"ember-wormhole": "0.5.2",
|
||||
"emberx-file-input": "1.1.2",
|
||||
"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 () {
|
||||
let ctrl = this.subject();
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-5.2.4.tgz#5ce4f46b08ed6f6d21e878619fb689719d6e8e13"
|
||||
dependencies:
|
||||
@ -3393,15 +3393,6 @@ ember-cli-pretender@1.0.1:
|
||||
pretender "^1.4.2"
|
||||
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:
|
||||
version "1.1.0"
|
||||
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"
|
||||
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:
|
||||
version "0.1.5"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/ember-invoke-action/-/ember-invoke-action-1.4.0.tgz#2899854bd755f9331ca86c902bf6d4dbf8bdfcb3"
|
||||
dependencies:
|
||||
@ -3848,12 +3845,6 @@ ember-native-dom-helpers@0.5.4, ember-native-dom-helpers@^0.5.3:
|
||||
broccoli-funnel "^1.1.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:
|
||||
version "2.0.1"
|
||||
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-power-calendar "^0.5.0"
|
||||
|
||||
ember-power-select@1.9.9:
|
||||
version "1.9.9"
|
||||
resolved "https://registry.yarnpkg.com/ember-power-select/-/ember-power-select-1.9.9.tgz#24b733f5be603a434dffdb57dffe702759448ca5"
|
||||
ember-power-select@1.9.11:
|
||||
version "1.9.11"
|
||||
resolved "https://registry.yarnpkg.com/ember-power-select/-/ember-power-select-1.9.11.tgz#d7aa04e4b6baa93adc9a7b8a8ae989e7a3751eb1"
|
||||
dependencies:
|
||||
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-concurrency "^0.8.1"
|
||||
ember-text-measurer "^0.3.3"
|
||||
ember-truth-helpers "^1.3.0"
|
||||
ember-truth-helpers "^2.0.0"
|
||||
|
||||
ember-resolver@4.5.0:
|
||||
version "4.5.0"
|
||||
@ -3972,15 +3963,6 @@ ember-sinon@1.0.1:
|
||||
ember-cli-babel "^6.3.0"
|
||||
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:
|
||||
version "2.16.0"
|
||||
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:
|
||||
ember-cli-babel "^5.1.5"
|
||||
|
||||
ember-truth-helpers@1.3.0, ember-truth-helpers@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-truth-helpers/-/ember-truth-helpers-1.3.0.tgz#6ed9f83ce9a49f52bb416d55e227426339a64c60"
|
||||
ember-truth-helpers@2.0.0, ember-truth-helpers@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-truth-helpers/-/ember-truth-helpers-2.0.0.tgz#f3e2eef667859197f1328bb4f83b0b35b661c1ac"
|
||||
dependencies:
|
||||
ember-cli-babel "^5.1.6"
|
||||
ember-cli-babel "^6.8.2"
|
||||
|
||||
ember-try-config@^2.0.1:
|
||||
version "2.1.0"
|
||||
|
Loading…
Reference in New Issue
Block a user