Add UI for list site settings

This commit is contained in:
riking 2014-03-29 22:32:33 -07:00
parent 9c4dd1cb35
commit 6779bec58c
8 changed files with 220 additions and 10 deletions

View File

@ -0,0 +1,82 @@
/**
Provide a nice GUI for a pipe-delimited list in the site settings.
@param settingValue is a reference to SiteSetting.value.
@class Discourse.ListSettingComponent
@extends Ember.Component
@namespace Discourse
@module Discourse
**/
Discourse.ListSettingComponent = Ember.Component.extend({
layoutName: 'components/list-setting',
init: function() {
this._super();
this.on("focusOut", this.uncacheValue);
this.set('children', []);
},
canAddNew: true,
readValues: function() {
return this.get('settingValue').split('|');
}.property('settingValue'),
/**
Transfer the debounced value into the settingValue parameter.
This will cause a redraw of the child textboxes.
@param newFocus {Number|undefined} Which list index to focus on next, or undefined to not refocus
**/
uncacheValue: function(newFocus) {
var oldValue = this.get('settingValue'),
newValue = this.get('settingValueCached'),
self = this;
if (newValue !== undefined && newValue !== oldValue) {
this.set('settingValue', newValue);
}
if (newFocus !== undefined && newFocus > 0) {
Em.run.schedule('afterRender', function() {
var children = self.get('children');
if (newFocus < children.length) {
$(children[newFocus].get('element')).focus();
} else if (newFocus === children.length) {
$(self.get('element')).children().children('.list-add-value').focus();
}
});
}
},
setItemValue: function(index, item) {
var values = this.get('readValues');
values[index] = item;
// Remove blank items
values = values.filter(function(s) { return s !== ''; });
this.setProperties({
settingValueCached: values.join('|'),
canAddNew: true
});
},
actions: {
addNewItem: function() {
var newValue = this.get('settingValue') + '|';
this.setProperties({
settingValue: newValue,
settingValueCached: newValue,
canAddNew: false
});
var self = this;
Em.run.schedule('afterRender', function() {
var children = self.get('children');
$(children[children.length - 1].get('element')).focus();
});
}
}
});

View File

@ -0,0 +1,44 @@
/**
One item in a ListSetting.
@param parent is the ListSettingComponent.
@class Discourse.ListSettingItemComponent
@extends Ember.Component, Ember.TextSupport
@namespace Discourse
@module Discourse
**/
Discourse.ListSettingItemComponent = Ember.Component.extend(Ember.TextSupport, {
classNames: ['ember-text-field'],
tagName: "input",
attributeBindings: ['type', 'value', 'size', 'pattern'],
_initialize: function() {
// _parentView is the #each
// parent is the ListSettingComponent
this.setProperties({
value: this.get('_parentView.content'),
index: this.get('_parentView.contentIndex')
});
this.get('parent').get('children')[this.get('index')] = this;
}.on('init'),
markTab: function(e) {
var keyCode = e.keyCode || e.which;
if (keyCode === 9) {
this.set('nextIndex', this.get('index') + (e.shiftKey ? -1 : 1));
}
}.on('keyDown'),
reloadList: function() {
var nextIndex = this.get('nextIndex');
this.set('nextIndex', undefined); // one use only
this.get('parent').uncacheValue(nextIndex);
}.on('focusOut'),
_elementValueDidChange: function() {
this._super();
this.get('parent').setItemValue(this.get('index'), this.get('value'));
}
});

View File

@ -0,0 +1,17 @@
<div class='setting-label'>
<h3>{{unbound setting}}</h3>
</div>
<div class="setting-value">
{{list-setting settingValue=value}}
<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}}>{{i18n admin.site_settings.reset}}</button>
{{/if}}
{{/if}}

View File

