Support for `url_list` site setting.
This commit is contained in:
parent
35c58c1b00
commit
84b84a9d7c
|
@ -0,0 +1,91 @@
|
||||||
|
import BufferedContent from 'discourse/mixins/buffered-content';
|
||||||
|
import SiteSetting from 'admin/models/site-setting';
|
||||||
|
|
||||||
|
const CustomTypes = ['bool', 'enum', 'list', 'url_list'];
|
||||||
|
|
||||||
|
export default Ember.Component.extend(BufferedContent, Discourse.ScrollTop, {
|
||||||
|
classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'],
|
||||||
|
content: Ember.computed.alias('setting'),
|
||||||
|
dirty: Discourse.computed.propertyNotEqual('buffered.value', 'setting.value'),
|
||||||
|
validationMessage: null,
|
||||||
|
|
||||||
|
preview: function() {
|
||||||
|
const preview = this.get('setting.preview');
|
||||||
|
if (preview) {
|
||||||
|
return new Handlebars.SafeString("<div class='preview'>" +
|
||||||
|
preview.replace("{{value}}", this.get('buffered.value')) +
|
||||||
|
"</div>");
|
||||||
|
}
|
||||||
|
}.property('buffered.value'),
|
||||||
|
|
||||||
|
typeClass: function() {
|
||||||
|
return this.get('partialType').replace("_", "-");
|
||||||
|
}.property('partialType'),
|
||||||
|
|
||||||
|
enabled: function(key, value) {
|
||||||
|
if (arguments.length > 1) {
|
||||||
|
this.set('buffered.value', value ? 'true' : 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
const bufferedValue = this.get('buffered.value');
|
||||||
|
if (Ember.isEmpty(bufferedValue)) { return false; }
|
||||||
|
return bufferedValue === 'true';
|
||||||
|
}.property('buffered.value'),
|
||||||
|
|
||||||
|
settingName: function() {
|
||||||
|
return this.get('setting.setting').replace(/\_/g, ' ');
|
||||||
|
}.property('setting.setting'),
|
||||||
|
|
||||||
|
partialType: function() {
|
||||||
|
let type = this.get('setting.type');
|
||||||
|
return (CustomTypes.indexOf(type) !== -1) ? type : 'string';
|
||||||
|
}.property('setting.type'),
|
||||||
|
|
||||||
|
partialName: function() {
|
||||||
|
return 'admin/templates/site-settings/' + this.get('partialType');
|
||||||
|
}.property('partialType'),
|
||||||
|
|
||||||
|
_watchEnterKey: function() {
|
||||||
|
const self = this;
|
||||||
|
this.$().on("keydown.site-setting-enter", ".input-setting-string", function (e) {
|
||||||
|
if (e.keyCode === 13) { // enter key
|
||||||
|
self._save();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}.on('didInsertElement'),
|
||||||
|
|
||||||
|
_removeBindings: function() {
|
||||||
|
this.$().off("keydown.site-setting-enter");
|
||||||
|
}.on("willDestroyElement"),
|
||||||
|
|
||||||
|
_save() {
|
||||||
|
const setting = this.get('buffered');
|
||||||
|
const self = this;
|
||||||
|
SiteSetting.update(setting.get('setting'), setting.get('value')).then(function() {
|
||||||
|
self.set('validationMessage', null);
|
||||||
|
self.commitBuffer();
|
||||||
|
}).catch(function(e) {
|
||||||
|
if (e.responseJSON && e.responseJSON.errors) {
|
||||||
|
self.set('validationMessage', e.responseJSON.errors[0]);
|
||||||
|
} else {
|
||||||
|
self.set('validationMessage', I18n.t('generic_error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
save() {
|
||||||
|
this._save();
|
||||||
|
},
|
||||||
|
|
||||||
|
resetDefault() {
|
||||||
|
this.set('buffered.value', this.get('setting.default'));
|
||||||
|
this._save();
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.rollbackBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
_setupUrls: function() {
|
||||||
|
const value = this.get('value');
|
||||||
|
this.set('urls', (value && value.length) ? value.split("\n") : []);
|
||||||
|
}.on('init').observes('value'),
|
||||||
|
|
||||||
|
_urlsChanged: function() {
|
||||||
|
this.set('value', this.get('urls').join("\n"));
|
||||||
|
}.observes('urls.@each'),
|
||||||
|
|
||||||
|
urlInvalid: Ember.computed.empty('newUrl'),
|
||||||
|
|
||||||
|
keyDown(e) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
this.send('addUrl');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
addUrl() {
|
||||||
|
if (this.get('urlInvalid')) { return; }
|
||||||
|
|
||||||
|
this.get('urls').addObject(this.get('newUrl'));
|
||||||
|
this.set('newUrl', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
removeUrl(url) {
|
||||||
|
const urls = this.get('urls');
|
||||||
|
urls.removeObject(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -5,46 +5,12 @@ export default Ember.ObjectController.extend({
|
||||||
filteredContent: function() {
|
filteredContent: function() {
|
||||||
if (!this.get('categoryNameKey')) { return []; }
|
if (!this.get('categoryNameKey')) { return []; }
|
||||||
|
|
||||||
var category = this.get('controllers.adminSiteSettings.content').findProperty('nameKey', this.get('categoryNameKey'));
|
const category = this.get('controllers.adminSiteSettings.content').findProperty('nameKey', this.get('categoryNameKey'));
|
||||||
if (category) {
|
if (category) {
|
||||||
return category.siteSettings;
|
return category.siteSettings;
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}.property('controllers.adminSiteSettings.content', 'categoryNameKey'),
|
}.property('controllers.adminSiteSettings.content', 'categoryNameKey')
|
||||||
|
|
||||||
actions: {
|
|
||||||
|
|
||||||
/**
|
|
||||||
Reset a setting to its default value
|
|
||||||
|
|
||||||
@method resetDefault
|
|
||||||
@param {Discourse.SiteSetting} setting The setting we want to revert
|
|
||||||
**/
|
|
||||||
resetDefault: function(setting) {
|
|
||||||
setting.set('value', setting.get('default'));
|
|
||||||
setting.save();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Save changes to a site setting
|
|
||||||
|
|
||||||
@method save
|
|
||||||
@param {Discourse.SiteSetting} setting The setting we've changed
|
|
||||||
**/
|
|
||||||
save: function(setting) {
|
|
||||||
setting.save();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Cancel changes to a site setting
|
|
||||||
|
|
||||||
@method cancel
|
|
||||||
@param {Discourse.SiteSetting} setting The setting we've changed but want to revert
|
|
||||||
**/
|
|
||||||
cancel: function(setting) {
|
|
||||||
setting.resetValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,17 +3,12 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||||
onlyOverridden: false,
|
onlyOverridden: false,
|
||||||
filtered: Ember.computed.notEmpty('filter'),
|
filtered: Ember.computed.notEmpty('filter'),
|
||||||
|
|
||||||
/**
|
|
||||||
The list of settings based on the current filters
|
|
||||||
|
|
||||||
@property filterContent
|
|
||||||
**/
|
|
||||||
filterContent: Discourse.debounce(function() {
|
filterContent: Discourse.debounce(function() {
|
||||||
|
|
||||||
// If we have no content, don't bother filtering anything
|
// If we have no content, don't bother filtering anything
|
||||||
if (!this.present('allSiteSettings')) return;
|
if (!this.present('allSiteSettings')) return;
|
||||||
|
|
||||||
var filter;
|
let filter;
|
||||||
if (this.get('filter')) {
|
if (this.get('filter')) {
|
||||||
filter = this.get('filter').toLowerCase();
|
filter = this.get('filter').toLowerCase();
|
||||||
}
|
}
|
||||||
|
@ -24,12 +19,11 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this,
|
const self = this,
|
||||||
matches,
|
matchesGroupedByCategory = [{nameKey: 'all_results', name: I18n.t('admin.site_settings.categories.all_results'), siteSettings: []}];
|
||||||
matchesGroupedByCategory = Em.A([{nameKey: 'all_results', name: I18n.t('admin.site_settings.categories.all_results'), siteSettings: []}]);
|
|
||||||
|
|
||||||
_.each(this.get('allSiteSettings'), function(settingsCategory) {
|
this.get('allSiteSettings').forEach(function(settingsCategory) {
|
||||||
matches = settingsCategory.siteSettings.filter(function(item) {
|
const matches = settingsCategory.siteSettings.filter(function(item) {
|
||||||
if (self.get('onlyOverridden') && !item.get('overridden')) return false;
|
if (self.get('onlyOverridden') && !item.get('overridden')) return false;
|
||||||
if (filter) {
|
if (filter) {
|
||||||
if (item.get('setting').toLowerCase().indexOf(filter) > -1) return true;
|
if (item.get('setting').toLowerCase().indexOf(filter) > -1) return true;
|
||||||
|
@ -51,7 +45,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
|
||||||
}, 250).observes('filter', 'onlyOverridden'),
|
}, 250).observes('filter', 'onlyOverridden'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
clearFilter: function() {
|
clearFilter() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
filter: '',
|
filter: '',
|
||||||
onlyOverridden: false
|
onlyOverridden: false
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
const SiteSetting = Discourse.Model.extend({
|
||||||
|
overridden: function() {
|
||||||
|
let val = this.get('value'),
|
||||||
|
defaultVal = this.get('default');
|
||||||
|
|
||||||
|
if (val === null) val = '';
|
||||||
|
if (defaultVal === null) defaultVal = '';
|
||||||
|
|
||||||
|
return val.toString() !== defaultVal.toString();
|
||||||
|
}.property('value', 'default'),
|
||||||
|
|
||||||
|
validValues: function() {
|
||||||
|
const vals = [],
|
||||||
|
translateNames = this.get('translate_names');
|
||||||
|
|
||||||
|
this.get('valid_values').forEach(function(v) {
|
||||||
|
if (v.name && v.name.length > 0) {
|
||||||
|
vals.addObject(translateNames ? {name: I18n.t(v.name), value: v.value} : v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return vals;
|
||||||
|
}.property('valid_values'),
|
||||||
|
|
||||||
|
allowsNone: function() {
|
||||||
|
if ( _.indexOf(this.get('valid_values'), '') >= 0 ) return 'admin.site_settings.none';
|
||||||
|
}.property('valid_values')
|
||||||
|
});
|
||||||
|
|
||||||
|
SiteSetting.reopenClass({
|
||||||
|
findAll() {
|
||||||
|
return Discourse.ajax("/admin/site_settings").then(function (settings) {
|
||||||
|
// Group the results by category
|
||||||
|
const categories = {};
|
||||||
|
settings.site_settings.forEach(function(s) {
|
||||||
|
if (!categories[s.category]) {
|
||||||
|
categories[s.category] = [];
|
||||||
|
}
|
||||||
|
categories[s.category].pushObject(Discourse.SiteSetting.create(s));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.keys(categories).map(function(n) {
|
||||||
|
return {nameKey: n, name: I18n.t('admin.site_settings.categories.' + n), siteSettings: categories[n]};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
update(key, value) {
|
||||||
|
const data = {};
|
||||||
|
data[key] = value;
|
||||||
|
return Discourse.ajax("/admin/site_settings/" + key, { type: 'PUT', data });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SiteSetting;
|
|
@ -1,136 +0,0 @@
|
||||||
Discourse.SiteSetting = Discourse.Model.extend({
|
|
||||||
|
|
||||||
validationMessage: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
Is the boolean setting true?
|
|
||||||
|
|
||||||
@property enabled
|
|
||||||
**/
|
|
||||||
enabled: function(key, value) {
|
|
||||||
|
|
||||||
if (arguments.length > 1) {
|
|
||||||
this.set('value', value ? 'true' : 'false');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.blank('value')) return false;
|
|
||||||
return this.get('value') === 'true';
|
|
||||||
|
|
||||||
}.property('value'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
The name of the setting. Basically, underscores in the setting key are replaced with spaces.
|
|
||||||
|
|
||||||
@property settingName
|
|
||||||
**/
|
|
||||||
settingName: function() {
|
|
||||||
return this.get('setting').replace(/\_/g, ' ');
|
|
||||||
}.property('setting'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
Has the user changed the setting? If so we should save it.
|
|
||||||
|
|
||||||
@property dirty
|
|
||||||
**/
|
|
||||||
dirty: function() {
|
|
||||||
return this.get('originalValue') !== this.get('value');
|
|
||||||
}.property('originalValue', 'value'),
|
|
||||||
|
|
||||||
overridden: function() {
|
|
||||||
var val = this.get('value'),
|
|
||||||
defaultVal = this.get('default');
|
|
||||||
|
|
||||||
if (val === null) val = '';
|
|
||||||
if (defaultVal === null) defaultVal = '';
|
|
||||||
|
|
||||||
return val.toString() !== defaultVal.toString();
|
|
||||||
}.property('value', 'default'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
Reset the setting to its original value.
|
|
||||||
|
|
||||||
@method resetValue
|
|
||||||
**/
|
|
||||||
resetValue: function() {
|
|
||||||
this.set('value', this.get('originalValue'));
|
|
||||||
this.set('validationMessage', null);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
Save the setting's value.
|
|
||||||
|
|
||||||
@method save
|
|
||||||
**/
|
|
||||||
save: function() {
|
|
||||||
// Update the setting
|
|
||||||
var self = this, data = {};
|
|
||||||
data[this.get('setting')] = this.get('value');
|
|
||||||
return Discourse.ajax("/admin/site_settings/" + this.get('setting'), {
|
|
||||||
data: data,
|
|
||||||
type: 'PUT'
|
|
||||||
}).then(function() {
|
|
||||||
self.set('originalValue', self.get('value'));
|
|
||||||
self.set('validationMessage', null);
|
|
||||||
}, function(e) {
|
|
||||||
if (e.responseJSON && e.responseJSON.errors) {
|
|
||||||
self.set('validationMessage', e.responseJSON.errors[0]);
|
|
||||||
} else {
|
|
||||||
self.set('validationMessage', I18n.t('generic_error'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
validValues: function() {
|
|
||||||
var vals, setting;
|
|
||||||
vals = Em.A();
|
|
||||||
setting = this;
|
|
||||||
_.each(this.get('valid_values'), function(v) {
|
|
||||||
if (v.name && v.name.length > 0) {
|
|
||||||
if (setting.translate_names) {
|
|
||||||
vals.addObject({name: I18n.t(v.name), value: v.value});
|
|
||||||
} else {
|
|
||||||
vals.addObject(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return vals;
|
|
||||||
}.property('valid_values'),
|
|
||||||
|
|
||||||
allowsNone: function() {
|
|
||||||
if ( _.indexOf(this.get('valid_values'), '') >= 0 ) return 'admin.site_settings.none';
|
|
||||||
}.property('valid_values')
|
|
||||||
});
|
|
||||||
|
|
||||||
Discourse.SiteSetting.reopenClass({
|
|
||||||
|
|
||||||
findAll: function() {
|
|
||||||
return Discourse.ajax("/admin/site_settings").then(function (settings) {
|
|
||||||
// Group the results by category
|
|
||||||
var categoryNames = [],
|
|
||||||
categories = {},
|
|
||||||
result = Em.A();
|
|
||||||
_.each(settings.site_settings,function(s) {
|
|
||||||
s.originalValue = s.value;
|
|
||||||
if (!categoryNames.contains(s.category)) {
|
|
||||||
categoryNames.pushObject(s.category);
|
|
||||||
categories[s.category] = Em.A();
|
|
||||||
}
|
|
||||||
categories[s.category].pushObject(Discourse.SiteSetting.create(s));
|
|
||||||
});
|
|
||||||
_.each(categoryNames, function(n) {
|
|
||||||
result.pushObject({nameKey: n, name: I18n.t('admin.site_settings.categories.' + n), siteSettings: categories[n]});
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function(key, value) {
|
|
||||||
return Discourse.ajax("/admin/site_settings/" + key, {
|
|
||||||
type: 'PUT',
|
|
||||||
data: { value: value }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
/**
|
export default Discourse.Route.extend({
|
||||||
Handles routes related to viewing and editing site settings within one category.
|
model(params) {
|
||||||
|
|
||||||
@class AdminSiteSettingCategoryRoute
|
|
||||||
@extends Discourse.Route
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
Discourse.AdminSiteSettingsCategoryRoute = Discourse.Route.extend({
|
|
||||||
model: function(params) {
|
|
||||||
// The model depends on user input, so let the controller do the work:
|
// The model depends on user input, so let the controller do the work:
|
||||||
this.controllerFor('adminSiteSettingsCategory').set('categoryNameKey', params.category_id);
|
this.controllerFor('adminSiteSettingsCategory').set('categoryNameKey', params.category_id);
|
||||||
return Em.Object.create({
|
return Ember.Object.create({
|
||||||
nameKey: params.category_id,
|
nameKey: params.category_id,
|
||||||
name: I18n.t('admin.site_settings.categories.' + params.category_id),
|
name: I18n.t('admin.site_settings.categories.' + params.category_id),
|
||||||
siteSettings: this.controllerFor('adminSiteSettingsCategory').get('filteredContent')
|
siteSettings: this.controllerFor('adminSiteSettingsCategory').get('filteredContent')
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
Handles when you click the Site Settings tab in admin, but haven't
|
||||||
|
chosen a category. It will redirect to the first category.
|
||||||
|
**/
|
||||||
|
export default Discourse.Route.extend({
|
||||||
|
beforeModel() {
|
||||||
|
this.replaceWith('adminSiteSettingsCategory', this.modelFor('adminSiteSettings')[0].nameKey);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
import SiteSetting from 'admin/models/site-setting';
|
||||||
|
|
||||||
|
export default Discourse.Route.extend({
|
||||||
|
model() {
|
||||||
|
return SiteSetting.findAll();
|
||||||
|
},
|
||||||
|
|
||||||
|
afterModel(siteSettings) {
|
||||||
|
this.controllerFor('adminSiteSettings').set('allSiteSettings', siteSettings);
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
Handles routes related to viewing and editing site settings.
|
|
||||||
|
|
||||||
@class AdminSiteSettingsRoute
|
|
||||||
@extends Discourse.Route
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
Discourse.AdminSiteSettingsRoute = Discourse.Route.extend({
|
|
||||||
model: function() {
|
|
||||||
return Discourse.SiteSetting.findAll();
|
|
||||||
},
|
|
||||||
|
|
||||||
afterModel: function(siteSettings) {
|
|
||||||
this.controllerFor('adminSiteSettings').set('allSiteSettings', siteSettings);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
Handles when you click the Site Settings tab in admin, but haven't
|
|
||||||
chosen a category. It will redirect to the first category.
|
|
||||||
**/
|
|
||||||
Discourse.AdminSiteSettingsIndexRoute = Discourse.Route.extend({
|
|
||||||
model: function() {
|
|
||||||
this.replaceWith('adminSiteSettingsCategory', this.modelFor('adminSiteSettings')[0].nameKey);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div {{bind-attr class=":validation-error message::hidden"}}>
|
||||||
|
{{fa-icon "times"}}
|
||||||
|
{{message}}
|
||||||
|
</div>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<div class='setting-label'>
|
||||||
|
<h3>{{unbound settingName}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="setting-value">
|
||||||
|
{{partial partialName}}
|
||||||
|
</div>
|
||||||
|
{{#if dirty}}
|
||||||
|
<div class='setting-controls'>
|
||||||
|
{{d-button class="ok no-text" action="save" icon="check"}}
|
||||||
|
{{d-button class="cancel no-text" action="cancel" icon="times"}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{#if setting.overridden}}
|
||||||
|
{{d-button action="resetDefault" icon="undo" label="admin.site_settings.reset"}}
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{{#if urls}}
|
||||||
|
<div class='urls'>
|
||||||
|
{{#each url in urls}}
|
||||||
|
<div class='url'>
|
||||||
|
{{d-button action="removeUrl"
|
||||||
|
actionParam=url
|
||||||
|
icon="times"
|
||||||
|
class="btn-small no-text"}}
|
||||||
|
<a href="{{unbound url}}" target="_blank">{{url}}</a>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class='input'>
|
||||||
|
{{text-field value=newUrl placeholderKey="admin.site_settings.add_url"}}
|
||||||
|
{{d-button action="addUrl" icon="plus" class="btn-primary btn-small no-text" disabled=urlInvalid}}
|
||||||
|
</div>
|
|
@ -0,0 +1,10 @@
|
||||||
|
{{#if filteredContent}}
|
||||||
|
<div class='form-horizontal settings'>
|
||||||
|
{{#each setting in filteredContent}}
|
||||||
|
{{site-setting setting=setting saveAction="saveSetting"}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<br/>
|
||||||
|
{{i18n 'admin.site_settings.no_results'}}
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<label>
|
||||||
|
{{input type="checkbox" checked=enabled}}
|
||||||
|
{{unbound setting.description}}
|
||||||
|
</label>
|
|
@ -0,0 +1,4 @@
|
||||||
|
{{combo-box valueAttribute="value" content=setting.validValues value=buffered.value none=setting.allowsNone}}
|
||||||
|
{{preview}}
|
||||||
|
{{setting-validation-message message=validationMessage}}
|
||||||
|
<div class='desc'>{{unbound setting.description}}</div>
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{list-setting settingValue=buffered.value choices=setting.choices settingName=setting.setting}}
|
||||||
|
{{setting-validation-message message=validationMessage}}
|
||||||
|
<div class='desc'>{{unbound setting.description}}</div>
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{text-field value=buffered.value classNames="input-setting-string"}}
|
||||||
|
{{setting-validation-message message=validationMessage}}
|
||||||
|
<div class='desc'>{{unbound setting.description}}</div>
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{url-list value=buffered.value}}
|
||||||
|
{{setting-validation-message message=validationMessage}}
|
||||||
|
<div class='desc'>{{unbound setting.description}}</div>
|
|
@ -1,19 +0,0 @@
|
||||||
<div class='setting-label'>
|
|
||||||
<h3>{{unbound settingName}}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="setting-value">
|
|
||||||
<label>
|
|
||||||
{{input type="checkbox" checked=enabled}}
|
|
||||||
{{unbound description}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{{#if dirty}}
|
|
||||||
<div class='setting-controls'>
|
|
||||||
<button class='btn ok no-text' {{action "save" this}}><i class='fa fa-check'></i></button>
|
|
||||||
<button class='btn cancel no-text' {{action "cancel" this}}><i class='fa fa-times'></i></button>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
{{#if overridden}}
|
|
||||||
<button class='btn' href='#' {{action "resetDefault" this}}><i class="fa fa-undo"></i>{{i18n 'admin.site_settings.reset'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<div class='setting-label'>
|
|
||||||
<h3>{{unbound settingName}}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="setting-value">
|
|
||||||
{{combo-box valueAttribute="value" content=validValues value=value none=allowsNone}}
|
|
||||||
{{view.preview}}
|
|
||||||
<div class='desc'>{{unbound description}}</div>
|
|
||||||
</div>
|
|
||||||
{{#if dirty}}
|
|
||||||
<div class='setting-controls'>
|
|
||||||
<button class='btn ok no-text' {{action "save" this}}><i class='fa fa-check'></i></button>
|
|
||||||
<button class='btn cancel no-text' {{action "cancel" this}}><i class='fa fa-times'></i></button>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
{{#if overridden}}
|
|
||||||
<button class='btn' href='#' {{action "resetDefault" this}}><i class="fa fa-undo"></i>{{i18n 'admin.site_settings.reset'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<div class='setting-label'>
|
|
||||||
<h3>{{unbound settingName}}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="setting-value">
|
|
||||||
{{list-setting settingValue=value choices=choices settingName=setting}}
|
|
||||||
<div {{bind-attr class=":validation-error validationMessage::hidden"}}><i class='fa fa-times'></i> {{validationMessage}}</div>
|
|
||||||
<div class='desc'>{{{unbound description}}}</div>
|
|
||||||
</div>
|
|
||||||
{{#if dirty}}
|
|
||||||
<div class='setting-controls'>
|
|
||||||
<button class='btn ok no-text' {{action "save" this}}><i class='fa fa-check'></i></button>
|
|
||||||
<button class='btn cancel no-text' {{action "cancel" this}}><i class='fa fa-times'></i></button>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
{{#if overridden}}
|
|
||||||
<button class='btn' href='#' {{action "resetDefault" this}}><i class="fa fa-undo"></i>{{i18n 'admin.site_settings.reset'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<div class='setting-label'>
|
|
||||||
<h3>{{unbound settingName}}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="setting-value">
|
|
||||||
{{text-field value=value classNames="input-setting-string"}}
|
|
||||||
<div {{bind-attr class=":validation-error validationMessage::hidden"}}><i class='fa fa-times'></i> {{validationMessage}}</div>
|
|
||||||
<div class='desc'>{{unbound description}}</div>
|
|
||||||
</div>
|
|
||||||
{{#if dirty}}
|
|
||||||
<div class='setting-controls'>
|
|
||||||
<button class='btn ok no-text' {{action "save" this}}><i class='fa fa-check'></i></button>
|
|
||||||
<button class='btn cancel no-text' {{action "cancel" this}}><i class='fa fa-times'></i></button>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
{{#if overridden}}
|
|
||||||
<button class='btn' href='#' {{action "resetDefault" this}}><i class="fa fa-undo"></i>{{i18n 'admin.site_settings.reset'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{{#if filteredContent.length}}
|
|
||||||
{{collection contentBinding="filteredContent" classNames="form-horizontal settings" itemView="site-setting"}}
|
|
||||||
{{else}}
|
|
||||||
<br/>
|
|
||||||
{{i18n 'admin.site_settings.no_results'}}
|
|
||||||
{{/if}}
|
|
|
@ -1,3 +0,0 @@
|
||||||
Discourse.AdminSiteSettingsCategoryView = Discourse.View.extend({
|
|
||||||
templateName: 'admin/templates/site_settings_category'
|
|
||||||
});
|
|
|
@ -1,45 +0,0 @@
|
||||||
export default Discourse.View.extend(Discourse.ScrollTop, {
|
|
||||||
classNameBindings: [':row', ':setting', 'content.overridden'],
|
|
||||||
|
|
||||||
preview: function() {
|
|
||||||
var preview = this.get('content.preview');
|
|
||||||
if(preview){
|
|
||||||
return new Handlebars.SafeString("<div class='preview'>" +
|
|
||||||
preview.replace("{{value}}",this.get('content.value')) +
|
|
||||||
"</div>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}.property('content.value'),
|
|
||||||
|
|
||||||
templateName: function() {
|
|
||||||
// If we're editing a boolean, show a checkbox
|
|
||||||
if (this.get('content.type') === 'bool') return 'admin/templates/site_settings/setting_bool';
|
|
||||||
|
|
||||||
// If we're editing an enum field, show a dropdown
|
|
||||||
if (this.get('content.type') === 'enum') return 'admin/templates/site_settings/setting_enum';
|
|
||||||
|
|
||||||
// If we're editing a list, show a list editor
|
|
||||||
if (this.get('content.type') === 'list') return 'admin/templates/site_settings/setting_list';
|
|
||||||
|
|
||||||
// Default to string editor
|
|
||||||
return 'admin/templates/site_settings/setting_string';
|
|
||||||
|
|
||||||
}.property('content.type'),
|
|
||||||
|
|
||||||
_watchEnterKey: function() {
|
|
||||||
var self = this;
|
|
||||||
this.$().on("keydown.site-setting-enter", ".input-setting-string", function (e) {
|
|
||||||
if (e.keyCode === 13) { // enter key
|
|
||||||
var setting = self.get('content');
|
|
||||||
if (setting.get('dirty')) {
|
|
||||||
setting.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}.on('didInsertElement'),
|
|
||||||
|
|
||||||
_removeBindings: function() {
|
|
||||||
this.$().off("keydown.site-setting-enter");
|
|
||||||
}.on("willDestroyElement")
|
|
||||||
|
|
||||||
});
|
|
|
@ -52,11 +52,10 @@ export default Discourse.View.extend({
|
||||||
var $combo = this.$(),
|
var $combo = this.$(),
|
||||||
val = this.get('value');
|
val = this.get('value');
|
||||||
if (val !== undefined && val !== null) {
|
if (val !== undefined && val !== null) {
|
||||||
$combo.val(val.toString());
|
$combo.select2('val', val.toString());
|
||||||
} else {
|
} else {
|
||||||
$combo.val(null);
|
$combo.select2('val', null);
|
||||||
}
|
}
|
||||||
$combo.trigger("liszt:updated");
|
|
||||||
}.observes('value'),
|
}.observes('value'),
|
||||||
|
|
||||||
contentChanged: function() {
|
contentChanged: function() {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//= require list-view
|
//= require list-view
|
||||||
//= require admin/models/user-field
|
//= require admin/models/user-field
|
||||||
|
//= require admin/models/site-setting
|
||||||
//= require admin/controllers/admin-email-skipped
|
//= require admin/controllers/admin-email-skipped
|
||||||
//= require admin/controllers/change-site-customization-details
|
//= require admin/controllers/change-site-customization-details
|
||||||
//= require discourse/lib/export-result
|
//= require discourse/lib/export-result
|
||||||
|
|
|
@ -282,15 +282,16 @@ td.flaggers td {
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting.overridden {
|
.setting.overridden {
|
||||||
input[type=text] {
|
|
||||||
background-color: dark-light-diff($highlight, $secondary, 50%, -60%);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: scale-color($highlight, $lightness: -50%);
|
color: scale-color($highlight, $lightness: -50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting.overridden.string {
|
||||||
|
input[type=text] {
|
||||||
|
background-color: dark-light-diff($highlight, $secondary, 50%, -60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section.details {
|
section.details {
|
||||||
|
@ -1446,3 +1447,23 @@ table#user-badges {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.url-list {
|
||||||
|
.url {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding: 3px;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.urls {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
class Admin::SiteSettingsController < Admin::AdminController
|
class Admin::SiteSettingsController < Admin::AdminController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
site_settings = SiteSetting.all_settings
|
render_json_dump(site_settings: SiteSetting.all_settings, diags: SiteSetting.diags)
|
||||||
info = {site_settings: site_settings, diags: SiteSetting.diags }
|
|
||||||
render_json_dump(info.as_json)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|
|
@ -2161,6 +2161,7 @@ en:
|
||||||
none: 'none'
|
none: 'none'
|
||||||
no_results: "No results found."
|
no_results: "No results found."
|
||||||
clear_filter: "Clear"
|
clear_filter: "Clear"
|
||||||
|
add_url: "add URL"
|
||||||
categories:
|
categories:
|
||||||
all_results: 'All'
|
all_results: 'All'
|
||||||
required: 'Required'
|
required: 'Required'
|
||||||
|
|
|
@ -1067,6 +1067,7 @@ en:
|
||||||
allow_uploaded_avatars: "Allow users to upload custom avatars."
|
allow_uploaded_avatars: "Allow users to upload custom avatars."
|
||||||
allow_animated_avatars: "Allow users to use animated gif avatars. WARNING: run the avatars:refresh rake task after changing this setting."
|
allow_animated_avatars: "Allow users to use animated gif avatars. WARNING: run the avatars:refresh rake task after changing this setting."
|
||||||
allow_animated_thumbnails: "Generates animated thumbnails of animated gifs."
|
allow_animated_thumbnails: "Generates animated thumbnails of animated gifs."
|
||||||
|
default_avatars: "URLs to avatars that will be used by default for new users until they change them."
|
||||||
automatically_download_gravatars: "Download Gravatars for users upon account creation or email change."
|
automatically_download_gravatars: "Download Gravatars for users upon account creation or email change."
|
||||||
digest_topics: "The maximum number of topics to display in the email digest."
|
digest_topics: "The maximum number of topics to display in the email digest."
|
||||||
digest_min_excerpt_length: "Minimum post excerpt in the email digest, in characters."
|
digest_min_excerpt_length: "Minimum post excerpt in the email digest, in characters."
|
||||||
|
|
|
@ -515,6 +515,10 @@ files:
|
||||||
client: true
|
client: true
|
||||||
default: false
|
default: false
|
||||||
allow_animated_thumbnails: false
|
allow_animated_thumbnails: false
|
||||||
|
avatars:
|
||||||
|
default_avatars:
|
||||||
|
default: ''
|
||||||
|
type: url_list
|
||||||
|
|
||||||
trust:
|
trust:
|
||||||
default_trust_level:
|
default_trust_level:
|
||||||
|
|
|
@ -14,7 +14,7 @@ module SiteSettingExtension
|
||||||
end
|
end
|
||||||
|
|
||||||
def types
|
def types
|
||||||
@types ||= Enum.new(:string, :time, :fixnum, :float, :bool, :null, :enum, :list)
|
@types ||= Enum.new(:string, :time, :fixnum, :float, :bool, :null, :enum, :list, :url_list)
|
||||||
end
|
end
|
||||||
|
|
||||||
def mutex
|
def mutex
|
||||||
|
@ -38,8 +38,8 @@ module SiteSettingExtension
|
||||||
@enums ||= {}
|
@enums ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def lists
|
def static_types
|
||||||
@lists ||= []
|
@static_types ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def choices
|
def choices
|
||||||
|
@ -73,9 +73,9 @@ module SiteSettingExtension
|
||||||
categories[name] = opts[:category] || :uncategorized
|
categories[name] = opts[:category] || :uncategorized
|
||||||
current_value = current.has_key?(name) ? current[name] : default
|
current_value = current.has_key?(name) ? current[name] : default
|
||||||
|
|
||||||
if opts[:enum]
|
if enum = opts[:enum]
|
||||||
enum = opts[:enum]
|
|
||||||
enums[name] = enum.is_a?(String) ? enum.constantize : enum
|
enums[name] = enum.is_a?(String) ? enum.constantize : enum
|
||||||
|
opts[:type] ||= :enum
|
||||||
end
|
end
|
||||||
|
|
||||||
if opts[:choices]
|
if opts[:choices]
|
||||||
|
@ -84,8 +84,8 @@ module SiteSettingExtension
|
||||||
choices[name] = opts[:choices]
|
choices[name] = opts[:choices]
|
||||||
end
|
end
|
||||||
|
|
||||||
if opts[:type] == 'list'
|
if type = opts[:type]
|
||||||
lists << name
|
static_types[name.to_sym] = type.to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
if opts[:hidden]
|
if opts[:hidden]
|
||||||
|
@ -257,7 +257,7 @@ module SiteSettingExtension
|
||||||
clear_cache!
|
clear_cache!
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_override!(name,val)
|
def add_override!(name, val)
|
||||||
type = get_data_type(name, defaults[name])
|
type = get_data_type(name, defaults[name])
|
||||||
|
|
||||||
if type == types[:bool] && val != true && val != false
|
if type == types[:bool] && val != true && val != false
|
||||||
|
@ -344,10 +344,14 @@ module SiteSettingExtension
|
||||||
[changes,deletions]
|
[changes,deletions]
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_data_type(name,val)
|
def get_data_type(name, val)
|
||||||
return types[:null] if val.nil?
|
return types[:null] if val.nil?
|
||||||
return types[:enum] if enums[name]
|
|
||||||
return types[:list] if lists.include? name
|
# Some types are just for validations like email. Only consider
|
||||||
|
# it valid if includes in `types`
|
||||||
|
if static_type = static_types[name.to_sym]
|
||||||
|
return types[static_type] if types.keys.include?(static_type)
|
||||||
|
end
|
||||||
|
|
||||||
case val
|
case val
|
||||||
when String
|
when String
|
||||||
|
@ -369,13 +373,14 @@ module SiteSettingExtension
|
||||||
value.to_f
|
value.to_f
|
||||||
when types[:fixnum]
|
when types[:fixnum]
|
||||||
value.to_i
|
value.to_i
|
||||||
when types[:string], types[:list], types[:enum]
|
|
||||||
value
|
|
||||||
when types[:bool]
|
when types[:bool]
|
||||||
value == true || value == "t" || value == "true"
|
value == true || value == "t" || value == "true"
|
||||||
when types[:null]
|
when types[:null]
|
||||||
nil
|
nil
|
||||||
else
|
else
|
||||||
|
return value if types[type]
|
||||||
|
|
||||||
|
# Otherwise it's a type error
|
||||||
raise ArgumentError.new :type
|
raise ArgumentError.new :type
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue