implement color picking from predefined set for category badges + option to change foreground color

This commit is contained in:
Kuba Brecka 2013-03-14 14:16:57 +01:00
parent a8c44d90a3
commit 8784c55188
17 changed files with 142 additions and 35 deletions

View file

@ -39,10 +39,11 @@ Discourse.Utilities = {
// Create a badge like category link // Create a badge like category link
categoryLink: function(category) { categoryLink: function(category) {
var color, name, description, result; var color, textColor, name, description, result;
if (!category) return ""; if (!category) return "";
color = Em.get(category, 'color'); color = Em.get(category, 'color');
textColor = Em.get(category, 'text_color');
name = Em.get(category, 'name'); name = Em.get(category, 'name');
description = Em.get(category, 'description'); description = Em.get(category, 'description');
@ -52,7 +53,7 @@ Discourse.Utilities = {
// Add description if we have it // Add description if we have it
if (description) result += "title=\"" + description + "\" "; if (description) result += "title=\"" + description + "\" ";
return result + "style=\"background-color: #" + color + "\">" + name + "</a>"; return result + "style=\"background-color: #" + color + "; color: #" + textColor + ";\">" + name + "</a>";
}, },
avatarUrl: function(username, size, template) { avatarUrl: function(username, size, template) {

View file

@ -13,8 +13,8 @@ Discourse.Category = Discourse.Model.extend({
}).property('name'), }).property('name'),
style: (function() { style: (function() {
return "background-color: #" + (this.get('color')); return "background-color: #" + (this.get('category.color')) + "; color: #" + (this.get('category.text_color')) + ";";
}).property('color'), }).property('color', 'text_color'),
moreTopics: (function() { moreTopics: (function() {
return this.get('topic_count') > Discourse.SiteSettings.category_featured_topics; return this.get('topic_count') > Discourse.SiteSettings.category_featured_topics;
@ -32,7 +32,8 @@ Discourse.Category = Discourse.Model.extend({
return this.ajax(url, { return this.ajax(url, {
data: { data: {
name: this.get('name'), name: this.get('name'),
color: this.get('color') color: this.get('color'),
text_color: this.get('text_color')
}, },
type: this.get('id') ? 'PUT' : 'POST', type: this.get('id') ? 'PUT' : 'POST',
success: function(result) { return args.success(result); }, success: function(result) { return args.success(result); },

View file

@ -1,5 +1,5 @@
<div class='contents'> <div class='contents'>
<span class='badge-category' style='background-color: #{{unbound view.color}}'>{{unbound view.name}}</span> <span class='badge-category' style='background-color: #{{unbound view.color}}; color: #{{unbound view.text_color}}'>{{unbound view.name}}</span>
{{#if view.excerpt}} {{#if view.excerpt}}
<div class='description'> <div class='description'>

View file

@ -20,11 +20,22 @@
</div> </div>
<label>{{i18n category.color}}</label> <label>{{i18n category.badge_colors}}</label>
<div class='input-prepend input-append'> <div class="category-color-editor">
<span class='add-on'>#</span>{{view Discourse.TextField valueBinding="view.category.color" placeholderKey="category.color_placeholder" maxlength="6"}} <span class='badge-category' {{bindAttr style="view.colorStyle"}}>{{i18n preview}}</span>
<span class='badge-category' {{bindAttr style="view.colorStyle"}}>{{i18n preview}}</span>
<div class='input-prepend input-append' style="margin-top: 10px;">
<span class='color-title'>{{i18n category.background_color}}:</span>
<span class='add-on'>#</span>{{view Discourse.TextField valueBinding="view.category.color" placeholderKey="category.color_placeholder" maxlength="6"}}
{{view Discourse.ColorsView colorsBinding="view.predefinedColors" valueBinding="view.category.color"}}
</div>
<div class='input-prepend input-append'>
<span class='color-title'>{{i18n category.foreground_color}}:</span>
<span class='add-on'>#</span>{{view Discourse.TextField valueBinding="view.category.text_color" placeholderKey="category.color_placeholder" maxlength="6"}}
{{view Discourse.ColorsView colorsBinding="view.predefinedColors" valueBinding="view.category.text_color"}}
</div>
</div> </div>
</form> </form>

View file

@ -1,6 +1,6 @@
{{#with view.content}} {{#with view.content}}
<a href='{{unbound url}}'> <a href='{{unbound url}}'>
<span class='badge-category' style="background-color: #{{unbound color}}">{{unbound title}}</span> <span class='badge-category' style="background-color: #{{unbound color}}; color: #{{unbound text_color}};">{{unbound title}}</span>
</a> </a>
{{/with}} {{/with}}

View file

@ -8,12 +8,13 @@
**/ **/
Discourse.ComboboxViewCategory = Discourse.ComboboxView.extend({ Discourse.ComboboxViewCategory = Discourse.ComboboxView.extend({
none: 'category.none', none: 'category.none',
dataAttributes: ['color', 'description'], dataAttributes: ['color', 'text_color', 'description'],
template: function(text, templateData) { template: function(text, templateData) {
if (!templateData.color) return text; if (!templateData.color) return text;
var result = "<span class='badge-category' style='background-color: #" + templateData.color + "' " var result = "<span class='badge-category' style='background-color: #" + templateData.color + '; color: #' +
templateData.text_color + ";' ";
if (templateData.description && templateData.description !== 'null') { if (templateData.description && templateData.description !== 'null') {
result += "title=\"" + templateData.description + "\" "; result += "title=\"" + templateData.description + "\" ";
} }

View file

@ -0,0 +1,34 @@
/**
This view shows an array of buttons for selection of a color from a predefined set.
@class ColorsView
@extends Ember.ContainerView
@namespace Discourse
@module Discourse
**/
Discourse.ColorsView = Ember.ContainerView.extend({
classNames: 'colors-container',
init: function() {
this._super();
return this.createButtons();
},
createButtons: function() {
var colors = this.get('colors');
var _this = this;
colors.each(function(color) {
_this.addObject(Discourse.View.create({
tagName: 'button',
attributeBindings: ['style'],
classNames: ['colorpicker'],
style: 'background-color: #' + color + ';',
click: function() {
_this.set("value", color);
return false;
}
}));
});
}
});

View file

@ -18,8 +18,11 @@ Discourse.EditCategoryView = Discourse.ModalBodyView.extend({
}).property('category.name', 'category.color'), }).property('category.name', 'category.color'),
colorStyle: (function() { colorStyle: (function() {
return "background-color: #" + (this.get('category.color')) + ";"; return "background-color: #" + (this.get('category.color')) + "; color: #" + (this.get('category.text_color')) + ";";
}).property('category.color'), }).property('category.color', 'category.text_color'),
predefinedColors: ["FFFFFF", "000000", "AECFC6", "836953", "77DD77", "FFB347", "FDFD96", "536878",
"EC5800", "0096E0", "7C4848", "9AC932", "BA160C", "003366", "B19CD9", "E4717A"],
title: (function() { title: (function() {
if (this.get('category.id')) return Em.String.i18n("category.edit_long"); if (this.get('category.id')) return Em.String.i18n("category.edit_long");
@ -36,7 +39,7 @@ Discourse.EditCategoryView = Discourse.ModalBodyView.extend({
if (this.get('category')) { if (this.get('category')) {
this.set('id', this.get('category.slug')); this.set('id', this.get('category.slug'));
} else { } else {
this.set('category', Discourse.Category.create({ color: 'AB9364' })); this.set('category', Discourse.Category.create({ color: 'AB9364', text_color: 'FFFFFF' }));
} }
}, },

View file

@ -0,0 +1,29 @@
// styles for the category badge color picker
@import "foundation/variables";
@import "foundation/mixins";
.category-color-editor {
input {
width: 70px;
}
.color-title {
display: inline-block;
width: 130px;
}
.colors-container {
display: inline-block;
vertical-align: bottom;
padding-bottom: 2px;
padding-left: 15px;
.colorpicker {
border: 1px solid $darkish_gray;
width: 15px;
height: 15px;
margin-right: 2px;
}
}
}

View file

@ -43,7 +43,7 @@ class CategoriesController < ApplicationController
private private
def category_param_keys def category_param_keys
[:name, :color] [:name, :color, :text_color]
end end
def category_params def category_params

View file

@ -3,7 +3,7 @@ require_dependency 'excerpt_type'
class CategoryExcerptSerializer < ActiveModel::Serializer class CategoryExcerptSerializer < ActiveModel::Serializer
include ExcerptType include ExcerptType
attributes :excerpt, :name, :color, :slug, :topic_url, :topics_year, attributes :excerpt, :name, :color, :text_color, :slug, :topic_url, :topics_year,
:topics_month, :topics_week, :category_url, :can_edit, :can_delete :topics_month, :topics_week, :category_url, :can_edit, :can_delete

View file

@ -3,6 +3,7 @@ class CategorySerializer < ApplicationSerializer
attributes :id, attributes :id,
:name, :name,
:color, :color,
:text_color,
:slug, :slug,
:topic_count, :topic_count,
:description, :description,

View file

@ -584,7 +584,9 @@ cs:
name: "Název kategorie" name: "Název kategorie"
description: "Popis" description: "Popis"
topic: "téma kategorie" topic: "téma kategorie"
color: "Barva" badge_colors: "Barvy štítku"
background_color: "Barva pozadí"
foreground_color: "Barva textu"
name_placeholder: "Měl by být krátký a výstižný." name_placeholder: "Měl by být krátký a výstižný."
color_placeholder: "Jakákoliv webová barva" color_placeholder: "Jakákoliv webová barva"
delete_confirm: "Opravdu chcete odstranit tuto kategorii?" delete_confirm: "Opravdu chcete odstranit tuto kategorii?"

View file

@ -587,7 +587,9 @@ en:
name: "Category Name" name: "Category Name"
description: "Description" description: "Description"
topic: "category topic" topic: "category topic"
color: "Color" badge_colors: "Badge colors"
background_color: "Background color"
foreground_color: "Foreground color"
name_placeholder: "Should be short and succinct." name_placeholder: "Should be short and succinct."
color_placeholder: "Any web color" color_placeholder: "Any web color"
delete_confirm: "Are you sure you want to delete that category?" delete_confirm: "Are you sure you want to delete that category?"

View file

@ -0,0 +1,5 @@
class AddForegroundColorToCategories < ActiveRecord::Migration
def change
add_column :categories, :text_color, :string, limit: 6, null: false, default: 'FFFFFF'
end
end

View file

@ -14,7 +14,8 @@ module Search
'/users/' || u.username_lower AS url, '/users/' || u.username_lower AS url,
u.username AS title, u.username AS title,
u.email, u.email,
NULL AS color NULL AS color,
NULL AS text_color
FROM users AS u FROM users AS u
JOIN users_search s on s.id = u.id JOIN users_search s on s.id = u.id
WHERE s.search_data @@ TO_TSQUERY(:locale, :query) WHERE s.search_data @@ TO_TSQUERY(:locale, :query)
@ -29,7 +30,8 @@ module Search
'/t/slug/' || ft.id AS url, '/t/slug/' || ft.id AS url,
ft.title, ft.title,
NULL AS email, NULL AS email,
NULL AS color NULL AS color,
NULL AS text_color
FROM topics AS ft FROM topics AS ft
JOIN posts AS p ON p.topic_id = ft.id AND p.post_number = 1 JOIN posts AS p ON p.topic_id = ft.id AND p.post_number = 1
JOIN posts_search s on s.id = p.id JOIN posts_search s on s.id = p.id
@ -52,7 +54,8 @@ module Search
'/t/slug/' || ft.id || '/' || p.post_number AS url, '/t/slug/' || ft.id || '/' || p.post_number AS url,
ft.title, ft.title,
NULL AS email, NULL AS email,
NULL AS color NULL AS color,
NULL AS text_color
FROM topics AS ft FROM topics AS ft
JOIN posts AS p ON p.topic_id = ft.id AND p.post_number <> 1 JOIN posts AS p ON p.topic_id = ft.id AND p.post_number <> 1
JOIN posts_search s on s.id = p.id JOIN posts_search s on s.id = p.id
@ -74,7 +77,8 @@ module Search
'/category/' || c.slug AS url, '/category/' || c.slug AS url,
c.name AS title, c.name AS title,
NULL AS email, NULL AS email,
c.color c.color,
c.text_color
FROM categories AS c FROM categories AS c
JOIN categories_search s on s.id = c.id JOIN categories_search s on s.id = c.id
WHERE s.search_data @@ TO_TSQUERY(:locale, :query) WHERE s.search_data @@ TO_TSQUERY(:locale, :query)
@ -168,6 +172,7 @@ module Search
end end
row.delete('email') row.delete('email')
row.delete('color') unless type == 'category' row.delete('color') unless type == 'category'
row.delete('text_color') unless type == 'category'
grouped[type] ||= [] grouped[type] ||= []
grouped[type] << row grouped[type] << row

View file

@ -14,22 +14,26 @@ describe CategoriesController do
it "raises an exception when they don't have permission to create it" do it "raises an exception when they don't have permission to create it" do
Guardian.any_instance.expects(:can_create?).with(Category, nil).returns(false) Guardian.any_instance.expects(:can_create?).with(Category, nil).returns(false)
xhr :post, :create, name: 'hello', color: '#ff0' xhr :post, :create, name: 'hello', color: 'ff0', text_color: 'fff'
response.should be_forbidden response.should be_forbidden
end end
it 'raises an exception when the name is missing' do it 'raises an exception when the name is missing' do
lambda { xhr :post, :create, color: '#ff0' }.should raise_error(Discourse::InvalidParameters) lambda { xhr :post, :create, color: 'ff0', text_color: 'fff' }.should raise_error(Discourse::InvalidParameters)
end end
it 'raises an exception when the color is missing' do it 'raises an exception when the color is missing' do
lambda { xhr :post, :create, name: 'hello' }.should raise_error(Discourse::InvalidParameters) lambda { xhr :post, :create, name: 'hello', text_color: 'fff' }.should raise_error(Discourse::InvalidParameters)
end
it 'raises an exception when the text color is missing' do
lambda { xhr :post, :create, name: 'hello', color: 'ff0' }.should raise_error(Discourse::InvalidParameters)
end end
describe 'failure' do describe 'failure' do
before do before do
@category = Fabricate(:category, user: @user) @category = Fabricate(:category, user: @user)
xhr :post, :create, name: @category.name, color: '#ff0' xhr :post, :create, name: @category.name, color: 'ff0', text_color: 'fff'
end end
it { should_not respond_with(:success) } it { should_not respond_with(:success) }
@ -42,7 +46,7 @@ describe CategoriesController do
describe 'success' do describe 'success' do
before do before do
xhr :post, :create, name: 'hello', color: '#ff0' xhr :post, :create, name: 'hello', color: 'ff0', text_color: 'fff'
end end
it 'creates a category' do it 'creates a category' do
@ -97,22 +101,26 @@ describe CategoriesController do
it "raises an exception if they don't have permission to edit it" do it "raises an exception if they don't have permission to edit it" do
Guardian.any_instance.expects(:can_edit?).returns(false) Guardian.any_instance.expects(:can_edit?).returns(false)
xhr :put, :update, id: @category.slug, name: 'hello', color: '#ff0' xhr :put, :update, id: @category.slug, name: 'hello', color: 'ff0', text_color: 'fff'
response.should be_forbidden response.should be_forbidden
end end
it "requires a name" do it "requires a name" do
lambda { xhr :put, :update, id: @category.slug, color: '#fff' }.should raise_error(Discourse::InvalidParameters) lambda { xhr :put, :update, id: @category.slug, color: 'fff', text_color: '0ff' }.should raise_error(Discourse::InvalidParameters)
end end
it "requires a color" do it "requires a color" do
lambda { xhr :put, :update, id: @category.slug, name: 'asdf'}.should raise_error(Discourse::InvalidParameters) lambda { xhr :put, :update, id: @category.slug, name: 'asdf', text_color: '0ff' }.should raise_error(Discourse::InvalidParameters)
end
it "requires a text color" do
lambda { xhr :put, :update, id: @category.slug, name: 'asdf', color: 'fff' }.should raise_error(Discourse::InvalidParameters)
end end
describe 'failure' do describe 'failure' do
before do before do
@other_category = Fabricate(:category, name: 'Other', user: @user ) @other_category = Fabricate(:category, name: 'Other', user: @user )
xhr :put, :update, id: @category.id, name: @other_category.name, color: '#ff0' xhr :put, :update, id: @category.id, name: @other_category.name, color: 'ff0', text_color: 'fff'
end end
it 'returns errors on a duplicate category name' do it 'returns errors on a duplicate category name' do
@ -126,7 +134,7 @@ describe CategoriesController do
describe 'success' do describe 'success' do
before do before do
xhr :put, :update, id: @category.id, name: 'science', color: '#000' xhr :put, :update, id: @category.id, name: 'science', color: '000', text_color: '0ff'
@category.reload @category.reload
end end
@ -135,7 +143,11 @@ describe CategoriesController do
end end
it 'updates the color' do it 'updates the color' do
@category.color.should == '#000' @category.color.should == '000'
end
it 'updates the text color' do
@category.text_color.should == '0ff'
end end
end end