+
{{outlet}}
diff --git a/app/assets/javascripts/discourse/ember/resolver.js.es6 b/app/assets/javascripts/discourse/ember/resolver.js.es6
index 7976de86b..896baca33 100644
--- a/app/assets/javascripts/discourse/ember/resolver.js.es6
+++ b/app/assets/javascripts/discourse/ember/resolver.js.es6
@@ -44,7 +44,7 @@ export default Ember.DefaultResolver.extend({
parseName: parseName,
- normalize: function(fullName) {
+ normalize(fullName) {
var split = fullName.split(':');
if (split.length > 1) {
var discourseBase = 'discourse/' + split[0] + 's/';
@@ -68,14 +68,14 @@ export default Ember.DefaultResolver.extend({
return this._super(fullName);
},
- customResolve: function(parsedName) {
+ customResolve(parsedName) {
// If we end with the name we want, use it. This allows us to define components within plugins.
- var suffix = parsedName.type + 's/' + parsedName.fullNameWithoutType,
- dashed = Ember.String.dasherize(suffix),
- moduleName = Ember.keys(requirejs.entries).find(function(e) {
- return (e.indexOf(suffix, e.length - suffix.length) !== -1) ||
- (e.indexOf(dashed, e.length - dashed.length) !== -1);
- });
+ const suffix = parsedName.type + 's/' + parsedName.fullNameWithoutType,
+ dashed = Ember.String.dasherize(suffix),
+ moduleName = Ember.keys(requirejs.entries).find(function(e) {
+ return (e.indexOf(suffix, e.length - suffix.length) !== -1) ||
+ (e.indexOf(dashed, e.length - dashed.length) !== -1);
+ });
var module;
if (moduleName) {
@@ -85,27 +85,27 @@ export default Ember.DefaultResolver.extend({
return module;
},
- resolveView: function(parsedName) {
+ resolveView(parsedName) {
return this.findLoadingView(parsedName) || this.customResolve(parsedName) || this._super(parsedName);
},
- resolveHelper: function(parsedName) {
+ resolveHelper(parsedName) {
return this.customResolve(parsedName) || this._super(parsedName);
},
- resolveController: function(parsedName) {
+ resolveController(parsedName) {
return this.customResolve(parsedName) || this._super(parsedName);
},
- resolveComponent: function(parsedName) {
+ resolveComponent(parsedName) {
return this.customResolve(parsedName) || this._super(parsedName);
},
- resolveRoute: function(parsedName) {
+ resolveRoute(parsedName) {
return this.findLoadingRoute(parsedName) || this.customResolve(parsedName) || this._super(parsedName);
},
- resolveTemplate: function(parsedName) {
+ resolveTemplate(parsedName) {
return this.findPluginTemplate(parsedName) ||
this.findMobileTemplate(parsedName) ||
this.findTemplate(parsedName) ||
@@ -125,23 +125,23 @@ export default Ember.DefaultResolver.extend({
return _loadingView;
}),
- findPluginTemplate: function(parsedName) {
+ findPluginTemplate(parsedName) {
var pluginParsedName = this.parseName(parsedName.fullName.replace("template:", "template:javascripts/"));
return this.findTemplate(pluginParsedName);
},
- findMobileTemplate: function(parsedName) {
+ findMobileTemplate(parsedName) {
if (Discourse.Mobile.mobileView) {
var mobileParsedName = this.parseName(parsedName.fullName.replace("template:", "template:mobile/"));
return this.findTemplate(mobileParsedName);
}
},
- findTemplate: function(parsedName) {
- var withoutType = parsedName.fullNameWithoutType,
- slashedType = withoutType.replace(/\./g, '/'),
- decamelized = withoutType.decamelize(),
- templates = Ember.TEMPLATES;
+ findTemplate(parsedName) {
+ const withoutType = parsedName.fullNameWithoutType,
+ slashedType = withoutType.replace(/\./g, '/'),
+ decamelized = withoutType.decamelize(),
+ templates = Ember.TEMPLATES;
return this._super(parsedName) ||
templates[slashedType] ||
@@ -152,7 +152,7 @@ export default Ember.DefaultResolver.extend({
this.findUnderscoredTemplate(parsedName);
},
- findUnderscoredTemplate: function(parsedName) {
+ findUnderscoredTemplate(parsedName) {
var decamelized = parsedName.fullNameWithoutType.decamelize();
var underscored = decamelized.replace(/\-/g, "_");
return Ember.TEMPLATES[underscored];
@@ -160,14 +160,22 @@ export default Ember.DefaultResolver.extend({
// Try to find a template within a special admin namespace, e.g. adminEmail => admin/templates/email
// (similar to how discourse lays out templates)
- findAdminTemplate: function(parsedName) {
+ findAdminTemplate(parsedName) {
var decamelized = parsedName.fullNameWithoutType.decamelize();
- if (decamelized.indexOf('admin') === 0) {
+
+ if (decamelized.indexOf('components') === 0) {
+ const compTemplate = Ember.TEMPLATES['admin/templates/' + decamelized];
+ if (compTemplate) { return compTemplate; }
+ }
+ if (decamelized.indexOf('admin') === 0 || decamelized.indexOf('javascripts/admin') === 0) {
decamelized = decamelized.replace(/^admin\_/, 'admin/templates/');
decamelized = decamelized.replace(/^admin\./, 'admin/templates/');
decamelized = decamelized.replace(/\./g, '_');
- var dashed = decamelized.replace(/_/g, '-');
- return Ember.TEMPLATES[decamelized] || Ember.TEMPLATES[dashed];
+
+ const dashed = decamelized.replace(/_/g, '-');
+ return Ember.TEMPLATES[decamelized] ||
+ Ember.TEMPLATES[dashed] ||
+ Ember.TEMPLATES[dashed.replace('admin-', 'admin/')];
}
}
diff --git a/app/assets/javascripts/discourse/initializers/dynamic-route-builders.js.es6 b/app/assets/javascripts/discourse/initializers/dynamic-route-builders.js.es6
index a2222aa3f..64d7f8e03 100644
--- a/app/assets/javascripts/discourse/initializers/dynamic-route-builders.js.es6
+++ b/app/assets/javascripts/discourse/initializers/dynamic-route-builders.js.es6
@@ -6,7 +6,7 @@ export default {
name: 'dynamic-route-builders',
after: 'register-discourse-location',
- initialize: function(container, app) {
+ initialize(container, app) {
app.DiscoveryCategoryRoute = buildCategoryRoute('latest');
app.DiscoveryParentCategoryRoute = buildCategoryRoute('latest');
app.DiscoveryCategoryNoneRoute = buildCategoryRoute('latest', {no_subcategories: true});
diff --git a/app/assets/javascripts/discourse/routes/discourse_route.js b/app/assets/javascripts/discourse/routes/discourse_route.js
index 5d189de93..766292daa 100644
--- a/app/assets/javascripts/discourse/routes/discourse_route.js
+++ b/app/assets/javascripts/discourse/routes/discourse_route.js
@@ -97,7 +97,8 @@ Discourse.Route.reopenClass({
},
mapRoutes: function() {
- var resources = {};
+ var resources = {},
+ paths = {};
// If a module is defined as `route-map` in discourse or a plugin, its routes
// will be built automatically. You can supply a `resource` property to
@@ -115,6 +116,7 @@ Discourse.Route.reopenClass({
if (!resources[mapObj.resource]) { resources[mapObj.resource] = []; }
resources[mapObj.resource].push(mapObj.map);
+ if (mapObj.path) { paths[mapObj.resource] = mapObj.path; }
}
});
@@ -129,13 +131,32 @@ Discourse.Route.reopenClass({
delete resources.root;
}
- // Apply other resources next
+ var segments = {},
+ standalone = [];
+
Object.keys(resources).forEach(function(r) {
- router.resource(r, function() {
+ var m = /^([^\.]+)\.(.*)$/.exec(r);
+ if (m) {
+ segments[m[1]] = m[2];
+ } else {
+ standalone.push(r);
+ }
+ });
+
+ // Apply other resources next. A little hacky but works!
+ standalone.forEach(function(r) {
+ router.resource(r, {path: paths[r]}, function() {
var res = this;
- resources[r].forEach(function(m) {
- m.call(res);
- });
+ resources[r].forEach(function(m) { m.call(res); });
+
+ var s = segments[r];
+ if (s) {
+ var full = r + '.' + s;
+ res.resource(s, {path: paths[full]}, function() {
+ var nestedRes = this;
+ resources[full].forEach(function(m) { m.call(nestedRes); });
+ });
+ }
});
});
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index 7a9e50fda..9ff7cf1f6 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -181,7 +181,7 @@ td.flaggers td {
}
}
-.site-settings-nav {
+.admin-nav {
width: 18.018%;
margin-top: 30px;
.nav-stacked {
@@ -193,7 +193,7 @@ td.flaggers td {
}
}
-.site-settings-detail {
+.admin-detail {
width: 76.5765%;
min-height: 800px;
margin-left: 0;
diff --git a/app/controllers/admin/plugins_controller.rb b/app/controllers/admin/plugins_controller.rb
new file mode 100644
index 000000000..743fe97d2
--- /dev/null
+++ b/app/controllers/admin/plugins_controller.rb
@@ -0,0 +1,8 @@
+class Admin::PluginsController < Admin::AdminController
+
+ def index
+ # json = Discourse.plugins.map(&:metadata)
+ render_serialized(Discourse.plugins, AdminPluginSerializer)
+ end
+
+end
diff --git a/app/serializers/admin_plugin_serializer.rb b/app/serializers/admin_plugin_serializer.rb
new file mode 100644
index 000000000..28b9eea7b
--- /dev/null
+++ b/app/serializers/admin_plugin_serializer.rb
@@ -0,0 +1,26 @@
+class AdminPluginSerializer < ApplicationSerializer
+ attributes :name,
+ :version,
+ :admin_route
+
+ def name
+ object.metadata.name
+ end
+
+ def version
+ object.metadata.version
+ end
+
+ def admin_route
+ route = object.admin_route
+ return unless route
+
+ ret = route.slice(:location, :label)
+ ret[:full_location] = "adminPlugins.#{ret[:location]}"
+ ret
+ end
+
+ def include_admin_route?
+ admin_route.present?
+ end
+end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index f00b12b0a..0f3cc76ea 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1665,6 +1665,14 @@ en:
all_users: "All Users"
note_html: "Keep this key
secret, all users that have it may create arbitrary posts as any user."
+ plugins:
+ title: "Plugins"
+ installed: "Installed Plugins"
+ name: "Name"
+ none_installed: "You don't have any plugins installed."
+ version: "Version"
+ change_settings: "Change Settings"
+
backups:
title: "Backups"
menu:
diff --git a/config/routes.rb b/config/routes.rb
index 8a41b8be1..a6b2875f7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -34,6 +34,8 @@ Discourse::Application.routes.draw do
namespace :admin, constraints: StaffConstraint.new do
get "" => "admin#index"
+ get 'plugins' => 'plugins#index'
+
resources :site_settings, constraints: AdminConstraint.new do
collection do
get "category/:id" => "site_settings#index"
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index cb34bca3b..99de25729 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -6,6 +6,7 @@ require_dependency 'plugin/auth_provider'
class Plugin::Instance
attr_accessor :path, :metadata
+ attr_reader :admin_route
# Memoized array readers
[:assets, :auth_providers, :color_schemes, :initializers, :javascripts, :styles].each do |att|
@@ -39,6 +40,10 @@ class Plugin::Instance
end
end
+ def add_admin_route(label, location)
+ @admin_route = {label: label, location: location}
+ end
+
def enabled?
return @enabled_site_setting ? SiteSetting.send(@enabled_site_setting) : true
end