Support for "Only show overridden" in site text customization

This commit is contained in:
Robin Ward 2015-11-30 15:22:58 -05:00
parent 04593b8fef
commit de88be2fbc
17 changed files with 119 additions and 35 deletions

View File

@ -2,6 +2,7 @@ import { on } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({ export default Ember.Component.extend({
classNames: ['site-text'], classNames: ['site-text'],
classNameBindings: ['siteText.overridden'],
@on('didInsertElement') @on('didInsertElement')
highlightTerm() { highlightTerm() {

View File

@ -5,8 +5,20 @@ export default Ember.Controller.extend({
searching: false, searching: false,
siteTexts: null, siteTexts: null,
preferred: false, preferred: false,
_overridden: null,
queryParams: ['q', 'overridden'],
queryParams: ['q'], @computed
overridden: {
set(value) {
if (!value || value === "false") { value = false; }
this._overridden = value;
return value;
},
get() {
return this._overridden;
}
},
@computed @computed
q: { q: {
@ -21,8 +33,7 @@ export default Ember.Controller.extend({
}, },
_performSearch() { _performSearch() {
const q = this.get('q'); this.store.find('site-text', this.getProperties('q', 'overridden')).then(results => {
this.store.find('site-text', { q }).then(results => {
this.set('siteTexts', results); this.set('siteTexts', results);
}).finally(() => this.set('searching', false)); }).finally(() => this.set('searching', false));
}, },

View File

@ -1,10 +1,11 @@
export default Ember.Route.extend({ export default Ember.Route.extend({
queryParams: { queryParams: {
q: { replace: true } q: { replace: true },
overridden: { replace: true }
}, },
model(params) { model(params) {
return this.store.find('site-text', { q: params.q }); return this.store.find('site-text', Ember.getProperties(params, 'q', 'overridden'));
}, },
setupController(controller, model) { setupController(controller, model) {

View File

@ -5,13 +5,17 @@
placeholderKey="admin.site_text.search" placeholderKey="admin.site_text.search"
class="no-blur site-text-search" class="no-blur site-text-search"
autofocus="true" autofocus="true"
keyUpAction="search"}} key-up="search"}}
<div class='extra-options'>
{{d-checkbox label="admin.site_text.show_overriden" checked=overridden change="search"}}
</div>
</div> </div>
{{#conditional-loading-spinner condition=searching}} {{#conditional-loading-spinner condition=searching}}
{{#unless siteTexts.findArgs.q}} {{#if siteTexts.extras.recommended}}
<p><b>{{i18n "admin.site_text.recommended"}}</b></p> <p><b>{{i18n "admin.site_text.recommended"}}</b></p>
{{/unless}} {{/if}}
{{#each siteTexts as |siteText|}} {{#each siteTexts as |siteText|}}
{{site-text-summary siteText=siteText editAction="edit" term=q}} {{site-text-summary siteText=siteText editAction="edit" term=q}}

View File

@ -0,0 +1,18 @@
import { on } from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
tagName: 'label',
@on('didInsertElement')
_watchChanges() {
// In Ember 13.3 we can use action on the checkbox `{{input}}` but not in 1.11
this.$('input').on('click.d-checkbox', () => {
Ember.run.scheduleOnce('afterRender', () => this.sendAction('change'));
});
},
@on('willDestroyElement')
_stopWatching() {
this.$('input').off('click.d-checkbox');
}
});

View File

@ -6,12 +6,5 @@ export default Ember.TextField.extend({
@computed("placeholderKey") @computed("placeholderKey")
placeholder(placeholderKey) { placeholder(placeholderKey) {
return placeholderKey ? I18n.t(placeholderKey) : ""; return placeholderKey ? I18n.t(placeholderKey) : "";
},
keyUp() {
const act = this.get('keyUpAction');
if (act) {
this.sendAction('keyUpAction');
}
} }
}); });

View File

@ -88,9 +88,9 @@ export default Ember.Object.extend({
refreshResults(resultSet, type, url) { refreshResults(resultSet, type, url) {
const self = this; const self = this;
return Discourse.ajax(url).then(function(result) { return Discourse.ajax(url).then(result => {
const typeName = Ember.String.underscore(self.pluralize(type)), const typeName = Ember.String.underscore(self.pluralize(type));
content = result[typeName].map(obj => self._hydrate(type, obj, result)); const content = result[typeName].map(obj => self._hydrate(type, obj, result));
resultSet.set('content', content); resultSet.set('content', content);
}); });
}, },
@ -143,13 +143,24 @@ export default Ember.Object.extend({
}, },
_resultSet(type, result, findArgs) { _resultSet(type, result, findArgs) {
const typeName = Ember.String.underscore(this.pluralize(type)), const typeName = Ember.String.underscore(this.pluralize(type));
content = result[typeName].map(obj => this._hydrate(type, obj, result)), const content = result[typeName].map(obj => this._hydrate(type, obj, result));
totalRows = result["total_rows_" + typeName] || content.length,
loadMoreUrl = result["load_more_" + typeName],
refreshUrl = result['refresh_' + typeName];
return ResultSet.create({ content, totalRows, loadMoreUrl, refreshUrl, findArgs, store: this, __type: type }); const createArgs = {
content,
findArgs,
totalRows: result["total_rows_" + typeName] || content.length,
loadMoreUrl: result["load_more_" + typeName],
refreshUrl: result['refresh_' + typeName],
store: this,
__type: type
};
if (result.extras) {
createArgs.extras = result.extras;
}
return ResultSet.create(createArgs);
}, },
_build(type, obj) { _build(type, obj) {

View File

@ -0,0 +1,2 @@
{{input type="checkbox" checked=checked}}
{{i18n label}}

View File

@ -50,11 +50,19 @@ td.flaggers td {
margin-top: 0; margin-top: 0;
} }
input { .site-text-search {
padding: 0.5em; padding: 0.5em;
font-size: 1em; font-size: 1em;
width: 50%; width: 50%;
} }
.extra-options {
float: right;
input[type=checkbox] {
margin-right: 0.5em;
}
}
} }
.text-highlight { .text-highlight {
font-weight: bold; font-weight: bold;
@ -65,6 +73,10 @@ td.flaggers td {
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
margin-bottom: 0.5em; margin-bottom: 0.5em;
&.overridden {
background-color: dark-light-diff($highlight, $secondary, 50%, -60%);
}
h3 { h3 {
font-weight: normal; font-weight: normal;
font-size: 1.1em; font-size: 1.1em;

View File

@ -8,11 +8,15 @@ class Admin::SiteTextsController < Admin::AdminController
end end
def index def index
if params[:q].blank? overridden = params[:overridden] == 'true'
extras = {}
if params[:q].blank? && !overridden
extras[:recommended] = true
results = self.class.preferred_keys.map {|k| {id: k, value: I18n.t(k) }} results = self.class.preferred_keys.map {|k| {id: k, value: I18n.t(k) }}
else else
results = [] results = []
translations = I18n.search(params[:q]) translations = I18n.search(params[:q], overridden: overridden)
translations.each do |k, v| translations.each do |k, v|
results << {id: k, value: v} results << {id: k, value: v}
end end
@ -21,7 +25,7 @@ class Admin::SiteTextsController < Admin::AdminController
end end
end end
render_serialized(results[0..50], SiteTextSerializer, root: 'site_texts', rest_serializer: true) render_serialized(results[0..50], SiteTextSerializer, root: 'site_texts', rest_serializer: true, extras: extras)
end end
def show def show

View File

@ -229,6 +229,8 @@ class ApplicationController < ActionController::Base
opts.each do |k, v| opts.each do |k, v|
obj[k] = v if k.to_s.start_with?("refresh_") obj[k] = v if k.to_s.start_with?("refresh_")
end end
obj['extras'] = opts[:extras] if opts[:extras]
end end
render json: MultiJson.dump(obj), status: opts[:status] || 200 render json: MultiJson.dump(obj), status: opts[:status] || 200

View File

@ -1,5 +1,5 @@
class SiteTextSerializer < ApplicationSerializer class SiteTextSerializer < ApplicationSerializer
attributes :id, :value, :can_revert? attributes :id, :value, :overridden?, :can_revert?
def id def id
object[:id] object[:id]
@ -9,12 +9,14 @@ class SiteTextSerializer < ApplicationSerializer
object[:value] object[:value]
end end
def can_revert? def overridden?
current_val = value current_val = value
I18n.overrides_disabled do I18n.overrides_disabled do
return I18n.t(object[:id]) != current_val return I18n.t(object[:id]) != current_val
end end
end end
alias_method :can_revert?, :overridden?
end end

View File

@ -2492,6 +2492,7 @@ en:
revert_confirm: "Are you sure you want to revert your changes?" revert_confirm: "Are you sure you want to revert your changes?"
go_back: "Back to Search" go_back: "Back to Search"
recommended: "We recommend customizing the following text to suit your needs:" recommended: "We recommend customizing the following text to suit your needs:"
show_overriden: 'Only show overridden'
site_settings: site_settings:
show_overriden: 'Only show overridden' show_overriden: 'Only show overridden'

View File

@ -56,7 +56,7 @@ module I18n
opts ||= {} opts ||= {}
target = opts[:backend] || backend target = opts[:backend] || backend
results = target.search(config.locale, query) results = opts[:overridden] ? {} : target.search(config.locale, query)
regexp = /#{query}/i regexp = /#{query}/i
(overrides_by_locale || {}).each do |k, v| (overrides_by_locale || {}).each do |k, v|

View File

@ -6,9 +6,22 @@ test("search for a key", () => {
visit("/admin/customize/site_texts"); visit("/admin/customize/site_texts");
fillIn('.site-text-search', 'Test'); fillIn('.site-text-search', 'Test');
andThen(() => ok(exists('.site-text'))); andThen(() => {
ok(exists('.site-text'));
ok(exists(".site-text:not(.overridden)"));
ok(exists('.site-text.overridden'));
});
// Only show overridden
click('.extra-options input');
andThen(() => {
ok(!exists(".site-text:not(.overridden)"));
ok(exists('.site-text.overridden'));
});
}); });
test("edit and revert a site text by key", () => { test("edit and revert a site text by key", () => {
visit("/admin/customize/site_texts/site.test"); visit("/admin/customize/site_texts/site.test");
andThen(() => { andThen(() => {

View File

@ -210,7 +210,7 @@ export default function() {
}); });
this.get('/fruits', function() { this.get('/fruits', function() {
return response({ __rest_serializer: "1", fruits, farmers, colors }); return response({ __rest_serializer: "1", fruits, farmers, colors, extras: {hello: 'world'} });
}); });
this.get('/widgets/:widget_id', function(request) { this.get('/widgets/:widget_id', function(request) {
@ -262,7 +262,16 @@ export default function() {
this.post('/topics/timings', () => response(200, {})); this.post('/topics/timings', () => response(200, {}));
const siteText = {id: 'site.test', value: 'Test McTest'}; const siteText = {id: 'site.test', value: 'Test McTest'};
this.get('/admin/customize/site_texts', () => response(200, {site_texts: [siteText] })); const overridden = {id: 'site.overridden', value: 'Overridden', overridden: true };
this.get('/admin/customize/site_texts', request => {
if (request.queryParams.overridden) {
return response(200, {site_texts: [overridden] })
} else {
return response(200, {site_texts: [siteText, overridden] })
}
});
this.get('/admin/customize/site_texts/:key', () => response(200, {site_text: siteText })); this.get('/admin/customize/site_texts/:key', () => response(200, {site_text: siteText }));
this.delete('/admin/customize/site_texts/:key', () => response(200, {site_text: siteText })); this.delete('/admin/customize/site_texts/:key', () => response(200, {site_text: siteText }));

View File

@ -116,7 +116,6 @@ test('destroyRecord when new', function(assert) {
}); });
}); });
test('find embedded', function(assert) { test('find embedded', function(assert) {
const store = createStore(); const store = createStore();
return store.find('fruit', 2).then(function(f) { return store.find('fruit', 2).then(function(f) {
@ -136,6 +135,7 @@ test('findAll embedded', function(assert) {
return store.findAll('fruit').then(function(fruits) { return store.findAll('fruit').then(function(fruits) {
assert.equal(fruits.objectAt(0).get('farmer.name'), 'Old MacDonald'); assert.equal(fruits.objectAt(0).get('farmer.name'), 'Old MacDonald');
assert.equal(fruits.objectAt(0).get('farmer'), fruits.objectAt(1).get('farmer'), 'points at the same object'); assert.equal(fruits.objectAt(0).get('farmer'), fruits.objectAt(1).get('farmer'), 'points at the same object');
assert.equal(fruits.get('extras.hello'), 'world', 'it can supply extra information');
const fruitCols = fruits.objectAt(0).get('colors'); const fruitCols = fruits.objectAt(0).get('colors');
assert.equal(fruitCols.length, 2); assert.equal(fruitCols.length, 2);