mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-27 10:42:45 +03:00
Implements Initial lifecycle and App UI start
Closes #2083 * Added hbs template for apps listing * Added settings to read the activeApps * Added viewcontrol to activate / deactivate apps * Added API handler to store activeApps (by `name` in the `package.json` file) * On button click it turns the button into "Working" and changes class to `button` (grey one) * On success, rerenders the pane, adds success notification about apps being saved * On error, rerenders the pane, adds error notification with error message Missing: * tests: couldn't figure out how to add mock apps with mock package.json data * actually registering, etc, re #2140 * icon from the sidebar
This commit is contained in:
parent
c64148f361
commit
667888aeb3
@ -3,7 +3,7 @@
|
||||
'use strict';
|
||||
//id:0 is used to issue PUT requests
|
||||
Ghost.Models.Settings = Ghost.ProgressModel.extend({
|
||||
url: Ghost.paths.apiRoot + '/settings/?type=blog,theme',
|
||||
url: Ghost.paths.apiRoot + '/settings/?type=blog,theme,app',
|
||||
id: '0'
|
||||
});
|
||||
|
||||
|
15
core/client/tpl/settings/apps.hbs
Normal file
15
core/client/tpl/settings/apps.hbs
Normal file
@ -0,0 +1,15 @@
|
||||
<header>
|
||||
<button class="button-back">Back</button>
|
||||
<h2 class="title">Apps</h2>
|
||||
</header>
|
||||
|
||||
<section class="content">
|
||||
<ul class="js-apps">
|
||||
{{#each availableApps}}
|
||||
<li>
|
||||
{{#if package}}{{package.name}} - {{package.version}}{{else}}{{name}} - package.json missing :({{/if}}
|
||||
<button data-app="{{name}}" class="{{#if active}}button-delete js-button-deactivate js-button-active">Deactivate{{else}}button-add js-button-activate">Activate{{/if}}</button>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</section>
|
@ -5,5 +5,6 @@
|
||||
<ul>
|
||||
<li class="general"><a href="#general">General</a></li>
|
||||
<li class="users"><a href="#user">User</a></li>
|
||||
<li class="apps"><a href="#apps">Apps</a></li>
|
||||
</ul>
|
||||
</nav>
|
@ -446,4 +446,70 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ### Apps page
|
||||
Settings.apps = Settings.Pane.extend({
|
||||
id: "apps",
|
||||
|
||||
events: {
|
||||
'click .js-button-activate': 'activateApp',
|
||||
'click .js-button-deactivate': 'deactivateApp'
|
||||
},
|
||||
|
||||
beforeRender: function () {
|
||||
this.availableApps = this.model.toJSON().availableApps;
|
||||
},
|
||||
|
||||
activateApp: function (event) {
|
||||
var button = $(event.currentTarget);
|
||||
|
||||
button.removeClass('button-add').addClass('button js-button-active').text('Working');
|
||||
|
||||
this.saveStates();
|
||||
},
|
||||
|
||||
deactivateApp: function (event) {
|
||||
var button = $(event.currentTarget);
|
||||
|
||||
button.removeClass('button-delete js-button-active').addClass('button').text('Working');
|
||||
|
||||
this.saveStates();
|
||||
},
|
||||
|
||||
saveStates: function () {
|
||||
var activeButtons = this.$el.find('.js-apps .js-button-active'),
|
||||
toSave = [],
|
||||
self = this;
|
||||
|
||||
_.each(activeButtons, function (app) {
|
||||
toSave.push($(app).data('app'));
|
||||
});
|
||||
|
||||
this.model.save({
|
||||
activeApps: JSON.stringify(toSave)
|
||||
}, {
|
||||
success: this.saveSuccess,
|
||||
error: this.saveError
|
||||
}).then(function () { self.render(); });
|
||||
},
|
||||
|
||||
saveSuccess: function () {
|
||||
Ghost.notifications.addItem({
|
||||
type: 'success',
|
||||
message: 'Active applications updated.',
|
||||
status: 'passive',
|
||||
id: 'success-1100'
|
||||
});
|
||||
},
|
||||
|
||||
saveError: function (xhr) {
|
||||
Ghost.notifications.addItem({
|
||||
type: 'error',
|
||||
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
|
||||
status: 'passive'
|
||||
});
|
||||
},
|
||||
|
||||
templateName: 'settings/apps'
|
||||
});
|
||||
|
||||
}());
|
||||
|
@ -9,6 +9,7 @@ var _ = require('lodash'),
|
||||
settingsFilter,
|
||||
updateSettingsCache,
|
||||
readSettingsResult,
|
||||
filterPaths,
|
||||
// Holds cached settings
|
||||
settingsCache = {};
|
||||
|
||||
@ -78,36 +79,69 @@ readSettingsResult = function (result) {
|
||||
}
|
||||
})).then(function () {
|
||||
return when(config().paths.availableThemes).then(function (themes) {
|
||||
var themeKeys = Object.keys(themes),
|
||||
res = [],
|
||||
i,
|
||||
item;
|
||||
for (i = 0; i < themeKeys.length; i += 1) {
|
||||
//do not include hidden files or _messages
|
||||
if (themeKeys[i].indexOf('.') !== 0 && themeKeys[i] !== '_messages') {
|
||||
item = {};
|
||||
item.name = themeKeys[i];
|
||||
if (themes[themeKeys[i]].hasOwnProperty('package.json')) {
|
||||
item.package = themes[themeKeys[i]]['package.json'];
|
||||
} else {
|
||||
item.package = false;
|
||||
}
|
||||
//data about files currently not used
|
||||
//item.details = themes[themeKeys[i]];
|
||||
if (themeKeys[i] === settings.activeTheme.value) {
|
||||
item.active = true;
|
||||
}
|
||||
res.push(item);
|
||||
}
|
||||
}
|
||||
settings.availableThemes = {};
|
||||
settings.availableThemes.value = res;
|
||||
settings.availableThemes.type = 'theme';
|
||||
var res = filterPaths(themes, settings.activeTheme.value);
|
||||
settings.availableThemes = {
|
||||
value: res,
|
||||
type: 'theme'
|
||||
};
|
||||
return settings;
|
||||
});
|
||||
}).then(function () {
|
||||
return when(config().paths.availableApps).then(function (apps) {
|
||||
var res = filterPaths(apps, JSON.parse(settings.activeApps.value));
|
||||
settings.availableApps = {
|
||||
value: res,
|
||||
type: 'app'
|
||||
};
|
||||
return settings;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes paths read by require-tree so that the apps and themes modules can use them.
|
||||
* Creates an empty array (res), and populates it with useful info about the read packages
|
||||
* like name, whether they're active (comparison with the second argument), and if they
|
||||
* have a package.json, that, otherwise false
|
||||
* @param object paths as returned by require-tree()
|
||||
* @param array/string active as read from the settings object
|
||||
* @return array of objects with useful info about
|
||||
* apps / themes
|
||||
*/
|
||||
filterPaths = function (paths, active) {
|
||||
var pathKeys = Object.keys(paths),
|
||||
res = [],
|
||||
item;
|
||||
|
||||
// turn active into an array (so themes and apps can be checked the same)
|
||||
if (!Array.isArray(active)) {
|
||||
active = [active];
|
||||
}
|
||||
|
||||
_.each(pathKeys, function (key) {
|
||||
//do not include hidden files or _messages
|
||||
if (key.indexOf('.') !== 0
|
||||
&& key !== '_messages'
|
||||
&& key !== 'README.md'
|
||||
) {
|
||||
item = {
|
||||
name: key
|
||||
};
|
||||
if (paths[key].hasOwnProperty('package.json')) {
|
||||
item.package = paths[key]['package.json'];
|
||||
} else {
|
||||
item.package = false;
|
||||
}
|
||||
|
||||
if (_.indexOf(active, key) !== -1) {
|
||||
item.active = true;
|
||||
}
|
||||
res.push(item);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
settings = {
|
||||
// #### Browse
|
||||
|
||||
@ -153,6 +187,7 @@ settings = {
|
||||
var type = key.type;
|
||||
delete key.type;
|
||||
delete key.availableThemes;
|
||||
delete key.availableApps;
|
||||
|
||||
key = settingsCollection(key);
|
||||
return dataProvider.Settings.edit(key).then(function (result) {
|
||||
|
@ -89,7 +89,7 @@ adminControllers = {
|
||||
// Method: GET
|
||||
'settings': function (req, res, next) {
|
||||
// TODO: Centralise list/enumeration of settings panes, so we don't run into trouble in future.
|
||||
var allowedSections = ['', 'general', 'user'],
|
||||
var allowedSections = ['', 'general', 'user', 'app'],
|
||||
section = req.url.replace(/(^\/ghost\/settings[\/]*|\/$)/ig, '');
|
||||
|
||||
if (allowedSections.indexOf(section) < 0) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*globals casper, __utils__, url */
|
||||
|
||||
CasperTest.begin("Settings screen is correct", 15, function suite(test) {
|
||||
CasperTest.begin("Settings screen is correct", 18, function suite(test) {
|
||||
casper.thenOpen(url + "ghost/settings/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
|
||||
@ -10,6 +10,9 @@ CasperTest.begin("Settings screen is correct", 15, function suite(test) {
|
||||
test.assertExists(".wrapper", "Settings main view is present");
|
||||
test.assertExists(".settings-sidebar", "Settings sidebar view is present");
|
||||
test.assertExists(".settings-menu", "Settings menu is present");
|
||||
test.assertExists(".settings-menu .general", "General tab is present");
|
||||
test.assertExists(".settings-menu .users", "Users tab is present");
|
||||
test.assertExists(".settings-menu .apps", "Apps is present");
|
||||
test.assertExists(".wrapper", "Settings main view is present");
|
||||
test.assertExists(".settings-content", "Settings content view is present");
|
||||
test.assertEval(function testGeneralIsActive() {
|
||||
|
@ -12,7 +12,7 @@ var _ = require('lodash'),
|
||||
// TODO: remove databaseVersion, dbHash
|
||||
settings: ['databaseVersion', 'dbHash', 'title', 'description', 'email', 'logo', 'cover', 'defaultLang',
|
||||
"permalinks", 'postsPerPage', 'forceI18n', 'activeTheme', 'activeApps', 'installedApps',
|
||||
'availableThemes', 'nextUpdateCheck', 'displayUpdateNotification'],
|
||||
'availableThemes', 'availableApps', 'nextUpdateCheck', 'displayUpdateNotification'],
|
||||
tag: ['id', 'uuid', 'name', 'slug', 'description', 'parent_id',
|
||||
'meta_title', 'meta_description', 'created_at', 'created_by', 'updated_at', 'updated_by'],
|
||||
user: ['id', 'uuid', 'name', 'slug', 'email', 'image', 'cover', 'bio', 'website',
|
||||
|
Loading…
Reference in New Issue
Block a user