mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Merge pull request #5553 from acburdine/tag-component
Re-implement tag-editing component
This commit is contained in:
commit
7090b4c4e5
14 changed files with 868 additions and 486 deletions
|
@ -59,6 +59,7 @@ app.import('bower_components/codemirror/mode/javascript/javascript.js');
|
||||||
app.import('bower_components/xregexp/xregexp-all.js');
|
app.import('bower_components/xregexp/xregexp-all.js');
|
||||||
app.import('bower_components/password-generator/lib/password-generator.js');
|
app.import('bower_components/password-generator/lib/password-generator.js');
|
||||||
app.import('bower_components/blueimp-md5/js/md5.js');
|
app.import('bower_components/blueimp-md5/js/md5.js');
|
||||||
|
app.import('bower_components/typeahead.js/dist/typeahead.bundle.js');
|
||||||
|
|
||||||
// 'dem Styles
|
// 'dem Styles
|
||||||
app.import('bower_components/codemirror/lib/codemirror.css');
|
app.import('bower_components/codemirror/lib/codemirror.css');
|
||||||
|
|
|
@ -1,149 +0,0 @@
|
||||||
import Ember from 'ember';
|
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
|
||||||
tagName: 'section',
|
|
||||||
elementId: 'entry-tags',
|
|
||||||
classNames: 'publish-bar-inner',
|
|
||||||
classNameBindings: ['hasFocus:focused'],
|
|
||||||
|
|
||||||
hasFocus: false,
|
|
||||||
|
|
||||||
keys: {
|
|
||||||
BACKSPACE: 8,
|
|
||||||
TAB: 9,
|
|
||||||
ENTER: 13,
|
|
||||||
ESCAPE: 27,
|
|
||||||
UP: 38,
|
|
||||||
DOWN: 40,
|
|
||||||
NUMPAD_ENTER: 108
|
|
||||||
},
|
|
||||||
|
|
||||||
didInsertElement: function () {
|
|
||||||
// this.get('controller').send('loadAllTags');
|
|
||||||
},
|
|
||||||
|
|
||||||
willDestroyElement: function () {
|
|
||||||
// this.get('controller').send('reset');
|
|
||||||
},
|
|
||||||
|
|
||||||
overlayStyles: Ember.computed('hasFocus', 'controller.suggestions.length', function () {
|
|
||||||
var styles = [],
|
|
||||||
leftPos;
|
|
||||||
|
|
||||||
if (this.get('hasFocus') && this.get('controller.suggestions.length')) {
|
|
||||||
leftPos = this.$().find('#tags').position().left;
|
|
||||||
styles.push('display: block');
|
|
||||||
styles.push('left: ' + leftPos + 'px');
|
|
||||||
} else {
|
|
||||||
styles.push('display: none');
|
|
||||||
styles.push('left', 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles.join(';').htmlSafe();
|
|
||||||
}),
|
|
||||||
|
|
||||||
// replace these views with components, or whatever works
|
|
||||||
// during the reimplementation of this component.
|
|
||||||
|
|
||||||
// tagInputView: Ember.TextField.extend({
|
|
||||||
// focusIn: function () {
|
|
||||||
// this.get('parentView').set('hasFocus', true);
|
|
||||||
// },
|
|
||||||
|
|
||||||
// focusOut: function () {
|
|
||||||
// this.get('parentView').set('hasFocus', false);
|
|
||||||
// },
|
|
||||||
|
|
||||||
// keyPress: function (event) {
|
|
||||||
// // listen to keypress event to handle comma key on international keyboard
|
|
||||||
// var controller = this.get('parentView.controller'),
|
|
||||||
// isComma = ','.localeCompare(String.fromCharCode(event.keyCode || event.charCode)) === 0;
|
|
||||||
|
|
||||||
// // use localeCompare in case of international keyboard layout
|
|
||||||
// if (isComma) {
|
|
||||||
// event.preventDefault();
|
|
||||||
|
|
||||||
// if (controller.get('selectedSuggestion')) {
|
|
||||||
// controller.send('addSelectedSuggestion');
|
|
||||||
// } else {
|
|
||||||
// controller.send('addNewTag');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
|
|
||||||
// keyDown: function (event) {
|
|
||||||
// var controller = this.get('parentView.controller'),
|
|
||||||
// keys = this.get('parentView.keys'),
|
|
||||||
// hasValue;
|
|
||||||
|
|
||||||
// switch (event.keyCode) {
|
|
||||||
// case keys.UP:
|
|
||||||
// event.preventDefault();
|
|
||||||
// controller.send('selectPreviousSuggestion');
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case keys.DOWN:
|
|
||||||
// event.preventDefault();
|
|
||||||
// controller.send('selectNextSuggestion');
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case keys.TAB:
|
|
||||||
// case keys.ENTER:
|
|
||||||
// case keys.NUMPAD_ENTER:
|
|
||||||
// if (controller.get('selectedSuggestion')) {
|
|
||||||
// event.preventDefault();
|
|
||||||
// controller.send('addSelectedSuggestion');
|
|
||||||
// } else {
|
|
||||||
// // allow user to tab out of field if input is empty
|
|
||||||
// hasValue = !Ember.isEmpty(this.get('value'));
|
|
||||||
// if (hasValue || event.keyCode !== keys.TAB) {
|
|
||||||
// event.preventDefault();
|
|
||||||
// controller.send('addNewTag');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case keys.BACKSPACE:
|
|
||||||
// if (Ember.isEmpty(this.get('value'))) {
|
|
||||||
// event.preventDefault();
|
|
||||||
// controller.send('deleteLastTag');
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case keys.ESCAPE:
|
|
||||||
// event.preventDefault();
|
|
||||||
// controller.send('reset');
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }),
|
|
||||||
|
|
||||||
// suggestionView: Ember.View.extend({
|
|
||||||
// tagName: 'li',
|
|
||||||
// classNameBindings: 'suggestion.selected',
|
|
||||||
|
|
||||||
// suggestion: null,
|
|
||||||
|
|
||||||
// // we can't use the 'click' event here as the focusOut event on the
|
|
||||||
// // input will fire first
|
|
||||||
|
|
||||||
// mouseDown: function (event) {
|
|
||||||
// event.preventDefault();
|
|
||||||
// },
|
|
||||||
|
|
||||||
// mouseUp: function (event) {
|
|
||||||
// event.preventDefault();
|
|
||||||
// this.get('parentView.controller').send('addTag',
|
|
||||||
// this.get('suggestion.tag'));
|
|
||||||
// }
|
|
||||||
// }),
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
deleteTag: function (tag) {
|
|
||||||
// The view wants to keep focus on the input after a click on a tag
|
|
||||||
Ember.$('.js-tag-input').focus();
|
|
||||||
// Make the controller do the actual work
|
|
||||||
this.sendAction('deleteTag', tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
280
core/client/app/components/gh-tags-input.js
Normal file
280
core/client/app/components/gh-tags-input.js
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
/* global Bloodhound, key */
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ghost Tag Input Component
|
||||||
|
*
|
||||||
|
* Creates an input field that is used to input tags for a post.
|
||||||
|
* @param {Boolean} hasFocus Whether or not the input is focused
|
||||||
|
* @param {DS.Model} post The current post object to input tags for
|
||||||
|
*/
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: ['gh-input'],
|
||||||
|
classNameBindings: ['hasFocus:focus'],
|
||||||
|
|
||||||
|
// Uses the Ember-Data store directly, as it needs to create and get tag records
|
||||||
|
store: Ember.inject.service(),
|
||||||
|
|
||||||
|
hasFocus: false,
|
||||||
|
post: null,
|
||||||
|
highlightIndex: null,
|
||||||
|
|
||||||
|
isDirty: false,
|
||||||
|
isReloading: false,
|
||||||
|
|
||||||
|
unassignedTags: Ember.A(), // tags that AREN'T assigned to this post
|
||||||
|
currentTags: Ember.A(), // tags that ARE assigned to this post
|
||||||
|
|
||||||
|
// Input field events
|
||||||
|
click: function () {
|
||||||
|
this.$('#tag-input').focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
focusIn: function () {
|
||||||
|
this.set('hasFocus', true);
|
||||||
|
key.setScope('tags');
|
||||||
|
},
|
||||||
|
|
||||||
|
focusOut: function () {
|
||||||
|
this.set('hasFocus', false);
|
||||||
|
key.setScope('default');
|
||||||
|
this.set('highlightIndex', null);
|
||||||
|
// if there is text in the input field, create a tag with it
|
||||||
|
if (this.$('#tag-input').val() !== '') {
|
||||||
|
this.send('addTag', this.$('#tag-input').val());
|
||||||
|
}
|
||||||
|
this.saveTags();
|
||||||
|
},
|
||||||
|
|
||||||
|
keyPress: function (event) {
|
||||||
|
var val = this.$('#tag-input').val(),
|
||||||
|
isComma = ','.localeCompare(String.fromCharCode(event.keyCode || event.charCode)) === 0;
|
||||||
|
|
||||||
|
if (isComma && val !== '') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.send('addTag', val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tag Loading functions
|
||||||
|
loadTagsOnInit: Ember.on('init', function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (this.get('post')) {
|
||||||
|
this.loadTags().then(function () {
|
||||||
|
Ember.run.schedule('afterRender', self, 'initTypeahead');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
reloadTags: Ember.observer('post', function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.loadTags().then(function () {
|
||||||
|
self.reloadTypeahead(false);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
loadTags: function () {
|
||||||
|
var self = this,
|
||||||
|
post = this.get('post');
|
||||||
|
|
||||||
|
this.get('currentTags').clear();
|
||||||
|
this.get('unassignedTags').clear();
|
||||||
|
|
||||||
|
return this.get('store').find('tag', {limit: 'all'}).then(function (tags) {
|
||||||
|
if (post.get('id')) { // if it's a new post, it won't have an id
|
||||||
|
self.get('currentTags').pushObjects(post.get('tags').toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.forEach(function (tag) {
|
||||||
|
if (Ember.isEmpty(post.get('id')) || Ember.isEmpty(self.get('currentTags').findBy('id', tag.get('id')))) {
|
||||||
|
self.get('unassignedTags').pushObject(tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ember.RSVP.resolve();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Key Binding functions
|
||||||
|
bindKeys: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
key('enter, tab', 'tags', function (event) {
|
||||||
|
var val = self.$('#tag-input').val();
|
||||||
|
|
||||||
|
if (val !== '') {
|
||||||
|
event.preventDefault();
|
||||||
|
self.send('addTag', val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
key('backspace', 'tags', function (event) {
|
||||||
|
if (self.$('#tag-input').val() === '') {
|
||||||
|
event.preventDefault();
|
||||||
|
self.send('deleteTag');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
key('left', 'tags', function (event) {
|
||||||
|
self.updateHighlightIndex(-1, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
key('right', 'tags', function (event) {
|
||||||
|
self.updateHighlightIndex(1, event);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unbindKeys: function () {
|
||||||
|
key.unbind('enter, tab', 'tags');
|
||||||
|
key.unbind('backspace', 'tags');
|
||||||
|
key.unbind('left', 'tags');
|
||||||
|
key.unbind('right', 'tags');
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement: function () {
|
||||||
|
this.bindKeys();
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroyElement: function () {
|
||||||
|
this.unbindKeys();
|
||||||
|
this.destroyTypeahead();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateHighlightIndex: function (modifier, event) {
|
||||||
|
if (this.$('#tag-input').val() === '') {
|
||||||
|
var highlightIndex = this.get('highlightIndex'),
|
||||||
|
length = this.get('currentTags.length'),
|
||||||
|
newIndex;
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highlightIndex === null) {
|
||||||
|
newIndex = (modifier > 0) ? 0 : length - 1;
|
||||||
|
} else {
|
||||||
|
newIndex = highlightIndex + modifier;
|
||||||
|
if (newIndex < 0 || newIndex >= length) {
|
||||||
|
newIndex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.set('highlightIndex', newIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Typeahead functions
|
||||||
|
initTypeahead: function () {
|
||||||
|
var tags = new Bloodhound({
|
||||||
|
datumTokenizer: Bloodhound.tokenizers.whitespace,
|
||||||
|
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||||
|
local: this.get('unassignedTags').map(function (tag) {
|
||||||
|
return tag.get('name');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$('#tag-input').typeahead({
|
||||||
|
minLength: 1,
|
||||||
|
classNames: {
|
||||||
|
// TODO: Fix CSS for these
|
||||||
|
input: 'tag-input',
|
||||||
|
hint: 'tag-input',
|
||||||
|
menu: 'dropdown-menu',
|
||||||
|
suggestion: 'dropdown-item',
|
||||||
|
open: 'open'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'tags',
|
||||||
|
source: tags
|
||||||
|
}).bind('typeahead:selected', Ember.run.bind(this, 'typeaheadAdd'));
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyTypeahead: function () {
|
||||||
|
this.$('#tag-input').typeahead('destroy');
|
||||||
|
},
|
||||||
|
|
||||||
|
reloadTypeahead: function (refocus) {
|
||||||
|
this.set('isReloading', true);
|
||||||
|
this.destroyTypeahead();
|
||||||
|
this.initTypeahead();
|
||||||
|
if (refocus) {
|
||||||
|
this.click();
|
||||||
|
}
|
||||||
|
this.set('isReloading', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tag Saving / Tag Add/Delete Actions
|
||||||
|
saveTags: function () {
|
||||||
|
var post = this.get('post');
|
||||||
|
|
||||||
|
if (post && this.get('isDirty') && !this.get('isReloading')) {
|
||||||
|
post.get('tags').clear();
|
||||||
|
post.get('tags').pushObjects(this.get('currentTags').toArray());
|
||||||
|
this.set('isDirty', false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Used for typeahead selection
|
||||||
|
typeaheadAdd: function (obj, datum) {
|
||||||
|
if (datum) {
|
||||||
|
// this is needed so two tags with the same name aren't added
|
||||||
|
this.$('#tag-input').typeahead('val', '');
|
||||||
|
this.send('addTag', datum);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
addTag: function (tagName) {
|
||||||
|
var tagToAdd, checkTag;
|
||||||
|
|
||||||
|
// Prevent multiple tags with the same name occuring
|
||||||
|
if (this.get('currentTags').findBy('name', tagName)) {
|
||||||
|
this.$('#tag-input').typeahead('val', '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkTag = this.get('unassignedTags').findBy('name', tagName);
|
||||||
|
|
||||||
|
if (checkTag) {
|
||||||
|
tagToAdd = checkTag;
|
||||||
|
this.get('unassignedTags').removeObject(checkTag);
|
||||||
|
this.reloadTypeahead();
|
||||||
|
} else {
|
||||||
|
tagToAdd = this.get('store').createRecord('tag', {name: tagName});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('isDirty', true);
|
||||||
|
this.set('highlightIndex', null);
|
||||||
|
this.get('currentTags').pushObject(tagToAdd);
|
||||||
|
this.$('#tag-input').typeahead('val', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteTag: function (tag) {
|
||||||
|
var removedTag;
|
||||||
|
|
||||||
|
if (tag) {
|
||||||
|
removedTag = this.get('currentTags').findBy('name', tag);
|
||||||
|
this.get('currentTags').removeObject(removedTag);
|
||||||
|
} else {
|
||||||
|
if (this.get('highlightIndex') !== null) {
|
||||||
|
removedTag = this.get('currentTags').objectAt(this.get('highlightIndex'));
|
||||||
|
this.get('currentTags').removeObject(removedTag);
|
||||||
|
this.set('highlightIndex', null);
|
||||||
|
} else {
|
||||||
|
this.set('highlightIndex', this.get('currentTags.length') - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedTag) {
|
||||||
|
if (removedTag.get('isNew')) { // if tag is new, don't change isDirty,
|
||||||
|
removedTag.deleteRecord(); // and delete the new record
|
||||||
|
} else {
|
||||||
|
this.set('isDirty', true);
|
||||||
|
this.get('unassignedTags').pushObject(removedTag);
|
||||||
|
this.reloadTypeahead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,248 +0,0 @@
|
||||||
import Ember from 'ember';
|
|
||||||
|
|
||||||
// should be integrated into tag input component during reimplementation
|
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
|
||||||
tagEnteredOrder: Ember.A(),
|
|
||||||
|
|
||||||
tags: Ember.computed('parentController.model.tags', function () {
|
|
||||||
var proxyTags = Ember.ArrayProxy.create({
|
|
||||||
content: this.get('parentController.model.tags')
|
|
||||||
}),
|
|
||||||
temp = proxyTags.get('arrangedContent').slice();
|
|
||||||
|
|
||||||
proxyTags.get('arrangedContent').clear();
|
|
||||||
|
|
||||||
this.get('tagEnteredOrder').forEach(function (tagName) {
|
|
||||||
var tag = temp.find(function (tag) {
|
|
||||||
return tag.get('name') === tagName;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tag) {
|
|
||||||
proxyTags.get('arrangedContent').addObject(tag);
|
|
||||||
temp.removeObject(tag);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
proxyTags.get('arrangedContent').unshiftObjects(temp);
|
|
||||||
|
|
||||||
return proxyTags;
|
|
||||||
}),
|
|
||||||
|
|
||||||
suggestions: null,
|
|
||||||
newTagText: null,
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// triggered when the view is inserted so that later store.all('tag')
|
|
||||||
// queries hit a full store cache and we don't see empty or out-of-date
|
|
||||||
// suggestion lists
|
|
||||||
loadAllTags: function () {
|
|
||||||
this.store.find('tag', {limit: 'all'});
|
|
||||||
},
|
|
||||||
|
|
||||||
addNewTag: function () {
|
|
||||||
var newTagText = this.get('newTagText'),
|
|
||||||
searchTerm,
|
|
||||||
existingTags,
|
|
||||||
newTag;
|
|
||||||
|
|
||||||
if (Ember.isEmpty(newTagText) || this.hasTag(newTagText)) {
|
|
||||||
this.send('reset');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
newTagText = newTagText.trim();
|
|
||||||
searchTerm = newTagText.toLowerCase();
|
|
||||||
|
|
||||||
// add existing tag if we have a match
|
|
||||||
existingTags = this.store.all('tag').filter(function (tag) {
|
|
||||||
if (tag.get('isNew')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tag.get('name').toLowerCase() === searchTerm;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingTags.get('length')) {
|
|
||||||
this.send('addTag', existingTags.get('firstObject'));
|
|
||||||
} else {
|
|
||||||
// otherwise create a new one
|
|
||||||
newTag = this.store.createRecord('tag');
|
|
||||||
newTag.set('name', newTagText);
|
|
||||||
|
|
||||||
this.send('addTag', newTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.send('reset');
|
|
||||||
},
|
|
||||||
|
|
||||||
addTag: function (tag) {
|
|
||||||
if (!Ember.isEmpty(tag)) {
|
|
||||||
this.get('tags').addObject(tag);
|
|
||||||
this.get('tagEnteredOrder').addObject(tag.get('name'));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.send('reset');
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteTag: function (tag) {
|
|
||||||
if (tag) {
|
|
||||||
this.get('tags').removeObject(tag);
|
|
||||||
this.get('tagEnteredOrder').removeObject(tag.get('name'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteLastTag: function () {
|
|
||||||
this.send('deleteTag', this.get('tags.lastObject'));
|
|
||||||
},
|
|
||||||
|
|
||||||
selectSuggestion: function (suggestion) {
|
|
||||||
if (!Ember.isEmpty(suggestion)) {
|
|
||||||
this.get('suggestions').setEach('selected', false);
|
|
||||||
suggestion.set('selected', true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectNextSuggestion: function () {
|
|
||||||
var suggestions = this.get('suggestions'),
|
|
||||||
selectedSuggestion = this.get('selectedSuggestion'),
|
|
||||||
currentIndex,
|
|
||||||
newSelection;
|
|
||||||
|
|
||||||
if (!Ember.isEmpty(suggestions)) {
|
|
||||||
currentIndex = suggestions.indexOf(selectedSuggestion);
|
|
||||||
if (currentIndex + 1 < suggestions.get('length')) {
|
|
||||||
newSelection = suggestions[currentIndex + 1];
|
|
||||||
this.send('selectSuggestion', newSelection);
|
|
||||||
} else {
|
|
||||||
suggestions.setEach('selected', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectPreviousSuggestion: function () {
|
|
||||||
var suggestions = this.get('suggestions'),
|
|
||||||
selectedSuggestion = this.get('selectedSuggestion'),
|
|
||||||
currentIndex,
|
|
||||||
lastIndex,
|
|
||||||
newSelection;
|
|
||||||
|
|
||||||
if (!Ember.isEmpty(suggestions)) {
|
|
||||||
currentIndex = suggestions.indexOf(selectedSuggestion);
|
|
||||||
if (currentIndex === -1) {
|
|
||||||
lastIndex = suggestions.get('length') - 1;
|
|
||||||
this.send('selectSuggestion', suggestions[lastIndex]);
|
|
||||||
} else if (currentIndex - 1 >= 0) {
|
|
||||||
newSelection = suggestions[currentIndex - 1];
|
|
||||||
this.send('selectSuggestion', newSelection);
|
|
||||||
} else {
|
|
||||||
suggestions.setEach('selected', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addSelectedSuggestion: function () {
|
|
||||||
var suggestion = this.get('selectedSuggestion');
|
|
||||||
|
|
||||||
if (Ember.isEmpty(suggestion)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.send('addTag', suggestion.get('tag'));
|
|
||||||
},
|
|
||||||
|
|
||||||
reset: function () {
|
|
||||||
this.set('suggestions', null);
|
|
||||||
this.set('newTagText', null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectedSuggestion: Ember.computed('suggestions.@each.selected', function () {
|
|
||||||
var suggestions = this.get('suggestions');
|
|
||||||
|
|
||||||
if (suggestions && suggestions.get('length')) {
|
|
||||||
return suggestions.filterBy('selected').get('firstObject');
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
updateSuggestionsList: Ember.observer('newTagText', function () {
|
|
||||||
var searchTerm = this.get('newTagText'),
|
|
||||||
matchingTags,
|
|
||||||
// Limit the suggestions number
|
|
||||||
maxSuggestions = 5,
|
|
||||||
suggestions = Ember.A();
|
|
||||||
|
|
||||||
if (!searchTerm || Ember.isEmpty(searchTerm.trim())) {
|
|
||||||
this.set('suggestions', null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchTerm = searchTerm.trim();
|
|
||||||
|
|
||||||
matchingTags = this.findMatchingTags(searchTerm);
|
|
||||||
matchingTags = matchingTags.slice(0, maxSuggestions);
|
|
||||||
matchingTags.forEach(function (matchingTag) {
|
|
||||||
var suggestion = this.makeSuggestionObject(matchingTag, searchTerm);
|
|
||||||
suggestions.pushObject(suggestion);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.set('suggestions', suggestions);
|
|
||||||
}),
|
|
||||||
|
|
||||||
findMatchingTags: function (searchTerm) {
|
|
||||||
var matchingTags,
|
|
||||||
self = this,
|
|
||||||
allTags = this.store.all('tag').filterBy('isNew', false),
|
|
||||||
deDupe = {};
|
|
||||||
|
|
||||||
if (allTags.get('length') === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
searchTerm = searchTerm.toLowerCase();
|
|
||||||
|
|
||||||
matchingTags = allTags.filter(function (tag) {
|
|
||||||
var tagNameMatches,
|
|
||||||
hasAlreadyBeenAdded,
|
|
||||||
tagName = tag.get('name');
|
|
||||||
|
|
||||||
tagNameMatches = tagName.toLowerCase().indexOf(searchTerm) !== -1;
|
|
||||||
hasAlreadyBeenAdded = self.hasTag(tagName);
|
|
||||||
|
|
||||||
if (tagNameMatches && !hasAlreadyBeenAdded) {
|
|
||||||
if (typeof deDupe[tagName] === 'undefined') {
|
|
||||||
deDupe[tagName] = 1;
|
|
||||||
} else {
|
|
||||||
deDupe[tagName] += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deDupe[tagName] === 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
return matchingTags;
|
|
||||||
},
|
|
||||||
|
|
||||||
hasTag: function (tagName) {
|
|
||||||
return this.get('tags').mapBy('name').contains(tagName);
|
|
||||||
},
|
|
||||||
|
|
||||||
makeSuggestionObject: function (matchingTag, _searchTerm) {
|
|
||||||
var searchTerm = Ember.Handlebars.Utils.escapeExpression(_searchTerm),
|
|
||||||
regexEscapedSearchTerm = searchTerm.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
|
|
||||||
tagName = Ember.Handlebars.Utils.escapeExpression(matchingTag.get('name')),
|
|
||||||
regex = new RegExp('(' + regexEscapedSearchTerm + ')', 'gi'),
|
|
||||||
highlightedName,
|
|
||||||
suggestion = Ember.Object.create();
|
|
||||||
|
|
||||||
highlightedName = tagName.replace(regex, '<mark>$1</mark>');
|
|
||||||
highlightedName = Ember.String.htmlSafe(highlightedName);
|
|
||||||
|
|
||||||
suggestion.set('tag', matchingTag);
|
|
||||||
suggestion.set('highlightedName', highlightedName);
|
|
||||||
|
|
||||||
return suggestion;
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -249,7 +249,48 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#entry-tags input[type="text"].tag-input {
|
/* Tags input CSS (TODO: needs some revision)
|
||||||
|
/* ------------------------------------------------------ */
|
||||||
|
.tags-input-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-input-list li {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-tag {
|
||||||
|
margin-right: 0.3em;
|
||||||
|
padding: 0.2em 0.6em 0.3em;
|
||||||
|
background-color: var(--darkgrey);
|
||||||
|
border-radius: 0.25em;
|
||||||
|
color: var(--lightgrey);
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-tag.highlight {
|
||||||
|
background: var(--midgrey);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-input {
|
||||||
|
margin-top: 5px;
|
||||||
|
border: none;
|
||||||
|
font-weight: 300;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-input:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: can be removed once tag-component css is fixed */
|
||||||
|
/*#entry-tags input[type="text"].tag-input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 9px 9px 9px 0;
|
padding: 9px 9px 9px 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -410,7 +451,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
align-self: auto;
|
align-self: auto;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.publish-bar-actions {
|
.publish-bar-actions {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
|
|
|
@ -125,6 +125,7 @@ select.error {
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-input:focus,
|
.gh-input:focus,
|
||||||
|
.gh-input.focus,
|
||||||
.gh-select:focus,
|
.gh-select:focus,
|
||||||
select:focus {
|
select:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
<div class="publish-bar-tags-icon">
|
|
||||||
<label class="tag-label icon-tag" for="tags" title="Tags">
|
|
||||||
<span class="sr-only">Tags</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="publish-bar-tags">
|
|
||||||
<div class="tags-wrapper tags">
|
|
||||||
{{#each tags as |tag|}}
|
|
||||||
<span class="tag" {{action "deleteTag" tag target=view}}>{{tag.name}} <i class="icon-x"></i></span>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="publish-bar-tags-input">
|
|
||||||
<input type="hidden" class="tags-holder" id="tags-holder">
|
|
||||||
{{!-- {{view view.tagInputView class="tag-input js-tag-input" id="tags" value=newTagText}}
|
|
||||||
<ul class="suggestions dropdown-menu dropdown-triangle-bottom" style={{view.overlayStyles}}>
|
|
||||||
{{#each suggestions as |suggestion|}}
|
|
||||||
{{#view view.suggestionView suggestion=suggestion}}
|
|
||||||
<a href="javascript:void(0);">{{view.suggestion.highlightedName}}</a>
|
|
||||||
{{/view}}
|
|
||||||
{{/each}}
|
|
||||||
</ul> --}}
|
|
||||||
</div>
|
|
6
core/client/app/templates/components/gh-tags-input.hbs
Normal file
6
core/client/app/templates/components/gh-tags-input.hbs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<ul class="tags-input-list">
|
||||||
|
{{#each currentTags as |tag index|}}
|
||||||
|
<li class="label-tag {{if (is-equal highlightIndex index) 'highlight'}}" {{action "deleteTag" tag.name}}>{{tag.name}}</li>
|
||||||
|
{{/each}}
|
||||||
|
<li><input type="text" id="tag-input"></li>
|
||||||
|
</ul>
|
|
@ -33,6 +33,11 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="tag-input">Tags</label>
|
||||||
|
{{gh-tags-input post=model}}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#unless session.user.isAuthor}}
|
{{#unless session.user.isAuthor}}
|
||||||
<div class="form-group for-select">
|
<div class="form-group for-select">
|
||||||
<label for="author-list">Author</label>
|
<label for="author-list">Author</label>
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"rangyinputs": "1.2.0",
|
"rangyinputs": "1.2.0",
|
||||||
"showdown-ghost": "0.3.6",
|
"showdown-ghost": "0.3.6",
|
||||||
"sinonjs": "1.14.1",
|
"sinonjs": "1.14.1",
|
||||||
|
"typeahead.js": "0.11.1",
|
||||||
"validator-js": "3.39.0",
|
"validator-js": "3.39.0",
|
||||||
"xregexp": "2.0.0"
|
"xregexp": "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
/* jshint expr:true */
|
|
||||||
import {expect} from 'chai';
|
|
||||||
import {
|
|
||||||
describeComponent,
|
|
||||||
it
|
|
||||||
} from 'ember-mocha';
|
|
||||||
|
|
||||||
describeComponent(
|
|
||||||
'gh-post-tags-input',
|
|
||||||
'GhPostTagsInputComponent',
|
|
||||||
{
|
|
||||||
// specify the other units that are required for this test
|
|
||||||
// needs: ['component:foo', 'helper:bar']
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
it('renders', function () {
|
|
||||||
// creates the component instance
|
|
||||||
var component = this.subject();
|
|
||||||
|
|
||||||
expect(component._state).to.equal('preRender');
|
|
||||||
|
|
||||||
// renders the component on the page
|
|
||||||
this.render();
|
|
||||||
expect(component._state).to.equal('inDOM');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
405
core/client/tests/unit/components/gh-tags-input-test.js
Normal file
405
core/client/tests/unit/components/gh-tags-input-test.js
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
/* jshint expr:true */
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import {
|
||||||
|
describeComponent,
|
||||||
|
it
|
||||||
|
} from 'ember-mocha';
|
||||||
|
|
||||||
|
describeComponent(
|
||||||
|
'gh-tags-input',
|
||||||
|
'GhTagsInputComponent',
|
||||||
|
{
|
||||||
|
needs: ['helper:is-equal']
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
var post = Ember.Object.create({
|
||||||
|
id: 1,
|
||||||
|
tags: Ember.A()
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
var store = Ember.Object.create({
|
||||||
|
tags: Ember.A(),
|
||||||
|
|
||||||
|
find: function () {
|
||||||
|
return Ember.RSVP.resolve(this.get('tags'));
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord: function (name, opts) {
|
||||||
|
return Ember.Object.create({
|
||||||
|
isNew: true,
|
||||||
|
isDeleted: false,
|
||||||
|
|
||||||
|
name: opts.name,
|
||||||
|
|
||||||
|
deleteRecord: function () {
|
||||||
|
this.set('isDeleted', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
store.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 1,
|
||||||
|
name: 'Test1'
|
||||||
|
}));
|
||||||
|
store.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 2,
|
||||||
|
name: 'Test2'
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.subject().set('store', store);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
post.get('tags').clear(); // reset tags
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with null post', function () {
|
||||||
|
// creates the component instance
|
||||||
|
var component = this.subject();
|
||||||
|
expect(component._state).to.equal('preRender');
|
||||||
|
|
||||||
|
// renders the component on the page
|
||||||
|
this.render();
|
||||||
|
expect(component._state).to.equal('inDOM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly loads all tags', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly loads & filters tags when post has tags', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 1,
|
||||||
|
name: 'Test1'
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(1);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(1);
|
||||||
|
expect(component.get('unassignedTags').findBy('id', 1)).to.not.exist;
|
||||||
|
expect(component.get('unassignedTags').findBy('id', 2)).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly adds new tag to currentTags', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(0);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.send('addTag', 'Test3');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(1);
|
||||||
|
expect(component.get('isDirty')).to.be.true;
|
||||||
|
expect(component.get('currentTags').findBy('name', 'Test3')).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly adds existing tag to currentTags', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(0);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.send('addTag', 'Test2');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(1);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(1);
|
||||||
|
expect(component.get('isDirty')).to.be.true;
|
||||||
|
expect(component.get('currentTags').findBy('name', 'Test2')).to.exist;
|
||||||
|
expect(component.get('unassignedTags').findBy('name', 'Test2')).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doesn\'t allow duplicate tags to be added', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 1,
|
||||||
|
name: 'Test1'
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(1);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(1);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.send('addTag', 'Test1');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(1);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deletes new tag correctly', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(0);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.send('addTag', 'Test3');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(1);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.send('deleteTag', 'Test3');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(0);
|
||||||
|
expect(component.get('currentTags').findBy('name', 'Test3')).to.not.exist;
|
||||||
|
expect(component.get('unassignedTags').findBy('name', 'Test3')).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deletes existing tag correctly', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 1,
|
||||||
|
name: 'Test1'
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(1);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(1);
|
||||||
|
expect(component.get('unassignedTags').findBy('name', 'Test1')).to.not.exist;
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.send('deleteTag', 'Test1');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(0);
|
||||||
|
expect(component.get('unassignedTags').findBy('name', 'Test1')).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates tag with leftover text when component is de-focused', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(0);
|
||||||
|
|
||||||
|
component.$('#tag-input').typeahead('val', 'Test3');
|
||||||
|
component.focusOut(); // simluate de-focus
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets highlight index to length-1 if it is null and modifier is negative', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 3,
|
||||||
|
name: 'Test3'
|
||||||
|
}));
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 4,
|
||||||
|
name: 'Test4'
|
||||||
|
}));
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 5,
|
||||||
|
name: 'Test5'
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(3);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.updateHighlightIndex(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets highlight index to 0 if it is null and modifier is positive', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 3,
|
||||||
|
name: 'Test3'
|
||||||
|
}));
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 4,
|
||||||
|
name: 'Test4'
|
||||||
|
}));
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 5,
|
||||||
|
name: 'Test5'
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(3);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.updateHighlightIndex(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments highlight index correctly (no reset)', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 3,
|
||||||
|
name: 'Test3'
|
||||||
|
}));
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 4,
|
||||||
|
name: 'Test4'
|
||||||
|
}));
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 5,
|
||||||
|
name: 'Test5'
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
component.set('highlightIndex', 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.equal(1);
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(3);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.updateHighlightIndex(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.equal(2);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.updateHighlightIndex(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments highlight index correctly (with reset)', function () {
|
||||||
|
var component = this.subject();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 3,
|
||||||
|
name: 'Test3'
|
||||||
|
}));
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 4,
|
||||||
|
name: 'Test4'
|
||||||
|
}));
|
||||||
|
|
||||||
|
post.get('tags').pushObject(Ember.Object.create({
|
||||||
|
id: 5,
|
||||||
|
name: 'Test5'
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('post', post);
|
||||||
|
component.set('highlightIndex', 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.equal(2);
|
||||||
|
expect(component.get('unassignedTags.length')).to.equal(2);
|
||||||
|
expect(component.get('currentTags.length')).to.equal(3);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.updateHighlightIndex(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.be.null;
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.set('highlightIndex', 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.equal(0);
|
||||||
|
|
||||||
|
Ember.run(function () {
|
||||||
|
component.updateHighlightIndex(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(component.get('highlightIndex')).to.be.null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
|
@ -469,9 +469,6 @@ CasperTest.Routines = (function () {
|
||||||
casper.thenOpenAndWaitForPageLoad('editor', function createTestPost() {
|
casper.thenOpenAndWaitForPageLoad('editor', function createTestPost() {
|
||||||
casper.sendKeys('#entry-title', testPost.title);
|
casper.sendKeys('#entry-title', testPost.title);
|
||||||
casper.writeContentToEditor(testPost.html);
|
casper.writeContentToEditor(testPost.html);
|
||||||
// TODO move these into psm tests when tags have been added there
|
|
||||||
// casper.sendKeys('#entry-tags input.tag-input', 'TestTag');
|
|
||||||
// casper.sendKeys('#entry-tags input.tag-input', casper.page.event.key.Enter);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
casper.waitForSelectorTextChange('.entry-preview .rendered-markdown');
|
casper.waitForSelectorTextChange('.entry-preview .rendered-markdown');
|
||||||
|
|
|
@ -180,37 +180,129 @@ CasperTest.begin('Post url input is reset from all whitespace back to original v
|
||||||
test.assertEquals(slugVal, originalSlug, 'slug gets reset to original value');
|
test.assertEquals(slugVal, originalSlug, 'slug gets reset to original value');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// TODO this test is from editor_test and needs to come back in some form when tags are moved into PSM
|
|
||||||
// CasperTest.begin('Tag editor', 7, function suite(test) {
|
CasperTest.begin('Tag Editor', 18, function suite(test) {
|
||||||
// casper.thenOpenAndWaitForPageLoad('editor', function testTitleAndUrl() {
|
var testTag = 'Test1',
|
||||||
// test.assertTitle('Editor - Test Blog', 'Ghost admin has incorrect title');
|
createdTag = '.tags-input-list li.label-tag';
|
||||||
// test.assertUrlMatch(/ghost\/editor\/$/, 'Landed on the correct URL');
|
|
||||||
// });
|
casper.thenOpenAndWaitForPageLoad('editor', function testTitleAndUrl() {
|
||||||
//
|
test.assertTitle('Editor - Test Blog', 'Ghost admin has incorrect title');
|
||||||
// var tagName = 'someTagName',
|
test.assertUrlMatch(/ghost\/editor\/$/, 'Landed on the correct URL');
|
||||||
// createdTagSelector = '#entry-tags .tags .tag';
|
});
|
||||||
//
|
|
||||||
// casper.then(function () {
|
casper.then(function () {
|
||||||
// test.assertExists('#entry-tags', 'should have tag label area');
|
test.assertExists('.tags-input-list', 'should have tag list area');
|
||||||
// test.assertExists('#entry-tags .tag-label', 'should have tag label icon');
|
test.assertExists('#tag-input', 'should have tag input');
|
||||||
// test.assertExists('#entry-tags input.tag-input', 'should have tag input area');
|
});
|
||||||
// });
|
|
||||||
//
|
casper.thenClick('#tag-input');
|
||||||
// casper.thenClick('#entry-tags input.tag-input');
|
casper.then(function () {
|
||||||
// casper.then(function () {
|
casper.sendKeys('#tag-input', testTag, {keepFocus: true});
|
||||||
// casper.sendKeys('#entry-tags input.tag-input', tagName, {keepFocus: true});
|
});
|
||||||
// });
|
casper.then(function () {
|
||||||
// casper.then(function () {
|
casper.sendKeys('#tag-input', casper.page.event.key.Enter, {keepFocus: true});
|
||||||
// casper.sendKeys('#entry-tags input.tag-input', casper.page.event.key.Enter);
|
});
|
||||||
// });
|
|
||||||
//
|
casper.waitForSelector(createdTag, function onSuccess() {
|
||||||
// casper.waitForSelector(createdTagSelector, function onSuccess() {
|
test.assertSelectorHasText(createdTag, testTag, 'typing enter after tag name should create tag');
|
||||||
// test.assertSelectorHasText(createdTagSelector, tagName, 'typing enter after tag name should create tag');
|
});
|
||||||
// });
|
|
||||||
//
|
casper.thenClick(createdTag);
|
||||||
// casper.thenClick(createdTagSelector);
|
casper.waitWhileSelector(createdTag, function onSuccess() {
|
||||||
//
|
test.assert(true, 'clicking the tag should delete the tag');
|
||||||
// casper.waitWhileSelector(createdTagSelector, function onSuccess() {
|
});
|
||||||
// test.assert(true, 'clicking the tag should delete the tag');
|
|
||||||
// });
|
casper.then(function () {
|
||||||
// });
|
casper.sendKeys('#tag-input', testTag, {keepFocus: true});
|
||||||
|
});
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', casper.page.event.key.Tab, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitForSelector(createdTag, function onSuccess() {
|
||||||
|
test.assertSelectorHasText(createdTag, testTag, 'typing tab after tag name should create tag');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', casper.page.event.key.Backspace, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitForSelector(createdTag + '.highlight', function onSuccess() {
|
||||||
|
test.assert(true, 'hitting backspace should highlight the last tag');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', casper.page.event.key.Backspace, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitWhileSelector(createdTag + '.highlight', function onSuccess() {
|
||||||
|
test.assert(true, 'hitting backspace on a higlighted tag should delete it');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', testTag, {keepFocus: true});
|
||||||
|
});
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', casper.page.event.key.Tab, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitForSelector(createdTag, function onSuccess() {
|
||||||
|
test.assertSelectorHasText(createdTag, testTag, 'typing tab after tag name should create tag');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', casper.page.event.key.Left, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitForSelector(createdTag + '.highlight', function onSuccess() {
|
||||||
|
test.assert(true, 'hitting left should highlight the last tag');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', casper.page.event.key.Left, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitWhileSelector(createdTag + '.highlight', function onSuccess() {
|
||||||
|
test.assert(true, 'hitting left on a higlighted tag should un-highlight it');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitForSelector(createdTag, function onSuccess() {
|
||||||
|
test.assertSelectorHasText(createdTag, testTag, 'un-highlighting tag should not delete it');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', casper.page.event.key.Right, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitForSelector(createdTag + '.highlight', function onSuccess() {
|
||||||
|
test.assert(true, 'hitting right should highlight the first tag');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', casper.page.event.key.Right, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitWhileSelector(createdTag + '.highlight', function onSuccess() {
|
||||||
|
test.assert(true, 'hitting right on a higlighted tag should un-highlight it');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.waitForSelector(createdTag, function onSuccess() {
|
||||||
|
test.assertSelectorHasText(createdTag, testTag, 'un-highlighting tag should not delete it');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.thenClick(createdTag);
|
||||||
|
casper.waitWhileSelector(createdTag, function onSuccess() {
|
||||||
|
test.assert(true, 'clicking the tag should delete the tag');
|
||||||
|
});
|
||||||
|
|
||||||
|
casper.then(function () {
|
||||||
|
casper.sendKeys('#tag-input', testTag, {keepFocus: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click in a different field
|
||||||
|
casper.thenClick('#post-setting-date');
|
||||||
|
|
||||||
|
casper.waitForSelector(createdTag, function onSuccess() {
|
||||||
|
test.assertSelectorHasText(createdTag, testTag, 'de-focusing from tag input should create tag with leftover text');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue