mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 11:22:19 +03:00
Merge pull request #5969 from kevinansfield/routable-tags
Routable tags
This commit is contained in:
commit
2c7a4f9328
@ -1,3 +1,9 @@
|
||||
import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter';
|
||||
|
||||
export default EmbeddedRelationAdapter.extend();
|
||||
export default EmbeddedRelationAdapter.extend({
|
||||
|
||||
shouldBackgroundReloadRecord: function () {
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -5,6 +5,10 @@ export default DS.RESTAdapter.extend({
|
||||
host: window.location.origin,
|
||||
namespace: ghostPaths().apiRoot.slice(1),
|
||||
|
||||
shouldBackgroundReloadRecord: function () {
|
||||
return false;
|
||||
},
|
||||
|
||||
query: function (store, type, query) {
|
||||
var id;
|
||||
|
||||
|
@ -18,20 +18,17 @@ export default Ember.Component.extend({
|
||||
|
||||
didInsertElement: function () {
|
||||
var options = this.getProperties('lineNumbers', 'indentUnit', 'mode', 'theme'),
|
||||
self = this,
|
||||
editor;
|
||||
editor = new CodeMirror(this.get('element'), options);
|
||||
editor = new CodeMirror(this.get('element'), options);
|
||||
|
||||
editor.getDoc().setValue(this.get('value'));
|
||||
|
||||
// events
|
||||
editor.on('focus', function () {
|
||||
self.set('isFocused', true);
|
||||
});
|
||||
editor.on('blur', function () {
|
||||
self.set('isFocused', false);
|
||||
});
|
||||
editor.on('change', function () {
|
||||
self.set('value', editor.getDoc().getValue());
|
||||
editor.on('focus', Ember.run.bind(this, 'set', 'isFocused', true));
|
||||
editor.on('blur', Ember.run.bind(this, 'set', 'isFocused', false));
|
||||
editor.on('change', () => {
|
||||
Ember.run(this, function () {
|
||||
this.set('value', editor.getDoc().getValue());
|
||||
});
|
||||
});
|
||||
|
||||
this.set('editor', editor);
|
||||
|
@ -37,10 +37,12 @@ export default Ember.Component.extend(DropdownMixin, {
|
||||
}
|
||||
this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
|
||||
if (event.originalEvent.animationName === 'fade-out') {
|
||||
if (self.get('closing')) {
|
||||
self.set('isOpen', false);
|
||||
self.set('closing', false);
|
||||
}
|
||||
Ember.run(self, function () {
|
||||
if (this.get('closing')) {
|
||||
this.set('isOpen', false);
|
||||
this.set('closing', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
127
ghost/admin/app/components/gh-tag-settings-form.js
Normal file
127
ghost/admin/app/components/gh-tag-settings-form.js
Normal file
@ -0,0 +1,127 @@
|
||||
/* global key */
|
||||
import Ember from 'ember';
|
||||
import boundOneWay from 'ghost/utils/bound-one-way';
|
||||
|
||||
const {get} = Ember;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
|
||||
tag: null,
|
||||
|
||||
scratchName: boundOneWay('tag.name'),
|
||||
scratchSlug: boundOneWay('tag.slug'),
|
||||
scratchDescription: boundOneWay('tag.description'),
|
||||
scratchMetaTitle: boundOneWay('tag.meta_title'),
|
||||
scratchMetaDescription: boundOneWay('tag.meta_description'),
|
||||
|
||||
isViewingSubview: false,
|
||||
|
||||
config: Ember.inject.service(),
|
||||
|
||||
title: Ember.computed('tag.isNew', function () {
|
||||
if (this.get('tag.isNew')) {
|
||||
return 'New Tag';
|
||||
} else {
|
||||
return 'Tag Settings';
|
||||
}
|
||||
}),
|
||||
|
||||
seoTitle: Ember.computed('scratchName', 'scratchMetaTitle', function () {
|
||||
let metaTitle = this.get('scratchMetaTitle') || '';
|
||||
|
||||
metaTitle = metaTitle.length > 0 ? metaTitle : this.get('scratchName');
|
||||
|
||||
if (metaTitle && metaTitle.length > 70) {
|
||||
metaTitle = metaTitle.substring(0, 70).trim();
|
||||
metaTitle = Ember.Handlebars.Utils.escapeExpression(metaTitle);
|
||||
metaTitle = Ember.String.htmlSafe(metaTitle + '…');
|
||||
}
|
||||
|
||||
return metaTitle;
|
||||
}),
|
||||
|
||||
seoURL: Ember.computed('scratchSlug', function () {
|
||||
const blogUrl = this.get('config.blogUrl'),
|
||||
seoSlug = this.get('scratchSlug') || '';
|
||||
|
||||
let seoURL = blogUrl + '/tag/' + seoSlug;
|
||||
|
||||
// only append a slash to the URL if the slug exists
|
||||
if (seoSlug) {
|
||||
seoURL += '/';
|
||||
}
|
||||
|
||||
if (seoURL.length > 70) {
|
||||
seoURL = seoURL.substring(0, 70).trim();
|
||||
seoURL = Ember.String.htmlSafe(seoURL + '…');
|
||||
}
|
||||
|
||||
return seoURL;
|
||||
}),
|
||||
|
||||
seoDescription: Ember.computed('scratchDescription', 'scratchMetaDescription', function () {
|
||||
let metaDescription = this.get('scratchMetaDescription') || '';
|
||||
|
||||
metaDescription = metaDescription.length > 0 ? metaDescription : this.get('scratchDescription');
|
||||
|
||||
if (metaDescription && metaDescription.length > 156) {
|
||||
metaDescription = metaDescription.substring(0, 156).trim();
|
||||
metaDescription = Ember.Handlebars.Utils.escapeExpression(metaDescription);
|
||||
metaDescription = Ember.String.htmlSafe(metaDescription + '…');
|
||||
}
|
||||
|
||||
return metaDescription;
|
||||
}),
|
||||
|
||||
didReceiveAttrs: function (attrs) {
|
||||
if (get(attrs, 'newAttrs.tag.value.id') !== get(attrs, 'oldAttrs.tag.value.id')) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
this.set('isViewingSubview', false);
|
||||
if (this.$()) {
|
||||
this.$('.settings-menu-pane').scrollTop(0);
|
||||
}
|
||||
},
|
||||
|
||||
focusIn: function () {
|
||||
key.setScope('tag-settings-form');
|
||||
},
|
||||
|
||||
focusOut: function () {
|
||||
key.setScope('default');
|
||||
},
|
||||
|
||||
actions: {
|
||||
setProperty: function (property, value) {
|
||||
this.attrs.setProperty(property, value);
|
||||
},
|
||||
|
||||
setCoverImage: function (image) {
|
||||
this.attrs.setProperty('image', image);
|
||||
},
|
||||
|
||||
clearCoverImage: function () {
|
||||
this.attrs.setProperty('image', '');
|
||||
},
|
||||
|
||||
setUploaderReference: function () {
|
||||
// noop
|
||||
},
|
||||
|
||||
openMeta: function () {
|
||||
this.set('isViewingSubview', true);
|
||||
},
|
||||
|
||||
closeMeta: function () {
|
||||
this.set('isViewingSubview', false);
|
||||
},
|
||||
|
||||
deleteTag: function () {
|
||||
this.sendAction('openModal', 'delete-tag', this.get('tag'));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
@ -10,29 +10,6 @@ export default Ember.Component.extend({
|
||||
return this.get('image') || '';
|
||||
}),
|
||||
|
||||
/**
|
||||
* Sets up the uploader on render
|
||||
*/
|
||||
setup: function () {
|
||||
var $this = this.$(),
|
||||
self = this;
|
||||
|
||||
// this.set('uploaderReference', uploader.call($this, {
|
||||
// editor: true,
|
||||
// fileStorage: this.get('config.fileStorage')
|
||||
// }));
|
||||
|
||||
$this.on('uploadsuccess', function (event, result) {
|
||||
if (result && result !== '' && result !== 'http://') {
|
||||
self.sendAction('uploaded', result);
|
||||
}
|
||||
});
|
||||
|
||||
$this.on('imagecleared', function () {
|
||||
self.sendAction('canceled');
|
||||
});
|
||||
},
|
||||
|
||||
// removes event listeners from the uploader
|
||||
removeListeners: function () {
|
||||
var $this = this.$();
|
||||
@ -41,17 +18,6 @@ export default Ember.Component.extend({
|
||||
$this.find('.js-cancel').off();
|
||||
},
|
||||
|
||||
// didInsertElement: function () {
|
||||
// Ember.run.scheduleOnce('afterRender', this, this.setup());
|
||||
// },
|
||||
didInsertElement: function () {
|
||||
this.send('initUploader');
|
||||
},
|
||||
|
||||
willDestroyElement: function () {
|
||||
this.removeListeners();
|
||||
},
|
||||
|
||||
// NOTE: because the uploader is sometimes in the same place in the DOM
|
||||
// between transitions Glimmer will re-use the existing elements including
|
||||
// those that arealready decorated by jQuery. The following works around
|
||||
@ -77,13 +43,20 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement: function () {
|
||||
this.send('initUploader');
|
||||
},
|
||||
|
||||
willDestroyElement: function () {
|
||||
this.removeListeners();
|
||||
},
|
||||
|
||||
actions: {
|
||||
initUploader: function () {
|
||||
var ref,
|
||||
el,
|
||||
el = this.$(),
|
||||
self = this;
|
||||
|
||||
el = this.$();
|
||||
ref = uploader.call(el, {
|
||||
editor: true,
|
||||
fileStorage: this.get('config.fileStorage')
|
||||
@ -91,13 +64,13 @@ export default Ember.Component.extend({
|
||||
|
||||
el.on('uploadsuccess', function (event, result) {
|
||||
if (result && result !== '' && result !== 'http://') {
|
||||
self.sendAction('uploaded', result);
|
||||
Ember.run(self, function () {
|
||||
this.sendAction('uploaded', result);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
el.on('imagecleared', function () {
|
||||
self.sendAction('canceled');
|
||||
});
|
||||
el.on('imagecleared', Ember.run.bind(self, 'sendAction', 'canceled'));
|
||||
|
||||
this.sendAction('initUploader', ref);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import Ember from 'ember';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
notifications: Ember.inject.service(),
|
||||
application: Ember.inject.controller(),
|
||||
|
||||
postInflection: Ember.computed('model.post_count', function () {
|
||||
return this.get('model.post_count') > 1 ? 'posts' : 'post';
|
||||
@ -9,13 +10,18 @@ export default Ember.Controller.extend({
|
||||
|
||||
actions: {
|
||||
confirmAccept: function () {
|
||||
var tag = this.get('model'),
|
||||
self = this;
|
||||
var tag = this.get('model');
|
||||
|
||||
this.send('closeMenus');
|
||||
|
||||
tag.destroyRecord().catch(function (error) {
|
||||
self.get('notifications').showAPIError(error, {key: 'tag.delete'});
|
||||
tag.destroyRecord().then(() => {
|
||||
let currentRoute = this.get('application.currentRouteName') || '';
|
||||
|
||||
if (currentRoute.match(/^settings\.tags/)) {
|
||||
this.transitionToRoute('settings.tags.index');
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.get('notifications').showAPIError(error, {key: 'tag.delete'});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1,153 +1,21 @@
|
||||
import Ember from 'ember';
|
||||
import SettingsMenuMixin from 'ghost/mixins/settings-menu-controller';
|
||||
import boundOneWay from 'ghost/utils/bound-one-way';
|
||||
|
||||
export default Ember.Controller.extend(SettingsMenuMixin, {
|
||||
tags: Ember.computed.alias('model'),
|
||||
export default Ember.Controller.extend({
|
||||
|
||||
activeTag: null,
|
||||
activeTagNameScratch: boundOneWay('activeTag.name'),
|
||||
activeTagSlugScratch: boundOneWay('activeTag.slug'),
|
||||
activeTagDescriptionScratch: boundOneWay('activeTag.description'),
|
||||
activeTagMetaTitleScratch: boundOneWay('activeTag.meta_title'),
|
||||
activeTagMetaDescriptionScratch: boundOneWay('activeTag.meta_description'),
|
||||
tagListFocused: Ember.computed.equal('keyboardFocus', 'tagList'),
|
||||
tagContentFocused: Ember.computed.equal('keyboardFocus', 'tagContent'),
|
||||
|
||||
application: Ember.inject.controller(),
|
||||
config: Ember.inject.service(),
|
||||
notifications: Ember.inject.service(),
|
||||
tags: Ember.computed.sort('model', function (a, b) {
|
||||
const idA = +a.get('id'),
|
||||
idB = +b.get('id');
|
||||
|
||||
uploaderReference: null,
|
||||
|
||||
// This observer loads and resets the uploader whenever the active tag changes,
|
||||
// ensuring that we can reuse the whole settings menu.
|
||||
updateUploader: Ember.observer('activeTag.image', 'uploaderReference', function () {
|
||||
var uploader = this.get('uploaderReference'),
|
||||
image = this.get('activeTag.image');
|
||||
|
||||
if (uploader && uploader[0]) {
|
||||
if (image) {
|
||||
uploader[0].uploaderUi.initWithImage();
|
||||
} else {
|
||||
uploader[0].uploaderUi.reset();
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
saveActiveTagProperty: function (propKey, newValue) {
|
||||
var activeTag = this.get('activeTag'),
|
||||
currentValue = activeTag.get(propKey),
|
||||
self = this;
|
||||
|
||||
newValue = newValue.trim();
|
||||
|
||||
// Quit if there was no change
|
||||
if (newValue === currentValue) {
|
||||
return;
|
||||
if (idA > idB) {
|
||||
return 1;
|
||||
} else if (idA < idB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
activeTag.set(propKey, newValue);
|
||||
activeTag.get('hasValidated').addObject(propKey);
|
||||
return 0;
|
||||
})
|
||||
|
||||
activeTag.save().catch(function (error) {
|
||||
if (error) {
|
||||
self.get('notifications').showAPIError(error, {key: 'tag.save'});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
seoTitle: Ember.computed('scratch', 'activeTagNameScratch', 'activeTagMetaTitleScratch', function () {
|
||||
var metaTitle = this.get('activeTagMetaTitleScratch') || '';
|
||||
|
||||
metaTitle = metaTitle.length > 0 ? metaTitle : this.get('activeTagNameScratch');
|
||||
|
||||
if (metaTitle && metaTitle.length > 70) {
|
||||
metaTitle = metaTitle.substring(0, 70).trim();
|
||||
metaTitle = Ember.Handlebars.Utils.escapeExpression(metaTitle);
|
||||
metaTitle = Ember.String.htmlSafe(metaTitle + '…');
|
||||
}
|
||||
|
||||
return metaTitle;
|
||||
}),
|
||||
|
||||
seoURL: Ember.computed('activeTagSlugScratch', function () {
|
||||
var blogUrl = this.get('config.blogUrl'),
|
||||
seoSlug = this.get('activeTagSlugScratch') ? this.get('activeTagSlugScratch') : '',
|
||||
seoURL = blogUrl + '/tag/' + seoSlug;
|
||||
|
||||
// only append a slash to the URL if the slug exists
|
||||
if (seoSlug) {
|
||||
seoURL += '/';
|
||||
}
|
||||
|
||||
if (seoURL.length > 70) {
|
||||
seoURL = seoURL.substring(0, 70).trim();
|
||||
seoURL = Ember.String.htmlSafe(seoURL + '…');
|
||||
}
|
||||
|
||||
return seoURL;
|
||||
}),
|
||||
|
||||
seoDescription: Ember.computed('scratch', 'activeTagDescriptionScratch', 'activeTagMetaDescriptionScratch', function () {
|
||||
var metaDescription = this.get('activeTagMetaDescriptionScratch') || '';
|
||||
|
||||
metaDescription = metaDescription.length > 0 ? metaDescription : this.get('activeTagDescriptionScratch');
|
||||
|
||||
if (metaDescription && metaDescription.length > 156) {
|
||||
metaDescription = metaDescription.substring(0, 156).trim();
|
||||
metaDescription = Ember.Handlebars.Utils.escapeExpression(metaDescription);
|
||||
metaDescription = Ember.String.htmlSafe(metaDescription + '…');
|
||||
}
|
||||
|
||||
return metaDescription;
|
||||
}),
|
||||
|
||||
actions: {
|
||||
newTag: function () {
|
||||
this.set('activeTag', this.store.createRecord('tag', {post_count: 0}));
|
||||
this.get('activeTag.errors').clear();
|
||||
this.send('openSettingsMenu');
|
||||
},
|
||||
|
||||
editTag: function (tag) {
|
||||
tag.validate();
|
||||
this.set('activeTag', tag);
|
||||
this.send('openSettingsMenu');
|
||||
},
|
||||
|
||||
saveActiveTagName: function (name) {
|
||||
this.saveActiveTagProperty('name', name);
|
||||
},
|
||||
|
||||
saveActiveTagSlug: function (slug) {
|
||||
this.saveActiveTagProperty('slug', slug);
|
||||
},
|
||||
|
||||
saveActiveTagDescription: function (description) {
|
||||
this.saveActiveTagProperty('description', description);
|
||||
},
|
||||
|
||||
saveActiveTagMetaTitle: function (metaTitle) {
|
||||
this.saveActiveTagProperty('meta_title', metaTitle);
|
||||
},
|
||||
|
||||
saveActiveTagMetaDescription: function (metaDescription) {
|
||||
this.saveActiveTagProperty('meta_description', metaDescription);
|
||||
},
|
||||
|
||||
setCoverImage: function (image) {
|
||||
this.saveActiveTagProperty('image', image);
|
||||
},
|
||||
|
||||
clearCoverImage: function () {
|
||||
this.saveActiveTagProperty('image', '');
|
||||
},
|
||||
|
||||
closeNavMenu: function () {
|
||||
this.get('application').send('closeNavMenu');
|
||||
},
|
||||
|
||||
setUploaderReference: function (ref) {
|
||||
this.set('uploaderReference', ref);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
40
ghost/admin/app/controllers/settings/tags/tag.js
Normal file
40
ghost/admin/app/controllers/settings/tags/tag.js
Normal file
@ -0,0 +1,40 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
const {computed} = Ember,
|
||||
{alias} = computed;
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
|
||||
tag: alias('model'),
|
||||
|
||||
saveTagProperty: function (propKey, newValue) {
|
||||
const tag = this.get('tag'),
|
||||
currentValue = tag.get(propKey);
|
||||
|
||||
newValue = newValue.trim();
|
||||
|
||||
// Quit if there was no change
|
||||
if (newValue === currentValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
tag.set(propKey, newValue);
|
||||
// TODO: This is required until .validate/.save mark fields as validated
|
||||
tag.get('hasValidated').addObject(propKey);
|
||||
|
||||
tag.save().then((savedTag) => {
|
||||
// replace 'new' route with 'tag' route
|
||||
this.replaceWith('settings.tags.tag', savedTag);
|
||||
}).catch((error) => {
|
||||
if (error) {
|
||||
this.notifications.showAPIError(error, {key: 'tag.save'});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
setProperty: function (propKey, value) {
|
||||
this.saveTagProperty(propKey, value);
|
||||
}
|
||||
}
|
||||
});
|
@ -60,7 +60,9 @@ export default Ember.Mixin.create({
|
||||
key(shortcut, scope, function (event) {
|
||||
// stop things like ctrl+s from actually opening a save dialogue
|
||||
event.preventDefault();
|
||||
self.send(action, options);
|
||||
Ember.run(self, function () {
|
||||
this.send(action, options);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -43,7 +43,10 @@ Router.map(function () {
|
||||
});
|
||||
|
||||
this.route('settings.general', {path: '/settings/general'});
|
||||
this.route('settings.tags', {path: '/settings/tags'});
|
||||
this.route('settings.tags', {path: '/settings/tags'}, function () {
|
||||
this.route('tag', {path: ':tag_id'});
|
||||
this.route('new');
|
||||
});
|
||||
this.route('settings.labs', {path: '/settings/labs'});
|
||||
this.route('settings.code-injection', {path: '/settings/code-injection'});
|
||||
this.route('settings.navigation', {path: '/settings/navigation'});
|
||||
|
@ -1,8 +1,10 @@
|
||||
import Ember from 'ember';
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
import CurrentUserSettings from 'ghost/mixins/current-user-settings';
|
||||
import PaginationRouteMixin from 'ghost/mixins/pagination-route';
|
||||
import ShortcutsRoute from 'ghost/mixins/shortcuts-route';
|
||||
import PaginationRoute from 'ghost/mixins/pagination-route';
|
||||
|
||||
export default AuthenticatedRoute.extend(CurrentUserSettings, PaginationRouteMixin, {
|
||||
export default AuthenticatedRoute.extend(CurrentUserSettings, PaginationRoute, ShortcutsRoute, {
|
||||
titleToken: 'Settings - Tags',
|
||||
|
||||
paginationModel: 'tag',
|
||||
@ -11,6 +13,14 @@ export default AuthenticatedRoute.extend(CurrentUserSettings, PaginationRouteMix
|
||||
limit: 15
|
||||
},
|
||||
|
||||
shortcuts: {
|
||||
'up, k': 'moveUp',
|
||||
'down, j': 'moveDown',
|
||||
left: 'focusList',
|
||||
right: 'focusContent',
|
||||
c: 'newTag'
|
||||
},
|
||||
|
||||
beforeModel: function () {
|
||||
this._super(...arguments);
|
||||
|
||||
@ -20,22 +30,70 @@ export default AuthenticatedRoute.extend(CurrentUserSettings, PaginationRouteMix
|
||||
|
||||
model: function () {
|
||||
this.store.unloadAll('tag');
|
||||
this.loadFirstPage();
|
||||
|
||||
return this.store.filter('tag', function (tag) {
|
||||
return !tag.get('isNew');
|
||||
});
|
||||
},
|
||||
|
||||
renderTemplate: function (controller, model) {
|
||||
this._super(controller, model);
|
||||
this.render('settings/tags/settings-menu', {
|
||||
into: 'application',
|
||||
outlet: 'settings-menu'
|
||||
return this.loadFirstPage().then(() => {
|
||||
return this.store.filter('tag', (tag) => {
|
||||
return !tag.get('isNew');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
deactivate: function () {
|
||||
this.send('resetPagination');
|
||||
},
|
||||
|
||||
stepThroughTags: function (step) {
|
||||
let currentTag = this.modelFor('settings.tags.tag'),
|
||||
tags = this.get('controller.tags'),
|
||||
length = tags.get('length');
|
||||
|
||||
if (currentTag && length) {
|
||||
let newPosition = tags.indexOf(currentTag) + step;
|
||||
|
||||
if (newPosition >= length) {
|
||||
return;
|
||||
} else if (newPosition < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.transitionTo('settings.tags.tag', tags.objectAt(newPosition));
|
||||
}
|
||||
},
|
||||
|
||||
scrollContent: function (amount) {
|
||||
let content = Ember.$('.tag-settings-pane'),
|
||||
scrolled = content.scrollTop();
|
||||
|
||||
content.scrollTop(scrolled + 50 * amount);
|
||||
},
|
||||
|
||||
actions: {
|
||||
moveUp: function () {
|
||||
if (this.controller.get('tagContentFocused')) {
|
||||
this.scrollContent(-1);
|
||||
} else {
|
||||
this.stepThroughTags(-1);
|
||||
}
|
||||
},
|
||||
|
||||
moveDown: function () {
|
||||
if (this.controller.get('tagContentFocused')) {
|
||||
this.scrollContent(1);
|
||||
} else {
|
||||
this.stepThroughTags(1);
|
||||
}
|
||||
},
|
||||
|
||||
focusList: function () {
|
||||
this.set('controller.keyboardFocus', 'tagList');
|
||||
},
|
||||
|
||||
focusContent: function () {
|
||||
this.set('controller.keyboardFocus', 'tagContent');
|
||||
},
|
||||
|
||||
newTag: function () {
|
||||
this.transitionTo('settings.tags.new');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
13
ghost/admin/app/routes/settings/tags/index.js
Normal file
13
ghost/admin/app/routes/settings/tags/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
|
||||
export default AuthenticatedRoute.extend({
|
||||
|
||||
beforeModel: function () {
|
||||
const firstTag = this.modelFor('settings.tags').get('firstObject');
|
||||
|
||||
if (firstTag) {
|
||||
this.transitionTo('settings.tags.tag', firstTag);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
15
ghost/admin/app/routes/settings/tags/new.js
Normal file
15
ghost/admin/app/routes/settings/tags/new.js
Normal file
@ -0,0 +1,15 @@
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
|
||||
export default AuthenticatedRoute.extend({
|
||||
|
||||
controllerName: 'settings.tags.tag',
|
||||
|
||||
model: function () {
|
||||
return this.store.createRecord('tag');
|
||||
},
|
||||
|
||||
renderTemplate: function () {
|
||||
this.render('settings.tags.tag');
|
||||
}
|
||||
|
||||
});
|
9
ghost/admin/app/routes/settings/tags/tag.js
Normal file
9
ghost/admin/app/routes/settings/tags/tag.js
Normal file
@ -0,0 +1,9 @@
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
|
||||
export default AuthenticatedRoute.extend({
|
||||
|
||||
model: function (params) {
|
||||
return this.store.findRecord('tag', params.tag_id);
|
||||
}
|
||||
|
||||
});
|
@ -267,7 +267,9 @@
|
||||
|
||||
|
||||
/* This has to be a pseudo element to sit over the top of everything else in the content list */
|
||||
.content-list.keyboard-focused:before {
|
||||
.content-list.keyboard-focused:before,
|
||||
.tag-list-content.keyboard-focused:before,
|
||||
.tag-settings.keyboard-focused:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -2,69 +2,6 @@
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
|
||||
/* Search
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.tags-search {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.tags-search .btn {
|
||||
position: relative;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
transition: padding 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.tags-search .btn.active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tags-search .btn .icon-search:before {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.tags-search .tags-search-input {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
margin: 0;
|
||||
padding: 7px 10px;
|
||||
width: 0;
|
||||
border: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.tags-search.opened .btn {
|
||||
padding-left: 120px;
|
||||
}
|
||||
.tags-search.opened .tags-search-input {
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 401px) {
|
||||
.tags-search.opened .btn {
|
||||
padding-left: 140px;
|
||||
}
|
||||
.tags-search.opened .tags-search-input {
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-search.opened .tags-search-input {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
|
||||
/* Tag
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
@ -76,15 +13,14 @@
|
||||
}
|
||||
|
||||
.settings-tag .tag-edit-button {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
width: calc(100% + 45px);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings-tag .tag-edit-button:hover,
|
||||
.settings-tag .tag-edit-button:focus,
|
||||
.settings-tag .tag-edit-button:active {
|
||||
background: color(#dfe1e3 lightness(+10%));
|
||||
.settings-tag .tag-edit-button.active {
|
||||
border-left: 3px solid;
|
||||
}
|
||||
|
||||
.settings-tag:last-of-type:hover .tag-edit-button {
|
||||
@ -92,9 +28,12 @@
|
||||
}
|
||||
|
||||
.settings-tag .label {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 2px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.settings-tag .label-alt {
|
||||
@ -121,3 +60,40 @@
|
||||
color: color(#dfe1e3 lightness(-10%));
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Tag List (Left pane)
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.tag-list {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
max-width: calc(100% - 350px);
|
||||
width: 66%;
|
||||
border-right: #dfe1e3 1px solid;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* Tag Settings (Right pane)
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.tag-settings {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
min-width: 350px;
|
||||
width: 34%;
|
||||
border: none;
|
||||
background: #fff;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.tag-settings .no-posts h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
<div class="{{if isViewingSubview 'settings-menu-pane-out-left' 'settings-menu-pane-in'}} settings-menu settings-menu-pane tag-settings-pane">
|
||||
<div class="settings-menu-header">
|
||||
<h4>{{title}}</h4>
|
||||
</div>
|
||||
<div class="settings-menu-content">
|
||||
{{gh-uploader uploaded="setCoverImage" canceled="clearCoverImage" description="Add tag image" image=tag.image initUploader="setUploaderReference" tagName="section"}}
|
||||
<form>
|
||||
{{#gh-form-group errors=tag.errors hasValidated=tag.hasValidated property="name"}}
|
||||
<label for="tag-name">Name</label>
|
||||
{{gh-input id="tag-name" name="name" type="text" value=scratchName focus-out=(action 'setProperty' 'name')}}
|
||||
{{gh-error-message errors=tag.errors property="name"}}
|
||||
{{/gh-form-group}}
|
||||
|
||||
{{#gh-form-group errors=tag.errors hasValidated=tag.hasValidated property="slug"}}
|
||||
<label for="tag-slug">URL</label>
|
||||
{{gh-input id="tag-slug" name="slug" type="text" value=scratchSlug focus-out=(action 'setProperty' 'slug')}}
|
||||
{{gh-url-preview prefix="tag" slug=scratchSlug tagName="p" classNames="description"}}
|
||||
{{gh-error-message errors=activeTag.errors property="slug"}}
|
||||
{{/gh-form-group}}
|
||||
|
||||
{{#gh-form-group errors=tag.errors hasValidated=tag.hasValidated property="description"}}
|
||||
<label for="tag-description">Description</label>
|
||||
{{gh-textarea id="tag-description" name="description" value=scratchDescription focus-out=(action 'setProperty' 'description')}}
|
||||
{{gh-error-message errors=tag.errors property="description"}}
|
||||
<p>Maximum: <b>200</b> characters. You’ve used {{gh-count-down-characters scratchDescription 200}}</p>
|
||||
{{/gh-form-group}}
|
||||
|
||||
<ul class="nav-list nav-list-block">
|
||||
<li class="nav-list-item" {{action 'openMeta'}}>
|
||||
<button type="button" class="meta-data-button">
|
||||
<b>Meta Data</b>
|
||||
<span>Extra content for SEO and social media.</span>
|
||||
</button>
|
||||
<i class="icon-arrow-right"></i>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{#unless tag.isNew}}
|
||||
<button type="button" class="btn btn-link btn-sm tag-delete-button" {{action "deleteTag"}}><i class="icon-trash"></i> Delete Tag</button>
|
||||
{{/unless}}
|
||||
</form>
|
||||
</div>
|
||||
</div>{{! .settings-menu-pane }}
|
||||
|
||||
<div class="{{if isViewingSubview 'settings-menu-pane-in' 'settings-menu-pane-out-right'}} settings-menu settings-menu-pane tag-meta-settings-pane">
|
||||
<div class="settings-menu-header subview">
|
||||
<button {{action "closeMeta"}} class="back icon-arrow-left settings-menu-header-action"><span class="hidden">Back</span></button>
|
||||
<h4>Meta Data</h4>
|
||||
<div style="width:23px;">{{!flexbox space-between}}</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-menu-content">
|
||||
<form>
|
||||
{{#gh-form-group errors=tag.errors hasValidated=tag.hasValidated property="meta_title"}}
|
||||
<label for="meta-title">Meta Title</label>
|
||||
{{gh-input id="meta-title" name="meta_title" type="text" value=scratchMetaTitle focus-out=(action 'setProperty' 'meta_title')}}
|
||||
{{gh-error-message errors=tag.errors property="meta_title"}}
|
||||
<p>Recommended: <b>70</b> characters. You’ve used {{gh-count-down-characters scratchMetaTitle 70}}</p>
|
||||
{{/gh-form-group}}
|
||||
|
||||
{{#gh-form-group errors=tag.errors hasValidated=tag.hasValidated property="meta_description"}}
|
||||
<label for="meta-description">Meta Description</label>
|
||||
{{gh-textarea id="meta-description" name="meta_description" value=scratchMetaDescription focus-out=(action 'setProperty' 'meta_description')}}
|
||||
{{gh-error-message errors=tag.errors property="meta_description"}}
|
||||
<p>Recommended: <b>156</b> characters. You’ve used {{gh-count-down-characters scratchMetaDescription 156}}</p>
|
||||
{{/gh-form-group}}
|
||||
|
||||
<div class="form-group">
|
||||
<label>Search Engine Result Preview</label>
|
||||
<div class="seo-preview">
|
||||
<div class="seo-preview-title">{{seoTitle}}</div>
|
||||
<div class="seo-preview-link">{{seoURL}}</div>
|
||||
<div class="seo-preview-description">{{seoDescription}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -2,25 +2,32 @@
|
||||
<header class="view-header">
|
||||
{{#gh-view-title openMobileMenu="openMobileMenu"}}<span>Tags</span>{{/gh-view-title}}
|
||||
<section class="view-actions">
|
||||
<button type="button" class="btn btn-green" {{action "newTag"}}>New Tag</button>
|
||||
{{#link-to "settings.tags.new" class="btn btn-green" title="New Tag"}}New Tag{{/link-to}}
|
||||
{{!-- <button type="button" class="btn btn-green" {{action "newTag"}}>New Tag</button> --}}
|
||||
</section>
|
||||
</header>
|
||||
|
||||
{{#gh-infinite-scroll
|
||||
fetch="loadNextPage"
|
||||
isLoading=isLoading
|
||||
tagName="section"
|
||||
classNames="view-container settings-tags"
|
||||
}}
|
||||
{{#each tags as |tag|}}
|
||||
<div class="settings-tag">
|
||||
<button class="tag-edit-button" {{action "editTag" tag}}>
|
||||
<span class="tag-title">{{tag.name}}</span>
|
||||
<span class="label label-default">/{{tag.slug}}</span>
|
||||
<p class="tag-description">{{tag.description}}</p>
|
||||
<span class="tags-count">{{tag.post_count}}</span>
|
||||
</button>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/gh-infinite-scroll}}
|
||||
<div class="view-container">
|
||||
{{#gh-infinite-scroll
|
||||
fetch="loadNextPage"
|
||||
isLoading=isLoading
|
||||
classNames="tag-list"
|
||||
}}
|
||||
<section class="tag-list-content settings-tags {{if tagListFocused 'keyboard-focused'}}">
|
||||
{{#each tags as |tag|}}
|
||||
<div class="settings-tag">
|
||||
{{#link-to 'settings.tags.tag' tag.id class="tag-edit-button"}}
|
||||
<span class="tag-title">{{tag.name}}</span>
|
||||
<span class="label label-default">/{{tag.slug}}</span>
|
||||
<p class="tag-description">{{tag.description}}</p>
|
||||
<span class="tags-count">{{tag.post_count}}</span>
|
||||
{{/link-to}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</section>
|
||||
{{/gh-infinite-scroll}}
|
||||
<section class="settings-menu-container tag-settings {{if tagContentFocused 'keyboard-focused'}}">
|
||||
{{outlet}}
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
6
ghost/admin/app/templates/settings/tags/index.hbs
Normal file
6
ghost/admin/app/templates/settings/tags/index.hbs
Normal file
@ -0,0 +1,6 @@
|
||||
<div class="no-posts-box">
|
||||
<div class="no-posts">
|
||||
<h3>You haven't added any Tags yet!</h3>
|
||||
{{#link-to "settings.tags.new"}}<button type="button" class="btn btn-green btn-lg" title="New Tag">Add a Tag</button>{{/link-to}}
|
||||
</div>
|
||||
</div>
|
1
ghost/admin/app/templates/settings/tags/tag.hbs
Normal file
1
ghost/admin/app/templates/settings/tags/tag.hbs
Normal file
@ -0,0 +1 @@
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action "setProperty") openModal="openModal"}}
|
@ -50,11 +50,7 @@ export default function () {
|
||||
}),
|
||||
|
||||
setTitle: function (title) {
|
||||
if (Ember.testing) {
|
||||
this._title = title;
|
||||
} else {
|
||||
window.document.title = title;
|
||||
}
|
||||
window.document.title = title;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -10,60 +10,9 @@ import Ember from 'ember';
|
||||
import startApp from '../../helpers/start-app';
|
||||
import Pretender from 'pretender';
|
||||
import { invalidateSession, authenticateSession } from 'ghost/tests/helpers/ember-simple-auth';
|
||||
import requiredSettings from '../../fixtures/settings';
|
||||
|
||||
const {run} = Ember,
|
||||
// TODO: Pull this into a fixture or similar when required elsewhere
|
||||
requiredSettings = [{
|
||||
created_at: '2015-09-11T09:44:30.805Z',
|
||||
created_by: 1,
|
||||
id: 5,
|
||||
key: 'title',
|
||||
type: 'blog',
|
||||
updated_at: '2015-10-04T16:26:05.195Z',
|
||||
updated_by: 1,
|
||||
uuid: '39e16daf-43fa-4bf0-87d4-44948ba8bf4c',
|
||||
value: 'The Daily Awesome'
|
||||
}, {
|
||||
created_at: '2015-09-11T09:44:30.806Z',
|
||||
created_by: 1,
|
||||
id: 6,
|
||||
key: 'description',
|
||||
type: 'blog',
|
||||
updated_at: '2015-10-04T16:26:05.198Z',
|
||||
updated_by: 1,
|
||||
uuid: 'e6c8b636-6925-4c4a-a5d9-1dc0870fb8ea',
|
||||
value: 'Thoughts, stories and ideas.'
|
||||
}, {
|
||||
created_at: '2015-09-11T09:44:30.809Z',
|
||||
created_by: 1,
|
||||
id: 10,
|
||||
key: 'postsPerPage',
|
||||
type: 'blog',
|
||||
updated_at: '2015-10-04T16:26:05.211Z',
|
||||
updated_by: 1,
|
||||
uuid: '775e6ca1-bcc3-4347-a53d-15d5d76c04a4',
|
||||
value: '5'
|
||||
}, {
|
||||
created_at: '2015-09-11T09:44:30.809Z',
|
||||
created_by: 1,
|
||||
id: 13,
|
||||
key: 'ghost_head',
|
||||
type: 'blog',
|
||||
updated_at: '2015-09-23T13:32:49.858Z',
|
||||
updated_by: 1,
|
||||
uuid: 'df7f3151-bc08-4a77-be9d-dd315b630d51',
|
||||
value: ''
|
||||
}, {
|
||||
created_at: '2015-09-11T09:44:30.809Z',
|
||||
created_by: 1,
|
||||
id: 14,
|
||||
key: 'ghost_foot',
|
||||
type: 'blog',
|
||||
updated_at: '2015-09-23T13:32:49.858Z',
|
||||
updated_by: 1,
|
||||
uuid: '0649d45e-828b-4dd0-8381-3dff6d1d5ddb',
|
||||
value: ''
|
||||
}];
|
||||
const {run} = Ember;
|
||||
|
||||
describe('Acceptance: Settings - Navigation', function () {
|
||||
let application,
|
||||
@ -72,7 +21,7 @@ describe('Acceptance: Settings - Navigation', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
application = startApp();
|
||||
store = application.__container__.lookup('store:main');
|
||||
store = application.__container__.lookup('service:store');
|
||||
server = new Pretender(function () {
|
||||
// TODO: This needs to either be fleshed out to include all user data, or be killed with fire
|
||||
// as it needs to be loaded with all authenticated page loads
|
||||
|
375
ghost/admin/tests/acceptance/settings/tags-test.js
Normal file
375
ghost/admin/tests/acceptance/settings/tags-test.js
Normal file
@ -0,0 +1,375 @@
|
||||
/* jshint expr:true */
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import Ember from 'ember';
|
||||
import startApp from '../../helpers/start-app';
|
||||
import Pretender from 'pretender';
|
||||
import { invalidateSession, authenticateSession } from 'ghost/tests/helpers/ember-simple-auth';
|
||||
import requiredSettings from '../../fixtures/settings';
|
||||
|
||||
const {run} = Ember,
|
||||
// Grabbed from keymaster's testing code because Ember's `keyEvent` helper
|
||||
// is for some reason not triggering the events in a way that keymaster detects:
|
||||
// https://github.com/madrobby/keymaster/blob/master/test/keymaster.html#L31
|
||||
modifierMap = {
|
||||
16:'shiftKey',
|
||||
18:'altKey',
|
||||
17:'ctrlKey',
|
||||
91:'metaKey'
|
||||
},
|
||||
keydown = function (code, modifiers, el) {
|
||||
let event = document.createEvent('Event');
|
||||
event.initEvent('keydown', true, true);
|
||||
event.keyCode = code;
|
||||
if (modifiers && modifiers.length > 0) {
|
||||
for (let i in modifiers) {
|
||||
event[modifierMap[modifiers[i]]] = true;
|
||||
}
|
||||
}
|
||||
(el || document).dispatchEvent(event);
|
||||
},
|
||||
keyup = function (code, el) {
|
||||
let event = document.createEvent('Event');
|
||||
event.initEvent('keyup', true, true);
|
||||
event.keyCode = code;
|
||||
(el || document).dispatchEvent(event);
|
||||
};
|
||||
|
||||
describe('Acceptance: Settings - Tags', function () {
|
||||
let application,
|
||||
store,
|
||||
server,
|
||||
roleName;
|
||||
|
||||
beforeEach(function () {
|
||||
application = startApp();
|
||||
store = application.__container__.lookup('service:store');
|
||||
server = new Pretender(function () {
|
||||
// TODO: This needs to either be fleshed out to include all user data, or be killed with fire
|
||||
// as it needs to be loaded with all authenticated page loads
|
||||
this.get('/ghost/api/v0.1/users/me', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify({users: [{
|
||||
id: '1',
|
||||
roles: [{
|
||||
id: 1,
|
||||
name: roleName,
|
||||
slug: 'barry'
|
||||
}]
|
||||
}]})];
|
||||
});
|
||||
|
||||
this.get('/ghost/api/v0.1/settings/', function (_request) {
|
||||
let response = {meta: {filters: 'blog,theme'}};
|
||||
response.settings = requiredSettings;
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify(response)];
|
||||
});
|
||||
|
||||
// TODO: This will be needed for all authenticated page loads
|
||||
// - is there some way to make this a default?
|
||||
this.get('/ghost/api/v0.1/notifications/', function (_request) {
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify({notifications: []})];
|
||||
});
|
||||
|
||||
this.get('/ghost/api/v0.1/tags/', function (_request) {
|
||||
let response = {};
|
||||
|
||||
response.meta = {
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 15,
|
||||
pages: 1,
|
||||
total: 2,
|
||||
next: null,
|
||||
prev: null
|
||||
}
|
||||
};
|
||||
|
||||
response.tags = [
|
||||
{
|
||||
id: 1,
|
||||
parent: null,
|
||||
uuid: 'e2016ef1-4b51-46ff-9388-c6f066fc2e6c',
|
||||
image: '/content/images/2015/10/tag-1.jpg',
|
||||
name: 'Tag One',
|
||||
slug: 'tag-one',
|
||||
description: 'Description one.',
|
||||
meta_title: 'Meta Title One',
|
||||
meta_description: 'Meta description one.',
|
||||
created_at: '2015-09-11T09:44:29.871Z',
|
||||
created_by: 1,
|
||||
updated_at: '2015-10-19T16:25:07.756Z',
|
||||
updated_by: 1,
|
||||
hidden: false,
|
||||
post_count: 1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
parent: null,
|
||||
uuid: '0cade0f9-7a3f-4fd1-a80a-3a1ab7028340',
|
||||
image: '/content/images/2015/10/tag-2.jpg',
|
||||
name: 'Tag Two',
|
||||
slug: 'tag-two',
|
||||
description: 'Description two.',
|
||||
meta_title: 'Meta Title Two',
|
||||
meta_description: 'Meta description two.',
|
||||
created_at: '2015-09-11T09:44:29.871Z',
|
||||
created_by: 1,
|
||||
updated_at: '2015-10-19T16:25:07.756Z',
|
||||
updated_by: 1,
|
||||
hidden: false,
|
||||
post_count: 2
|
||||
}
|
||||
];
|
||||
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify(response)];
|
||||
});
|
||||
|
||||
this.put('/ghost/api/v0.1/tags/2/', function (_request) {
|
||||
let response = {};
|
||||
|
||||
response.tag = {
|
||||
id: 2,
|
||||
parent: null,
|
||||
uuid: '0cade0f9-7a3f-4fd1-a80a-3a1ab7028340',
|
||||
image: '/content/images/2015/10/tag-2.jpg',
|
||||
name: 'Saved Tag',
|
||||
slug: 'tag-two',
|
||||
description: 'Description two.',
|
||||
meta_title: 'Meta Title Two',
|
||||
meta_description: 'Meta description two.',
|
||||
created_at: '2015-09-11T09:44:29.871Z',
|
||||
created_by: 1,
|
||||
updated_at: '2015-10-19T16:25:07.756Z',
|
||||
updated_by: 1,
|
||||
hidden: false,
|
||||
post_count: 2
|
||||
};
|
||||
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify(response)];
|
||||
});
|
||||
|
||||
this.post('/ghost/api/v0.1/tags/', function (_request) {
|
||||
let response = {};
|
||||
|
||||
response.tag = {
|
||||
id: 3,
|
||||
parent: null,
|
||||
uuid: 'de9f4636-0398-4e23-a963-e073d12bc511',
|
||||
image: '/content/images/2015/10/tag-3.jpg',
|
||||
name: 'Tag Three',
|
||||
slug: 'tag-three',
|
||||
description: 'Description three.',
|
||||
meta_title: 'Meta Title Three',
|
||||
meta_description: 'Meta description three.',
|
||||
created_at: '2015-09-11T09:44:29.871Z',
|
||||
created_by: 1,
|
||||
updated_at: '2015-10-19T16:25:07.756Z',
|
||||
updated_by: 1,
|
||||
hidden: false,
|
||||
post_count: 2
|
||||
};
|
||||
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify(response)];
|
||||
});
|
||||
|
||||
this.delete('/ghost/api/v0.1/tags/3/', function (_request) {
|
||||
let response = {tags: []};
|
||||
|
||||
response.tags.push({
|
||||
id: 3,
|
||||
parent: null,
|
||||
uuid: 'de9f4636-0398-4e23-a963-e073d12bc511',
|
||||
image: '/content/images/2015/10/tag-3.jpg',
|
||||
name: 'Tag Three',
|
||||
slug: 'tag-three',
|
||||
description: 'Description three.',
|
||||
meta_title: 'Meta Title Three',
|
||||
meta_description: 'Meta description three.',
|
||||
created_at: '2015-09-11T09:44:29.871Z',
|
||||
created_by: 1,
|
||||
updated_at: '2015-10-19T16:25:07.756Z',
|
||||
updated_by: 1,
|
||||
hidden: false,
|
||||
post_count: 2
|
||||
});
|
||||
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify(response)];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Ember.run(application, 'destroy');
|
||||
});
|
||||
|
||||
it('redirects to signin when not authenticated', function () {
|
||||
invalidateSession(application);
|
||||
visit('/settings/tags');
|
||||
|
||||
andThen(() => {
|
||||
expect(currentURL()).to.equal('/signin');
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to team page when authenticated as author', function () {
|
||||
roleName = 'Author';
|
||||
authenticateSession(application);
|
||||
visit('/settings/navigation');
|
||||
|
||||
andThen(() => {
|
||||
expect(currentURL()).to.match(/^\/team\//);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when logged in', function () {
|
||||
beforeEach(function () {
|
||||
roleName = 'Administrator';
|
||||
authenticateSession(application);
|
||||
});
|
||||
|
||||
it('it renders, can be navigated, can edit, create & delete tags', function () {
|
||||
visit('/settings/tags');
|
||||
|
||||
andThen(() => {
|
||||
// it redirects to first tag
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/tags/1');
|
||||
|
||||
// it has correct page title
|
||||
expect(document.title, 'page title').to.equal('Settings - Tags - Test Blog');
|
||||
|
||||
// it highlights nav menu
|
||||
expect($('.gh-nav-settings-tags').hasClass('active'), 'highlights nav menu item')
|
||||
.to.be.true;
|
||||
|
||||
// it lists all tags
|
||||
expect(find('.settings-tags .settings-tag').length, 'tag list count')
|
||||
.to.equal(2);
|
||||
expect(find('.settings-tags .settings-tag:first .tag-title').text(), 'tag list item title')
|
||||
.to.equal('Tag One');
|
||||
|
||||
// it highlights selected tag
|
||||
expect(find('a[href="/settings/tags/1"]').hasClass('active'), 'highlights selected tag')
|
||||
.to.be.true;
|
||||
|
||||
// it shows selected tag form
|
||||
expect(find('.tag-settings-pane h4').text(), 'settings pane title')
|
||||
.to.equal('Tag Settings');
|
||||
expect(find('.tag-settings-pane input[name="name"]').val(), 'loads correct tag into form')
|
||||
.to.equal('Tag One');
|
||||
});
|
||||
|
||||
// click the second tag in the list
|
||||
click('.tag-edit-button:last');
|
||||
|
||||
andThen(() => {
|
||||
// it navigates to selected tag
|
||||
expect(currentURL(), 'url after clicking tag').to.equal('/settings/tags/2');
|
||||
|
||||
// it highlights selected tag
|
||||
expect(find('a[href="/settings/tags/2"]').hasClass('active'), 'highlights selected tag')
|
||||
.to.be.true;
|
||||
|
||||
// it shows selected tag form
|
||||
expect(find('.tag-settings-pane input[name="name"]').val(), 'loads correct tag into form')
|
||||
.to.equal('Tag Two');
|
||||
});
|
||||
|
||||
andThen(() => {
|
||||
// simulate up arrow press
|
||||
run(() => {
|
||||
keydown(38);
|
||||
keyup(38);
|
||||
});
|
||||
|
||||
// it navigates to previous tag
|
||||
expect(currentURL(), 'url after keyboard up arrow').to.equal('/settings/tags/1');
|
||||
|
||||
// it highlights selected tag
|
||||
expect(find('a[href="/settings/tags/1"]').hasClass('active'), 'selects previous tag')
|
||||
.to.be.true;
|
||||
});
|
||||
|
||||
andThen(() => {
|
||||
// simulate down arrow press
|
||||
run(() => {
|
||||
keydown(40);
|
||||
keyup(40);
|
||||
});
|
||||
|
||||
// it navigates to previous tag
|
||||
expect(currentURL(), 'url after keyboard down arrow').to.equal('/settings/tags/2');
|
||||
|
||||
// it highlights selected tag
|
||||
expect(find('a[href="/settings/tags/2"]').hasClass('active'), 'selects next tag')
|
||||
.to.be.true;
|
||||
});
|
||||
|
||||
// trigger save
|
||||
fillIn('.tag-settings-pane input[name="name"]', 'New Name');
|
||||
triggerEvent('.tag-settings-pane input[name="name"]', 'blur');
|
||||
|
||||
andThen(() => {
|
||||
// check we update with the data returned from the server
|
||||
expect(find('.settings-tags .settings-tag:last .tag-title').text(), 'tag list updates on save')
|
||||
.to.equal('Saved Tag');
|
||||
expect(find('.tag-settings-pane input[name="name"]').val(), 'settings form updates on save')
|
||||
.to.equal('Saved Tag');
|
||||
});
|
||||
|
||||
// start new tag
|
||||
click('.view-actions .btn-green');
|
||||
|
||||
andThen(() => {
|
||||
// it navigates to the new tag route
|
||||
expect(currentURL(), 'new tag URL').to.equal('/settings/tags/new');
|
||||
|
||||
// it displays the new tag form
|
||||
expect(find('.tag-settings-pane h4').text(), 'settings pane title')
|
||||
.to.equal('New Tag');
|
||||
|
||||
// all fields start blank
|
||||
find('.tag-settings-pane input, .tag-settings-pane textarea').each(function () {
|
||||
expect($(this).val(), `input field for ${$(this).attr('name')}`)
|
||||
.to.be.blank;
|
||||
});
|
||||
});
|
||||
|
||||
// save new tag
|
||||
fillIn('.tag-settings-pane input[name="name"]', 'New Tag');
|
||||
triggerEvent('.tag-settings-pane input[name="name"]', 'blur');
|
||||
|
||||
andThen(() => {
|
||||
// it redirects to the new tag's URL
|
||||
expect(currentURL(), 'URL after tag creation').to.equal('/settings/tags/3');
|
||||
|
||||
// it adds the tag to the list and selects
|
||||
expect(find('.settings-tags .settings-tag').length, 'tag list count after creation')
|
||||
.to.equal(3);
|
||||
expect(find('.settings-tags .settings-tag:last .tag-title').text(), 'new tag list item title')
|
||||
.to.equal('Tag Three');
|
||||
expect(find('a[href="/settings/tags/3"]').hasClass('active'), 'highlights new tag')
|
||||
.to.be.true;
|
||||
});
|
||||
|
||||
// delete tag
|
||||
click('.tag-delete-button');
|
||||
click('.modal-container .btn-red');
|
||||
|
||||
andThen(() => {
|
||||
// it redirects to the first tag
|
||||
expect(currentURL(), 'URL after tag deletion').to.equal('/settings/tags/1');
|
||||
|
||||
// it removes the tag from the list
|
||||
expect(find('.settings-tags .settings-tag').length, 'tag list count after deletion')
|
||||
.to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('has infinite scroll pagination of tags list');
|
||||
});
|
||||
});
|
51
ghost/admin/tests/fixtures/settings.js
vendored
Normal file
51
ghost/admin/tests/fixtures/settings.js
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
export default [{
|
||||
created_at: '2015-09-11T09:44:30.805Z',
|
||||
created_by: 1,
|
||||
id: 5,
|
||||
key: 'title',
|
||||
type: 'blog',
|
||||
updated_at: '2015-10-04T16:26:05.195Z',
|
||||
updated_by: 1,
|
||||
uuid: '39e16daf-43fa-4bf0-87d4-44948ba8bf4c',
|
||||
value: 'Test Blog'
|
||||
}, {
|
||||
created_at: '2015-09-11T09:44:30.806Z',
|
||||
created_by: 1,
|
||||
id: 6,
|
||||
key: 'description',
|
||||
type: 'blog',
|
||||
updated_at: '2015-10-04T16:26:05.198Z',
|
||||
updated_by: 1,
|
||||
uuid: 'e6c8b636-6925-4c4a-a5d9-1dc0870fb8ea',
|
||||
value: 'Thoughts, stories and ideas.'
|
||||
}, {
|
||||
created_at: '2015-09-11T09:44:30.809Z',
|
||||
created_by: 1,
|
||||
id: 10,
|
||||
key: 'postsPerPage',
|
||||
type: 'blog',
|
||||
updated_at: '2015-10-04T16:26:05.211Z',
|
||||
updated_by: 1,
|
||||
uuid: '775e6ca1-bcc3-4347-a53d-15d5d76c04a4',
|
||||
value: '5'
|
||||
}, {
|
||||
created_at: '2015-09-11T09:44:30.809Z',
|
||||
created_by: 1,
|
||||
id: 13,
|
||||
key: 'ghost_head',
|
||||
type: 'blog',
|
||||
updated_at: '2015-09-23T13:32:49.858Z',
|
||||
updated_by: 1,
|
||||
uuid: 'df7f3151-bc08-4a77-be9d-dd315b630d51',
|
||||
value: ''
|
||||
}, {
|
||||
created_at: '2015-09-11T09:44:30.809Z',
|
||||
created_by: 1,
|
||||
id: 14,
|
||||
key: 'ghost_foot',
|
||||
type: 'blog',
|
||||
updated_at: '2015-09-23T13:32:49.858Z',
|
||||
updated_by: 1,
|
||||
uuid: '0649d45e-828b-4dd0-8381-3dff6d1d5ddb',
|
||||
value: ''
|
||||
}];
|
@ -13,7 +13,7 @@
|
||||
<meta name="env-fileStorage" content="true" />
|
||||
<meta name="env-apps" content="false" />
|
||||
<meta name="env-blogUrl" content="http://localhost:7357/" />
|
||||
<meta name="env-blogTitle" content="The Daily Awesome" />
|
||||
<meta name="env-blogTitle" content="Test Blog" />
|
||||
<meta name="env-routeKeywords" content="{"tag":"tag","author":"author","page":"page","preview":"p","private":"private"}" />
|
||||
<meta name="env-clientId" content="ghost-admin" />
|
||||
<meta name="env-clientSecret" content="5076dc643873" />
|
||||
|
@ -0,0 +1,53 @@
|
||||
/* jshint expr:true */
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
describeComponent,
|
||||
it
|
||||
} from 'ember-mocha';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import Ember from 'ember';
|
||||
|
||||
const {run} = Ember;
|
||||
|
||||
describeComponent(
|
||||
'gh-cm-editor',
|
||||
'Integration: Component: gh-cm-editor',
|
||||
{
|
||||
integration: true
|
||||
},
|
||||
function () {
|
||||
it('handles editor events', function () {
|
||||
this.set('text', '');
|
||||
|
||||
this.render(hbs`{{gh-cm-editor class="gh-input" value=text}}`);
|
||||
let input = this.$('.gh-input');
|
||||
|
||||
expect(input.hasClass('focused'), 'has focused class on first render')
|
||||
.to.be.false;
|
||||
|
||||
run(() => {
|
||||
input.find('textarea').trigger('focus');
|
||||
});
|
||||
|
||||
expect(input.hasClass('focused'), 'has focused class after focus')
|
||||
.to.be.true;
|
||||
|
||||
run(() => {
|
||||
input.find('textarea').trigger('blur');
|
||||
});
|
||||
|
||||
expect(input.hasClass('focused'), 'loses focused class on blur')
|
||||
.to.be.false;
|
||||
|
||||
run(() => {
|
||||
// access CodeMirror directly as it doesn't pick up changes
|
||||
// to the textarea
|
||||
let cm = input.find('.CodeMirror').get(0).CodeMirror;
|
||||
cm.setValue('Testing');
|
||||
});
|
||||
|
||||
expect(this.get('text'), 'text value after CM editor change')
|
||||
.to.equal('Testing');
|
||||
});
|
||||
}
|
||||
);
|
@ -0,0 +1,301 @@
|
||||
/* jshint expr:true */
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
describeComponent,
|
||||
it
|
||||
} from 'ember-mocha';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import Ember from 'ember';
|
||||
import DS from 'ember-data';
|
||||
|
||||
const {run} = Ember,
|
||||
configStub = Ember.Service.extend({
|
||||
blogUrl: 'http://localhost:2368'
|
||||
});
|
||||
|
||||
describeComponent(
|
||||
'gh-tag-settings-form',
|
||||
'Integration: Component: gh-tag-settings-form',
|
||||
{
|
||||
integration: true
|
||||
},
|
||||
function () {
|
||||
beforeEach(function () {
|
||||
let tag = Ember.Object.create({
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
slug: 'test',
|
||||
description: 'Description.',
|
||||
meta_title: 'Meta Title',
|
||||
meta_description: 'Meta description',
|
||||
errors: DS.Errors.create(),
|
||||
hasValidated: []
|
||||
});
|
||||
|
||||
this.set('tag', tag);
|
||||
this.set('actions.setProperty', function (property, value) {
|
||||
// this should be overridden if a call is expected
|
||||
console.error(`setProperty called '${property}: ${value}'`);
|
||||
});
|
||||
|
||||
this.register('service:config', configStub);
|
||||
this.inject.service('config', {as: 'config'});
|
||||
});
|
||||
|
||||
it('renders', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
expect(this.$()).to.have.length(1);
|
||||
});
|
||||
|
||||
it('has the correct title', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
expect(this.$('.tag-settings-pane h4').text(), 'existing tag title').to.equal('Tag Settings');
|
||||
|
||||
this.set('tag.isNew', true);
|
||||
expect(this.$('.tag-settings-pane h4').text(), 'new tag title').to.equal('New Tag');
|
||||
});
|
||||
|
||||
it('renders main settings', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
|
||||
expect(this.$('.image-uploader').length, 'displays image uploader').to.equal(1);
|
||||
expect(this.$('input[name="name"]').val(), 'name field value').to.equal('Test');
|
||||
expect(this.$('input[name="slug"]').val(), 'slug field value').to.equal('test');
|
||||
expect(this.$('textarea[name="description"]').val(), 'description field value').to.equal('Description.');
|
||||
expect(this.$('input[name="meta_title"]').val(), 'meta_title field value').to.equal('Meta Title');
|
||||
expect(this.$('textarea[name="meta_description"]').val(), 'meta_description field value').to.equal('Meta description');
|
||||
});
|
||||
|
||||
it('can switch between main/meta settings', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
|
||||
expect(this.$('.tag-settings-pane').hasClass('settings-menu-pane-in'), 'main settings are displayed by default').to.be.true;
|
||||
expect(this.$('.tag-meta-settings-pane').hasClass('settings-menu-pane-out-right'), 'meta settings are hidden by default').to.be.true;
|
||||
|
||||
run(() => {
|
||||
this.$('.meta-data-button').click();
|
||||
});
|
||||
|
||||
expect(this.$('.tag-settings-pane').hasClass('settings-menu-pane-out-left'), 'main settings are hidden after clicking Meta Data button').to.be.true;
|
||||
expect(this.$('.tag-meta-settings-pane').hasClass('settings-menu-pane-in'), 'meta settings are displayed after clicking Meta Data button').to.be.true;
|
||||
|
||||
run(() => {
|
||||
this.$('.back').click();
|
||||
});
|
||||
|
||||
expect(this.$('.tag-settings-pane').hasClass('settings-menu-pane-in'), 'main settings are displayed after clicking "back"').to.be.true;
|
||||
expect(this.$('.tag-meta-settings-pane').hasClass('settings-menu-pane-out-right'), 'meta settings are hidden after clicking "back"').to.be.true;
|
||||
});
|
||||
|
||||
it('has one-way binding for properties', function () {
|
||||
this.set('actions.setProperty', function () {
|
||||
// noop
|
||||
});
|
||||
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
|
||||
run(() => {
|
||||
this.$('input[name="name"]').val('New name');
|
||||
this.$('input[name="slug"]').val('new-slug');
|
||||
this.$('textarea[name="description"]').val('New description');
|
||||
this.$('input[name="meta_title"]').val('New meta_title');
|
||||
this.$('textarea[name="meta_description"]').val('New meta_description');
|
||||
});
|
||||
|
||||
expect(this.get('tag.name'), 'tag name').to.equal('Test');
|
||||
expect(this.get('tag.slug'), 'tag slug').to.equal('test');
|
||||
expect(this.get('tag.description'), 'tag description').to.equal('Description.');
|
||||
expect(this.get('tag.meta_title'), 'tag meta_title').to.equal('Meta Title');
|
||||
expect(this.get('tag.meta_description'), 'tag meta_description').to.equal('Meta description');
|
||||
});
|
||||
|
||||
it('triggers setProperty action on blur of all fields', function () {
|
||||
let expectedProperty = '',
|
||||
expectedValue = '';
|
||||
|
||||
this.set('actions.setProperty', function (property, value) {
|
||||
expect(property, 'property').to.equal(expectedProperty);
|
||||
expect(value, 'value').to.equal(expectedValue);
|
||||
});
|
||||
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
|
||||
expectedProperty = 'name';
|
||||
expectedValue = 'new-slug';
|
||||
run(() => {
|
||||
this.$('input[name="name"]').val('New name');
|
||||
});
|
||||
|
||||
expectedProperty = 'url';
|
||||
expectedValue = 'new-slug';
|
||||
run(() => {
|
||||
this.$('input[name="slug"]').val('new-slug');
|
||||
});
|
||||
|
||||
expectedProperty = 'description';
|
||||
expectedValue = 'New description';
|
||||
run(() => {
|
||||
this.$('textarea[name="description"]').val('New description');
|
||||
});
|
||||
|
||||
expectedProperty = 'meta_title';
|
||||
expectedValue = 'New meta_title';
|
||||
run(() => {
|
||||
this.$('input[name="meta_title"]').val('New meta_title');
|
||||
});
|
||||
|
||||
expectedProperty = 'meta_description';
|
||||
expectedValue = 'New meta_description';
|
||||
run(() => {
|
||||
this.$('textarea[name="meta_description"]').val('New meta_description');
|
||||
});
|
||||
});
|
||||
|
||||
it('displays error messages for validated fields', function () {
|
||||
let errors = this.get('tag.errors'),
|
||||
hasValidated = this.get('tag.hasValidated');
|
||||
|
||||
errors.add('name', 'must be present');
|
||||
hasValidated.push('name');
|
||||
|
||||
errors.add('slug', 'must be present');
|
||||
hasValidated.push('slug');
|
||||
|
||||
errors.add('description', 'is too long');
|
||||
hasValidated.push('description');
|
||||
|
||||
errors.add('meta_title', 'is too long');
|
||||
hasValidated.push('meta_title');
|
||||
|
||||
errors.add('meta_description', 'is too long');
|
||||
hasValidated.push('meta_description');
|
||||
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
|
||||
let nameFormGroup = this.$('input[name="name"]').closest('.form-group');
|
||||
expect(nameFormGroup.hasClass('error'), 'name form group has error state').to.be.true;
|
||||
expect(nameFormGroup.find('.response').length, 'name form group has error message').to.equal(1);
|
||||
|
||||
let slugFormGroup = this.$('input[name="slug"]').closest('.form-group');
|
||||
expect(slugFormGroup.hasClass('error'), 'slug form group has error state').to.be.true;
|
||||
expect(slugFormGroup.find('.response').length, 'slug form group has error message').to.equal(1);
|
||||
|
||||
let descriptionFormGroup = this.$('textarea[name="description"]').closest('.form-group');
|
||||
expect(descriptionFormGroup.hasClass('error'), 'description form group has error state').to.be.true;
|
||||
|
||||
let metaTitleFormGroup = this.$('input[name="meta_title"]').closest('.form-group');
|
||||
expect(metaTitleFormGroup.hasClass('error'), 'meta_title form group has error state').to.be.true;
|
||||
expect(metaTitleFormGroup.find('.response').length, 'meta_title form group has error message').to.equal(1);
|
||||
|
||||
let metaDescriptionFormGroup = this.$('textarea[name="meta_description"]').closest('.form-group');
|
||||
expect(metaDescriptionFormGroup.hasClass('error'), 'meta_description form group has error state').to.be.true;
|
||||
expect(metaDescriptionFormGroup.find('.response').length, 'meta_description form group has error message').to.equal(1);
|
||||
});
|
||||
|
||||
it('displays char count for text fields', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
|
||||
let descriptionFormGroup = this.$('textarea[name="description"]').closest('.form-group');
|
||||
expect(descriptionFormGroup.find('.word-count').text(), 'description char count').to.equal('12');
|
||||
|
||||
let metaDescriptionFormGroup = this.$('textarea[name="meta_description"]').closest('.form-group');
|
||||
expect(metaDescriptionFormGroup.find('.word-count').text(), 'description char count').to.equal('16');
|
||||
});
|
||||
|
||||
it('renders SEO title preview', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
expect(this.$('.seo-preview-title').text(), 'displays meta title if present').to.equal('Meta Title');
|
||||
|
||||
run(() => {
|
||||
this.set('tag.meta_title', '');
|
||||
});
|
||||
expect(this.$('.seo-preview-title').text(), 'falls back to tag name without meta_title').to.equal('Test');
|
||||
|
||||
run(() => {
|
||||
this.set('tag.name', (new Array(151).join('x')));
|
||||
});
|
||||
let expectedLength = 70 + '…'.length;
|
||||
expect(this.$('.seo-preview-title').text().length, 'cuts title to max 70 chars').to.equal(expectedLength);
|
||||
});
|
||||
|
||||
it('renders SEO URL preview', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
expect(this.$('.seo-preview-link').text(), 'adds url and tag prefix').to.equal('http://localhost:2368/tag/test/');
|
||||
|
||||
run(() => {
|
||||
this.set('tag.slug', (new Array(151).join('x')));
|
||||
});
|
||||
let expectedLength = 70 + '…'.length;
|
||||
expect(this.$('.seo-preview-link').text().length, 'cuts slug to max 70 chars').to.equal(expectedLength);
|
||||
});
|
||||
|
||||
it('renders SEO description preview', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
expect(this.$('.seo-preview-description').text(), 'displays meta description if present').to.equal('Meta description');
|
||||
|
||||
run(() => {
|
||||
this.set('tag.meta_description', '');
|
||||
});
|
||||
expect(this.$('.seo-preview-description').text(), 'falls back to tag description without meta_description').to.equal('Description.');
|
||||
|
||||
run(() => {
|
||||
this.set('tag.description', (new Array(200).join('x')));
|
||||
});
|
||||
let expectedLength = 156 + '…'.length;
|
||||
expect(this.$('.seo-preview-description').text().length, 'cuts description to max 156 chars').to.equal(expectedLength);
|
||||
});
|
||||
|
||||
it('resets if a new tag is received', function () {
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
run(() => {
|
||||
this.$('.meta-data-button').click();
|
||||
});
|
||||
expect(this.$('.tag-meta-settings-pane').hasClass('settings-menu-pane-in'), 'meta data pane is shown').to.be.true;
|
||||
|
||||
run(() => {
|
||||
this.set('tag', Ember.Object.create({id: '2'}));
|
||||
});
|
||||
expect(this.$('.tag-settings-pane').hasClass('settings-menu-pane-in'), 'resets to main settings').to.be.true;
|
||||
});
|
||||
|
||||
it('triggers delete tag modal on delete click', function (done) {
|
||||
this.set('actions.openModal', (modalName, model) => {
|
||||
expect(modalName, 'passed modal name').to.equal('delete-tag');
|
||||
expect(model, 'passed model').to.equal(this.get('tag'));
|
||||
done();
|
||||
});
|
||||
|
||||
this.render(hbs`
|
||||
{{gh-tag-settings-form tag=tag setProperty=(action 'setProperty') openModal='openModal'}}
|
||||
`);
|
||||
|
||||
run(() => {
|
||||
this.$('.tag-delete-button').click();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
Loading…
Reference in New Issue
Block a user