@ -10,13 +10,15 @@ Discourse.SiteSettingView = Discourse.View.extend(Discourse.ScrollTop, {
classNameBindings: [':row', ':setting', 'content.overridden'], classNameBindings: [':row', ':setting', 'content.overridden'],
templateName: function() { templateName: function() {
// If we're editing a boolean, show a checkbox
// If we're editing a boolean, return a different template
if (this.get('content.type') === 'bool') return 'admin/templates/site_settings/setting_bool'; if (this.get('content.type') === 'bool') return 'admin/templates/site_settings/setting_bool';
// If we're editing an enum field, show a dropdown // If we're editing an enum field, show a dropdown
if (this.get('content.type') === 'enum' ) return 'admin/templates/site_settings/setting_enum'; 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 // Default to string editor
return 'admin/templates/site_settings/setting_string'; return 'admin/templates/site_settings/setting_string';

View File

@ -0,0 +1,8 @@
<div class="ac-wrap clearfix input-setting-list">
{{#each readValues}}
{{list-setting-item parent=view action=update classNames="list-input-item"}}
{{/each}}
{{#if canAddNew}}
<button class="btn no-text list-add-value" {{action addNewItem}}><i class="fa fa-plus"></i></button>
{{/if}}
</div>

View File

@ -194,6 +194,40 @@
@include medium-width { width: 314px; } @include medium-width { width: 314px; }
@include small-width { width: 284px; } @include small-width { width: 284px; }
} }
.input-setting-list {
width: 408px;
padding: 1px;
background-color: white;
border: 1px solid #e6e6e6;
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(51, 51, 51, 0.3);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
.list-input-item {
width: 90px;
margin: 2px 1px;
background-color: white;
border: 1px solid #e6e6e6;
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(51, 51, 51, 0.3);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
&:focus {
border-color: #00aaff;
outline: 0;
box-shadow: inset 0 1px 1px rgba(51, 51, 51, 0.3), 0 0 8px #00aaff;
}
}
.btn.list-add-value {
margin: 0px 3px;
padding: 4px 10px;
color: $link-color;
}
}
.desc { .desc {
padding-top: 3px; padding-top: 3px;

View File

@ -53,15 +53,19 @@ basic:
top_menu: top_menu:
client: true client: true
refresh: true refresh: true
list: true
default: 'latest|new|unread|starred|top|categories' default: 'latest|new|unread|starred|top|categories'
post_menu: post_menu:
client: true client: true
list: true
default: 'like|edit|flag|delete|share|bookmark|reply' default: 'like|edit|flag|delete|share|bookmark|reply'
share_links: share_links:
client: true client: true
list: true
default: 'twitter|facebook|google+|email' default: 'twitter|facebook|google+|email'
category_colors: category_colors:
client: true client: true
list: true
default: 'BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890' default: 'BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890'
enable_mobile_theme: enable_mobile_theme:
client: true client: true
@ -276,6 +280,7 @@ files:
client: true client: true
default: '.jpg|.jpeg|.png|.gif' default: '.jpg|.jpeg|.png|.gif'
refresh: true refresh: true
list: true
crawl_images: crawl_images:
default: default:
test: false test: false
@ -340,9 +345,15 @@ security:
spam: spam:
add_rel_nofollow_to_user_content: true add_rel_nofollow_to_user_content: true
exclude_rel_nofollow_domains: '' exclude_rel_nofollow_domains:
email_domains_blacklist: 'mailinator.com' default: ''
email_domains_whitelist: '' list: true
email_domains_blacklist:
default: 'mailinator.com'
list: true
email_domains_whitelist:
default: ''
list: true
flags_required_to_hide_post: 3 flags_required_to_hide_post: 3
cooldown_minutes_after_hiding_posts: 10 cooldown_minutes_after_hiding_posts: 10
num_flags_to_block_new_user: 3 num_flags_to_block_new_user: 3
@ -350,7 +361,9 @@ spam:
notify_mods_when_user_blocked: false notify_mods_when_user_blocked: false
flag_sockpuppets: true flag_sockpuppets: true
newuser_spam_host_threshold: 3 newuser_spam_host_threshold: 3
white_listed_spam_host_domains: "" white_listed_spam_host_domains:
default: ''
list: true
rate_limits: rate_limits:
unique_posts_mins: unique_posts_mins:

View File

@ -14,7 +14,7 @@ module SiteSettingExtension
end end
def types def types
@types ||= Enum.new(:string, :time, :fixnum, :float, :bool, :null, :enum) @types ||= Enum.new(:string, :time, :fixnum, :float, :bool, :null, :enum, :list)
end end
def mutex def mutex
@ -38,6 +38,10 @@ module SiteSettingExtension
@enums ||= {} @enums ||= {}
end end
def lists
@lists ||= []
end
def hidden_settings def hidden_settings
@hidden_settings ||= [] @hidden_settings ||= []
end end
@ -56,10 +60,13 @@ module SiteSettingExtension
enum = opts[:enum] enum = opts[:enum]
enums[name] = enum.is_a?(String) ? enum.constantize : enum enums[name] = enum.is_a?(String) ? enum.constantize : enum
end end
if opts[:hidden] == true if opts[:list]
lists << name
end
if opts[:hidden]
hidden_settings << name hidden_settings << name
end end
if opts[:refresh] == true if opts[:refresh]
refresh_settings << name refresh_settings << name
end end
@ -261,6 +268,7 @@ module SiteSettingExtension
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[:enum] if enums[name]
return types[:list] if lists.include? name
case val case val
when String when String
@ -278,12 +286,14 @@ module SiteSettingExtension
case type case type
when types[:fixnum] when types[:fixnum]
value.to_i value.to_i
when types[:string], types[:enum] when types[:string], types[:list], types[:enum]
value 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
raise ArgumentError.new :type
end end
end end