mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 11:55:03 +03:00
Convert general settings page to ember data
Issue #2846 -Implement custom RESTAdapter and serializer for settings data. -Convert theme selector to Ember.Select. -Finish upload modal and wire into settings page.
This commit is contained in:
parent
2dd5c5218a
commit
18a433debd
21
ghost/admin/adapters/setting.js
Normal file
21
ghost/admin/adapters/setting.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import ApplicationAdapter from 'ghost/adapters/application';
|
||||||
|
|
||||||
|
var SettingAdapter = ApplicationAdapter.extend({
|
||||||
|
updateRecord: function (store, type, record) {
|
||||||
|
var data = {},
|
||||||
|
serializer = store.serializerFor(type.typeKey);
|
||||||
|
|
||||||
|
// remove the fake id that we added onto the model.
|
||||||
|
delete record.id;
|
||||||
|
|
||||||
|
// use the SettingSerializer to transform the model back into
|
||||||
|
// an array of settings objects like the API expects
|
||||||
|
serializer.serializeIntoHash(data, type, record);
|
||||||
|
|
||||||
|
// use the ApplicationAdapter's buildURL method but do not
|
||||||
|
// pass in an id.
|
||||||
|
return this.ajax(this.buildURL(type.typeKey), 'PUT', { data: data });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SettingAdapter;
|
@ -43,6 +43,7 @@ var UploadModal = ModalDialog.extend({
|
|||||||
func.apply(this);
|
func.apply(this);
|
||||||
}
|
}
|
||||||
this.sendAction();
|
this.sendAction();
|
||||||
|
this.sendAction('confirm' + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2,15 +2,17 @@
|
|||||||
var UploadController = Ember.Controller.extend({
|
var UploadController = Ember.Controller.extend({
|
||||||
acceptEncoding: 'image/*',
|
acceptEncoding: 'image/*',
|
||||||
actions: {
|
actions: {
|
||||||
confirmReject: function () {
|
confirmAccept: function () {
|
||||||
return true;
|
var self = this;
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
confirm: {
|
this.get('model').save().then(function (model) {
|
||||||
reject: {
|
self.notifications.showSuccess('Saved');
|
||||||
buttonClass: true,
|
return model;
|
||||||
text: 'Cancel' // The reject button text
|
}).catch(this.notifications.showErrors);
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmReject: function () {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
var elementLookup = {
|
|
||||||
title: '#blog-title',
|
|
||||||
description: '#blog-description',
|
|
||||||
email: '#email-address',
|
|
||||||
postsPerPage: '#postsPerPage'
|
|
||||||
};
|
|
||||||
|
|
||||||
var SettingsGeneralController = Ember.ObjectController.extend({
|
var SettingsGeneralController = Ember.ObjectController.extend({
|
||||||
isDatedPermalinks: function (key, value) {
|
isDatedPermalinks: function (key, value) {
|
||||||
// setter
|
// setter
|
||||||
@ -18,41 +11,31 @@ var SettingsGeneralController = Ember.ObjectController.extend({
|
|||||||
return slugForm !== '/:slug/';
|
return slugForm !== '/:slug/';
|
||||||
}.property('permalinks'),
|
}.property('permalinks'),
|
||||||
|
|
||||||
|
themes: function () {
|
||||||
|
return this.get('availableThemes').reduce(function (themes, t) {
|
||||||
|
var theme = {};
|
||||||
|
|
||||||
|
theme.name = t.name;
|
||||||
|
theme.label = t.package ? t.package.name + ' - ' + t.package.version : t.name;
|
||||||
|
theme.package = t.package;
|
||||||
|
theme.active = !!t.active;
|
||||||
|
|
||||||
|
themes.push(theme);
|
||||||
|
|
||||||
|
return themes;
|
||||||
|
}, []);
|
||||||
|
}.property('availableThemes').readOnly(),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
save: function () {
|
save: function () {
|
||||||
// Validate and save settings
|
var self = this;
|
||||||
var model = this.get('model'),
|
|
||||||
// @TODO: Don't know how to scope this to this controllers view because this.view is null
|
|
||||||
errs = model.validate();
|
|
||||||
|
|
||||||
if (errs.length > 0) {
|
return this.get('model').save().then(function (model) {
|
||||||
// Set the actual element from this view based on the error
|
self.notifications.showSuccess('Settings successfully saved.');
|
||||||
errs.forEach(function (err) {
|
return model;
|
||||||
// @TODO: Probably should still be scoped to this controllers root element.
|
}).catch(this.notifications.showErrors);
|
||||||
err.el = $(elementLookup[err.el]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Let the applicationRoute handle validation errors
|
|
||||||
this.send('handleErrors', errs);
|
|
||||||
} else {
|
|
||||||
model.save().then(function () {
|
|
||||||
// @TODO: Notification of success
|
|
||||||
window.alert('Saved data!');
|
|
||||||
}, function () {
|
|
||||||
// @TODO: Notification of error
|
|
||||||
window.alert('Error saving data');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadLogo: function () {
|
|
||||||
// @TODO: Integrate with Modal component
|
|
||||||
},
|
|
||||||
|
|
||||||
uploadCover: function () {
|
|
||||||
// @TODO: Integrate with Modal component
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SettingsGeneralController;
|
export default SettingsGeneralController;
|
||||||
|
15
ghost/admin/models/setting.js
Normal file
15
ghost/admin/models/setting.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
var Setting = DS.Model.extend({
|
||||||
|
title: DS.attr('string'),
|
||||||
|
description: DS.attr('string'),
|
||||||
|
email: DS.attr('string'),
|
||||||
|
logo: DS.attr('string'),
|
||||||
|
cover: DS.attr('string'),
|
||||||
|
defaultLang: DS.attr('string'),
|
||||||
|
postsPerPage: DS.attr('number'),
|
||||||
|
forceI18n: DS.attr('boolean'),
|
||||||
|
permalinks: DS.attr('string'),
|
||||||
|
activeTheme: DS.attr('string'),
|
||||||
|
availableThemes: DS.attr()
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Setting;
|
@ -1,76 +0,0 @@
|
|||||||
var validator = window.validator;
|
|
||||||
|
|
||||||
import BaseModel from 'ghost/models/base';
|
|
||||||
|
|
||||||
var SettingsModel = BaseModel.extend({
|
|
||||||
url: BaseModel.apiRoot + '/settings/?type=blog,theme,app',
|
|
||||||
|
|
||||||
title: null,
|
|
||||||
description: null,
|
|
||||||
email: null,
|
|
||||||
logo: null,
|
|
||||||
cover: null,
|
|
||||||
defaultLang: null,
|
|
||||||
postsPerPage: null,
|
|
||||||
forceI18n: null,
|
|
||||||
permalinks: null,
|
|
||||||
activeTheme: null,
|
|
||||||
activeApps: null,
|
|
||||||
installedApps: null,
|
|
||||||
availableThemes: null,
|
|
||||||
availableApps: null,
|
|
||||||
|
|
||||||
validate: function () {
|
|
||||||
var validationErrors = [],
|
|
||||||
postsPerPage;
|
|
||||||
|
|
||||||
if (!validator.isLength(this.get('title'), 0, 150)) {
|
|
||||||
validationErrors.push({message: 'Title is too long', el: 'title'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator.isLength(this.get('description'), 0, 200)) {
|
|
||||||
validationErrors.push({message: 'Description is too long', el: 'description'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator.isEmail(this.get('email')) || !validator.isLength(this.get('email'), 0, 254)) {
|
|
||||||
validationErrors.push({message: 'Please supply a valid email address', el: 'email'});
|
|
||||||
}
|
|
||||||
|
|
||||||
postsPerPage = this.get('postsPerPage');
|
|
||||||
if (!validator.isInt(postsPerPage) || postsPerPage > 1000) {
|
|
||||||
validationErrors.push({message: 'Please use a number less than 1000', el: 'postsPerPage'});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator.isInt(postsPerPage) || postsPerPage < 0) {
|
|
||||||
validationErrors.push({message: 'Please use a number greater than 0', el: 'postsPerPage'});
|
|
||||||
}
|
|
||||||
|
|
||||||
return validationErrors;
|
|
||||||
},
|
|
||||||
exportPath: BaseModel.adminRoot + '/export/',
|
|
||||||
importFrom: function (file) {
|
|
||||||
var formData = new FormData();
|
|
||||||
formData.append('importfile', file);
|
|
||||||
return ic.ajax.request(BaseModel.apiRoot + '/db/', {
|
|
||||||
headers: {
|
|
||||||
'X-CSRF-Token': $('meta[name="csrf-param"]').attr('content')
|
|
||||||
},
|
|
||||||
type: 'POST',
|
|
||||||
data: formData,
|
|
||||||
dataType: 'json',
|
|
||||||
cache: false,
|
|
||||||
contentType: false,
|
|
||||||
processData: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
sendTestEmail: function () {
|
|
||||||
return ic.ajax.request(BaseModel.apiRoot + '/mail/test/', {
|
|
||||||
type: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-CSRF-Token': $('meta[name="csrf-param"]').attr('content')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SettingsModel;
|
|
@ -1,13 +1,11 @@
|
|||||||
import ajax from 'ghost/utils/ajax';
|
|
||||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||||
import SettingsModel from 'ghost/models/settings';
|
|
||||||
|
|
||||||
var SettingsGeneralRoute = AuthenticatedRoute.extend({
|
var SettingsGeneralRoute = AuthenticatedRoute.extend({
|
||||||
model: function () {
|
model: function () {
|
||||||
return ajax('/ghost/api/v0.1/settings/?type=blog,theme,app').then(function (resp) {
|
return this.store.find('setting', { type: 'blog,theme' }).then(function (records) {
|
||||||
return SettingsModel.create(resp);
|
return records.get('firstObject');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SettingsGeneralRoute;
|
export default SettingsGeneralRoute;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||||
|
|
||||||
var SettingsIndexRoute = AuthenticatedRoute.extend({
|
var SettingsIndexRoute = AuthenticatedRoute.extend({
|
||||||
// redirect to general tab
|
|
||||||
redirect: function () {
|
redirect: function () {
|
||||||
this.transitionTo('settings.general');
|
this.transitionTo('settings.general');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SettingsIndexRoute;
|
export default SettingsIndexRoute;
|
||||||
|
37
ghost/admin/serializers/setting.js
Normal file
37
ghost/admin/serializers/setting.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import ApplicationSerializer from 'ghost/serializers/application';
|
||||||
|
|
||||||
|
var SettingSerializer = ApplicationSerializer.extend({
|
||||||
|
serializeIntoHash: function (hash, type, record, options) {
|
||||||
|
// Settings API does not want ids
|
||||||
|
options = options || {};
|
||||||
|
options.includeId = false;
|
||||||
|
|
||||||
|
var root = Ember.String.pluralize(type.typeKey),
|
||||||
|
data = this.serialize(record, options),
|
||||||
|
payload = [];
|
||||||
|
|
||||||
|
delete data.id;
|
||||||
|
|
||||||
|
Object.keys(data).forEach(function (k) {
|
||||||
|
payload.push({ key: k, value: data[k] });
|
||||||
|
});
|
||||||
|
|
||||||
|
hash[root] = payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
extractArray: function (store, type, _payload) {
|
||||||
|
var payload = { id: '0' };
|
||||||
|
|
||||||
|
_payload.settings.forEach(function (setting) {
|
||||||
|
payload[setting.key] = setting.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return [payload];
|
||||||
|
},
|
||||||
|
|
||||||
|
extractSingle: function (store, type, payload) {
|
||||||
|
return this.extractArray(store, type, payload).pop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SettingSerializer;
|
@ -67,11 +67,13 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="activeTheme">Theme</label>
|
<label for="activeTheme">Theme</label>
|
||||||
<select id="activeTheme" name="general[activeTheme]">
|
{{view Ember.Select
|
||||||
{{#each availableThemes}}
|
id="activeTheme"
|
||||||
<option value="{{name}}" {{#if active}}selected{{/if}}>{{#if package}}{{package.name}} - {{package.version}}{{else}}{{name}}{{/if}}</option>
|
name="general[activeTheme]"
|
||||||
{{/each}}
|
content=themes
|
||||||
</select>
|
optionValuePath="content.name"
|
||||||
|
optionLabelPath="content.label"
|
||||||
|
value=activeTheme}}
|
||||||
<p>Select a theme for your blog</p>
|
<p>Select a theme for your blog</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user