mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-18 02:21:47 -05:00
commit
910c3e36f7
5 changed files with 172 additions and 28 deletions
|
@ -4,13 +4,14 @@ import boundOneWay from 'ghost/utils/bound-one-way';
|
|||
var TagsController = Ember.ArrayController.extend(PaginationMixin, {
|
||||
tags: Ember.computed.alias('model'),
|
||||
|
||||
needs: 'application',
|
||||
|
||||
activeTag: null,
|
||||
activeTagNameScratch: boundOneWay('activeTag.name'),
|
||||
activeTagSlugScratch: boundOneWay('activeTag.slug'),
|
||||
activeTagDescriptionScratch: boundOneWay('activeTag.description'),
|
||||
|
||||
// Tag properties that should not be set to the empty string
|
||||
requiredTagProperties: ['name', 'slug'],
|
||||
activeTagMetaTitleScratch: boundOneWay('activeTag.meta_title'),
|
||||
activeTagMetaDescriptionScratch: boundOneWay('activeTag.meta_description'),
|
||||
|
||||
init: function (options) {
|
||||
options = options || {};
|
||||
|
@ -18,18 +19,30 @@ var TagsController = Ember.ArrayController.extend(PaginationMixin, {
|
|||
this._super(options);
|
||||
},
|
||||
|
||||
isViewingSubview: Ember.computed('controllers.application.showSettingsMenu', function (key, value) {
|
||||
// Not viewing a subview if we can't even see the PSM
|
||||
if (!this.get('controllers.application.showSettingsMenu')) {
|
||||
return false;
|
||||
}
|
||||
if (arguments.length > 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}),
|
||||
|
||||
showErrors: function (errors) {
|
||||
errors = Ember.isArray(errors) ? errors : [errors];
|
||||
this.notifications.showErrors(errors);
|
||||
},
|
||||
|
||||
saveActiveTagProperty: function (propKey, newValue) {
|
||||
var activeTag = this.get('activeTag'),
|
||||
currentValue = activeTag.get(propKey),
|
||||
requiredTagProps = this.get('requiredTagProperties'),
|
||||
self = this,
|
||||
tagName;
|
||||
self = this;
|
||||
|
||||
newValue = newValue.trim();
|
||||
// Quit if value is empty for a required property
|
||||
if (!newValue && requiredTagProps.contains(propKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Quit if there was no change
|
||||
if (newValue === currentValue) {
|
||||
return;
|
||||
|
@ -37,19 +50,59 @@ var TagsController = Ember.ArrayController.extend(PaginationMixin, {
|
|||
|
||||
activeTag.set(propKey, newValue);
|
||||
|
||||
tagName = activeTag.get('name');
|
||||
// don't save a new tag until it has a name
|
||||
if (!tagName) {
|
||||
return;
|
||||
}
|
||||
this.notifications.closePassive();
|
||||
|
||||
activeTag.save().then(function () {
|
||||
self.notifications.showSuccess('Saved ' + tagName);
|
||||
}).catch(function (error) {
|
||||
self.notifications.showAPIError(error);
|
||||
activeTag.save().catch(function (errors) {
|
||||
self.showErrors(errors);
|
||||
});
|
||||
},
|
||||
|
||||
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 = new Ember.Handlebars.SafeString(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 = new Ember.Handlebars.SafeString(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 = new Ember.Handlebars.SafeString(metaDescription + '…');
|
||||
}
|
||||
|
||||
return metaDescription;
|
||||
}),
|
||||
|
||||
actions: {
|
||||
newTag: function () {
|
||||
this.set('activeTag', this.store.createRecord('tag'));
|
||||
|
@ -84,6 +137,22 @@ var TagsController = Ember.ArrayController.extend(PaginationMixin, {
|
|||
|
||||
saveActiveTagDescription: function (description) {
|
||||
this.saveActiveTagProperty('description', description);
|
||||
},
|
||||
|
||||
saveActiveTagMetaTitle: function (metaTitle) {
|
||||
this.saveActiveTagProperty('meta_title', metaTitle);
|
||||
},
|
||||
|
||||
saveActiveTagMetaDescription: function (metaDescription) {
|
||||
this.saveActiveTagProperty('meta_description', metaDescription);
|
||||
},
|
||||
|
||||
showSubview: function () {
|
||||
this.set('isViewingSubview', true);
|
||||
},
|
||||
|
||||
closeSubview: function () {
|
||||
this.set('isViewingSubview', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import ForgotValidator from 'ghost/validators/forgotten';
|
|||
import SettingValidator from 'ghost/validators/setting';
|
||||
import ResetValidator from 'ghost/validators/reset';
|
||||
import UserValidator from 'ghost/validators/user';
|
||||
import TagSettingsValidator from 'ghost/validators/tag-settings';
|
||||
|
||||
// our extensions to the validator library
|
||||
ValidatorExtensions.init();
|
||||
|
@ -71,7 +72,8 @@ var ValidationEngine = Ember.Mixin.create({
|
|||
forgotten: ForgotValidator,
|
||||
setting: SettingValidator,
|
||||
reset: ResetValidator,
|
||||
user: UserValidator
|
||||
user: UserValidator,
|
||||
tag: TagSettingsValidator
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import ValidationEngine from 'ghost/mixins/validation-engine';
|
||||
import NProgressSaveMixin from 'ghost/mixins/nprogress-save';
|
||||
|
||||
var Tag = DS.Model.extend(NProgressSaveMixin, {
|
||||
var Tag = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
|
||||
validationType: 'tag',
|
||||
|
||||
uuid: DS.attr('string'),
|
||||
name: DS.attr('string'),
|
||||
slug: DS.attr('string'),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="content-cover" {{action "closeSettingsMenu"}}></div>
|
||||
<div class="settings-menu-container">
|
||||
<div class="settings-menu settings-menu-pane settings-menu-pane-in">
|
||||
{{#gh-tabs-manager selected="showSubview" class="settings-menu-container"}}
|
||||
<div {{bind-attr class="isViewingSubview:settings-menu-pane-out-left:settings-menu-pane-in :settings-menu :settings-menu-pane"}}>
|
||||
<div class="settings-menu-header">
|
||||
<h4>Tag Settings</h4>
|
||||
<button class="close icon-x settings-menu-header-action" {{action "closeSettingsMenu"}}>
|
||||
|
@ -11,12 +11,12 @@
|
|||
<form>
|
||||
<div class="form-group">
|
||||
<label>Tag Name</label>
|
||||
{{gh-input type="text" placeholder=activeTag.name value=activeTagNameScratch focus-out="saveActiveTagName"}}
|
||||
{{gh-input type="text" value=activeTagNameScratch focus-out="saveActiveTagName"}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Slug</label>{{!--@TODO show full url preview, not just slug--}}
|
||||
{{gh-input type="text" placeholder=activeTag.slug value=activeTagSlugScratch focus-out="saveActiveTagSlug"}}
|
||||
{{gh-input type="text" value=activeTagSlugScratch focus-out="saveActiveTagSlug"}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
@ -24,10 +24,52 @@
|
|||
{{gh-textarea value=activeTagDescriptionScratch focus-out="saveActiveTagDescription"}}
|
||||
</div>
|
||||
|
||||
<ul class="nav-list nav-list-block">
|
||||
{{#gh-tab tagName="li" classNames="nav-list-item"}}
|
||||
<button type="button">
|
||||
<b>Meta Data</b>
|
||||
<span>Extra content for SEO and social media.</span>
|
||||
</button>
|
||||
{{/gh-tab}}
|
||||
</ul>
|
||||
|
||||
{{#unless activeTag.isNew}}
|
||||
<button class="btn btn-red icon-trash" {{action "deleteTag" activeTag}}>Delete Tag</button>
|
||||
<button type="button" class="btn btn-red icon-trash" {{action "deleteTag" activeTag}}>Delete Tag</button>
|
||||
{{/unless}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>{{! .settings-menu-pane }}
|
||||
|
||||
<div {{bind-attr class="isViewingSubview:settings-menu-pane-in:settings-menu-pane-out-right :settings-menu :settings-menu-pane"}}>
|
||||
{{#gh-tab-pane}}
|
||||
<div class="settings-menu-header subview">
|
||||
<button {{action "closeSubview"}} class="back icon-chevron-left settings-menu-header-action"><span class="hidden">Back</span></button>
|
||||
<h4>Meta Data</h4>
|
||||
</div>
|
||||
|
||||
<div class="settings-menu-content">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="meta-title">Meta Title</label>
|
||||
{{gh-input type="text" value=activeTagMetaTitleScratch focus-out="saveActiveTagMetaTitle"}}
|
||||
<p>Recommended: <b>70</b> characters. You’ve used {{gh-count-down-characters activeTagMetaTitleScratch 70}}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="meta-description">Meta Description</label>
|
||||
{{gh-textarea value=activeTagMetaDescriptionScratch focus-out="saveActiveTagMetaDescription"}}
|
||||
<p>Recommended: <b>156</b> characters. You’ve used {{gh-count-down-characters activeTagMetaDescriptionScratch 156}}</p>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</form>
|
||||
</div>{{! .settings-menu-content }}
|
||||
{{/gh-tab-pane}}
|
||||
</div>{{! .settings-menu-pane }}
|
||||
{{/gh-tabs-manager}}
|
28
core/client/validators/tag-settings.js
Normal file
28
core/client/validators/tag-settings.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
var TagSettingsValidator = Ember.Object.create({
|
||||
check: function (model) {
|
||||
var validationErrors = [],
|
||||
data = model.getProperties('name', 'meta_title', 'meta_description');
|
||||
|
||||
if (validator.empty(data.name)) {
|
||||
validationErrors.push({
|
||||
message: 'You must specify a name for the tag.'
|
||||
});
|
||||
}
|
||||
|
||||
if (!validator.isLength(data.meta_title, 0, 150)) {
|
||||
validationErrors.push({
|
||||
message: 'Meta Title cannot be longer than 150 characters.'
|
||||
});
|
||||
}
|
||||
|
||||
if (!validator.isLength(data.meta_description, 0, 200)) {
|
||||
validationErrors.push({
|
||||
message: 'Meta Description cannot be longer than 200 characters.'
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
}
|
||||
});
|
||||
|
||||
export default TagSettingsValidator;
|
Loading…
Add table
Reference in a new issue