Commit 79e1ad7bc95de7c8b5c312b57d904181b45f7b6f
Exists in
master
and in
29 other branches
Merge branch 'master' into stoa
Conflicts: app/controllers/application_controller.rb app/controllers/my_profile/memberships_controller.rb app/controllers/public/account_controller.rb app/helpers/application_helper.rb app/helpers/layout_helper.rb app/helpers/token_helper.rb app/views/account/signup.html.erb app/views/layouts/_javascript.html.erb db/schema.rb test/functional/application_controller_test.rb
Showing
61 changed files
with
3943 additions
and
303 deletions
Show diff stats
app/controllers/admin/categories_controller.rb
@@ -45,9 +45,11 @@ class CategoriesController < AdminController | @@ -45,9 +45,11 @@ class CategoriesController < AdminController | ||
45 | if request.post? | 45 | if request.post? |
46 | @category.update_attributes!(params[:category]) | 46 | @category.update_attributes!(params[:category]) |
47 | @saved = true | 47 | @saved = true |
48 | + session[:notice] = _("Category %s saved." % @category.name) | ||
48 | redirect_to :action => 'index' | 49 | redirect_to :action => 'index' |
49 | end | 50 | end |
50 | rescue Exception => e | 51 | rescue Exception => e |
52 | + session[:notice] = _('Could not save category.') | ||
51 | render :action => 'edit' | 53 | render :action => 'edit' |
52 | end | 54 | end |
53 | end | 55 | end |
app/controllers/admin/environment_design_controller.rb
@@ -3,9 +3,7 @@ class EnvironmentDesignController < BoxOrganizerController | @@ -3,9 +3,7 @@ class EnvironmentDesignController < BoxOrganizerController | ||
3 | protect 'edit_environment_design', :environment | 3 | protect 'edit_environment_design', :environment |
4 | 4 | ||
5 | def available_blocks | 5 | def available_blocks |
6 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
7 | - # the Noosfero core soon, see ActionItem3045 | ||
8 | - @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ] | 6 | + @available_blocks ||= [ ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ] |
9 | @available_blocks += plugins.dispatch(:extra_blocks, :type => Environment) | 7 | @available_blocks += plugins.dispatch(:extra_blocks, :type => Environment) |
10 | end | 8 | end |
11 | 9 |
app/controllers/admin/features_controller.rb
@@ -51,4 +51,10 @@ class FeaturesController < AdminController | @@ -51,4 +51,10 @@ class FeaturesController < AdminController | ||
51 | redirect_to :action => 'manage_fields' | 51 | redirect_to :action => 'manage_fields' |
52 | end | 52 | end |
53 | 53 | ||
54 | + def search_members | ||
55 | + arg = params[:q].downcase | ||
56 | + result = environment.people.find(:all, :conditions => ['LOWER(name) LIKE ? OR identifier LIKE ?', "%#{arg}%", "%#{arg}%"]) | ||
57 | + render :text => prepare_to_token_input(result).to_json | ||
58 | + end | ||
59 | + | ||
54 | end | 60 | end |
app/controllers/application_controller.rb
@@ -7,6 +7,12 @@ class ApplicationController < ActionController::Base | @@ -7,6 +7,12 @@ class ApplicationController < ActionController::Base | ||
7 | before_filter :detect_stuff_by_domain | 7 | before_filter :detect_stuff_by_domain |
8 | before_filter :init_noosfero_plugins | 8 | before_filter :init_noosfero_plugins |
9 | before_filter :allow_cross_domain_access | 9 | before_filter :allow_cross_domain_access |
10 | + before_filter :login_required, :if => :private_environment? | ||
11 | + before_filter :verify_members_whitelist, :if => [:private_environment?, :user] | ||
12 | + | ||
13 | + def verify_members_whitelist | ||
14 | + render_access_denied unless user.is_admin? || environment.in_whitelist?(user) | ||
15 | + end | ||
10 | 16 | ||
11 | after_filter :set_csrf_cookie | 17 | after_filter :set_csrf_cookie |
12 | 18 | ||
@@ -185,4 +191,8 @@ class ApplicationController < ActionController::Base | @@ -185,4 +191,8 @@ class ApplicationController < ActionController::Base | ||
185 | def find_suggestions(query, context, asset, options={}) | 191 | def find_suggestions(query, context, asset, options={}) |
186 | plugins.dispatch_first(:find_suggestions, query, context, asset, options) | 192 | plugins.dispatch_first(:find_suggestions, query, context, asset, options) |
187 | end | 193 | end |
194 | + | ||
195 | + def private_environment? | ||
196 | + @environment.enabled?(:restrict_to_members) | ||
197 | + end | ||
188 | end | 198 | end |
app/controllers/my_profile/cms_controller.rb
@@ -4,6 +4,12 @@ class CmsController < MyProfileController | @@ -4,6 +4,12 @@ class CmsController < MyProfileController | ||
4 | 4 | ||
5 | include ArticleHelper | 5 | include ArticleHelper |
6 | 6 | ||
7 | + def search_tags | ||
8 | + arg = params[:term].downcase | ||
9 | + result = ActsAsTaggableOn::Tag.find(:all, :conditions => ['LOWER(name) LIKE ?', "%#{arg}%"]) | ||
10 | + render :text => prepare_to_token_input_by_label(result).to_json, :content_type => 'application/json' | ||
11 | + end | ||
12 | + | ||
7 | def self.protect_if(*args) | 13 | def self.protect_if(*args) |
8 | before_filter(*args) do |c| | 14 | before_filter(*args) do |c| |
9 | user, profile = c.send(:user), c.send(:profile) | 15 | user, profile = c.send(:user), c.send(:profile) |
app/controllers/my_profile/memberships_controller.rb
@@ -27,7 +27,7 @@ class MembershipsController < MyProfileController | @@ -27,7 +27,7 @@ class MembershipsController < MyProfileController | ||
27 | redirect_to :action => 'welcome', :community_id => @community.id, :back_to => @back_to | 27 | redirect_to :action => 'welcome', :community_id => @community.id, :back_to => @back_to |
28 | rescue ActiveRecord::RecordNotFound | 28 | rescue ActiveRecord::RecordNotFound |
29 | # Community pending approval | 29 | # Community pending approval |
30 | - session[:notice] = _('Your community creation request is waiting approval of the administrator.') | 30 | + session[:notice] = _('Your new community creation request will be evaluated by an administrator. You will be notified.') |
31 | redirect_to @back_to | 31 | redirect_to @back_to |
32 | end | 32 | end |
33 | return | 33 | return |
app/controllers/my_profile/tasks_controller.rb
@@ -4,6 +4,7 @@ class TasksController < MyProfileController | @@ -4,6 +4,7 @@ class TasksController < MyProfileController | ||
4 | 4 | ||
5 | def index | 5 | def index |
6 | @filter = params[:filter_type].blank? ? nil : params[:filter_type] | 6 | @filter = params[:filter_type].blank? ? nil : params[:filter_type] |
7 | + @task_types = Task.pending_types_for(profile) | ||
7 | @tasks = Task.to(profile).without_spam.pending.of(@filter).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page]) | 8 | @tasks = Task.to(profile).without_spam.pending.of(@filter).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page]) |
8 | @failed = params ? params[:failed] : {} | 9 | @failed = params ? params[:failed] : {} |
9 | end | 10 | end |
app/controllers/public/account_controller.rb
@@ -15,11 +15,23 @@ class AccountController < ApplicationController | @@ -15,11 +15,23 @@ class AccountController < ApplicationController | ||
15 | 15 | ||
16 | def activate | 16 | def activate |
17 | @user = User.find_by_activation_code(params[:activation_code]) if params[:activation_code] | 17 | @user = User.find_by_activation_code(params[:activation_code]) if params[:activation_code] |
18 | - if @user and @user.activate | ||
19 | - @message = _("Your account has been activated, now you can log in!") | ||
20 | - check_redirection | ||
21 | - session[:join] = params[:join] unless params[:join].blank? | ||
22 | - render :action => 'login', :userlogin => @user.login | 18 | + if @user |
19 | + unless @user.environment.enabled?('admin_must_approve_new_users') | ||
20 | + if @user.activate | ||
21 | + @message = _("Your account has been activated, now you can log in!") | ||
22 | + check_redirection | ||
23 | + session[:join] = params[:join] unless params[:join].blank? | ||
24 | + render :action => 'login', :userlogin => @user.login | ||
25 | + end | ||
26 | + else | ||
27 | + if @user.create_moderate_task | ||
28 | + session[:notice] = _('Thanks for registering. The administrators were notified.') | ||
29 | + @register_pending = true | ||
30 | + @user.activation_code = nil | ||
31 | + @user.save! | ||
32 | + redirect_to :controller => :home | ||
33 | + end | ||
34 | + end | ||
23 | else | 35 | else |
24 | session[:notice] = _("It looks like you're trying to activate an account. Perhaps have already activated this account?") | 36 | session[:notice] = _("It looks like you're trying to activate an account. Perhaps have already activated this account?") |
25 | redirect_to :controller => :home | 37 | redirect_to :controller => :home |
@@ -111,6 +123,7 @@ class AccountController < ApplicationController | @@ -111,6 +123,7 @@ class AccountController < ApplicationController | ||
111 | go_to_signup_initial_page | 123 | go_to_signup_initial_page |
112 | else | 124 | else |
113 | redirect_to :controller => :home, :action => :welcome, :template_id => @user.person.template.id | 125 | redirect_to :controller => :home, :action => :welcome, :template_id => @user.person.template.id |
126 | + session[:notice] = _('Thanks for registering!') | ||
114 | end | 127 | end |
115 | end | 128 | end |
116 | end | 129 | end |
app/helpers/application_helper.rb
@@ -1438,4 +1438,15 @@ module ApplicationHelper | @@ -1438,4 +1438,15 @@ module ApplicationHelper | ||
1438 | '' | 1438 | '' |
1439 | end | 1439 | end |
1440 | end | 1440 | end |
1441 | + | ||
1442 | + def labelled_colorpicker_field(human_name, object_name, method, options = {}) | ||
1443 | + options[:id] ||= 'text-field-' + FormsHelper.next_id_number | ||
1444 | + content_tag('label', human_name, :for => options[:id], :class => 'formlabel') + | ||
1445 | + colorpicker_field(object_name, method, options.merge(:class => 'colorpicker_field')) | ||
1446 | + end | ||
1447 | + | ||
1448 | + def colorpicker_field(object_name, method, options = {}) | ||
1449 | + text_field(object_name, method, options.merge(:class => 'colorpicker_field')) | ||
1450 | + end | ||
1451 | + | ||
1441 | end | 1452 | end |
app/helpers/article_helper.rb
@@ -83,6 +83,10 @@ module ArticleHelper | @@ -83,6 +83,10 @@ module ArticleHelper | ||
83 | array.map { |object| {:id => object.id, :name => object.name} } | 83 | array.map { |object| {:id => object.id, :name => object.name} } |
84 | end | 84 | end |
85 | 85 | ||
86 | + def prepare_to_token_input_by_label(array) | ||
87 | + array.map { |object| {:label => object.name, :value => object.name} } | ||
88 | + end | ||
89 | + | ||
86 | def cms_label_for_new_children | 90 | def cms_label_for_new_children |
87 | _('New article') | 91 | _('New article') |
88 | end | 92 | end |
app/helpers/categories_helper.rb
1 | module CategoriesHelper | 1 | module CategoriesHelper |
2 | 2 | ||
3 | - | ||
4 | - COLORS = [ | ||
5 | - [ N_('Do not display at the menu'), nil ], | ||
6 | - [ N_('Orange'), 1], | ||
7 | - [ N_('Green'), 2], | ||
8 | - [ N_('Purple'), 3], | ||
9 | - [ N_('Red'), 4], | ||
10 | - [ N_('Dark Green'), 5], | ||
11 | - [ N_('Blue Oil'), 6], | ||
12 | - [ N_('Blue'), 7], | ||
13 | - [ N_('Brown'), 8], | ||
14 | - [ N_('Light Green'), 9], | ||
15 | - [ N_('Light Blue'), 10], | ||
16 | - [ N_('Dark Blue'), 11], | ||
17 | - [ N_('Blue Pool'), 12], | ||
18 | - [ N_('Beige'), 13], | ||
19 | - [ N_('Yellow'), 14], | ||
20 | - [ N_('Light Brown'), 15] | ||
21 | - ] | ||
22 | - | ||
23 | TYPES = [ | 3 | TYPES = [ |
24 | [ _('General Category'), Category.to_s ], | 4 | [ _('General Category'), Category.to_s ], |
25 | [ _('Product Category'), ProductCategory.to_s ], | 5 | [ _('Product Category'), ProductCategory.to_s ], |
26 | [ _('Region'), Region.to_s ], | 6 | [ _('Region'), Region.to_s ], |
27 | ] | 7 | ] |
28 | 8 | ||
29 | - def select_color_for_category | ||
30 | - if @category.top_level? | ||
31 | - labelled_form_field(_('Display at the menu?'), select('category', 'display_color', CategoriesHelper::COLORS.map {|item| [gettext(item[0]), item[1]] })) | ||
32 | - else | ||
33 | - "" | ||
34 | - end | ||
35 | - end | ||
36 | - | ||
37 | - def display_color_for_category(category) | ||
38 | - color = category.display_color | ||
39 | - if color.nil? | ||
40 | - "" | ||
41 | - else | ||
42 | - "[" + gettext(CategoriesHelper::COLORS.find {|item| item[1] == color}.first) + "]" | ||
43 | - end | ||
44 | - end | ||
45 | - | ||
46 | def select_category_type(field) | 9 | def select_category_type(field) |
47 | value = params[field] | 10 | value = params[field] |
48 | labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value))) | 11 | labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value))) |
49 | end | 12 | end |
50 | 13 | ||
14 | + def category_color_style(category) | ||
15 | + return '' if category.nil? or category.display_color.blank? | ||
16 | + 'background-color: #'+category.display_color+';' | ||
17 | + end | ||
18 | + | ||
51 | #FIXME make this test | 19 | #FIXME make this test |
52 | def selected_category_link(cat) | 20 | def selected_category_link(cat) |
53 | js_remove = "jQuery('#selected-category-#{cat.id}').remove();" | 21 | js_remove = "jQuery('#selected-category-#{cat.id}').remove();" |
app/helpers/layout_helper.rb
@@ -28,6 +28,7 @@ module LayoutHelper | @@ -28,6 +28,7 @@ module LayoutHelper | ||
28 | 'lightbox', | 28 | 'lightbox', |
29 | 'colorbox', | 29 | 'colorbox', |
30 | 'selectordie', | 30 | 'selectordie', |
31 | + 'inputosaurus', | ||
31 | pngfix_stylesheet_path, | 32 | pngfix_stylesheet_path, |
32 | ] + tokeninput_stylesheets | 33 | ] + tokeninput_stylesheets |
33 | plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') } | 34 | plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') } |
app/helpers/token_helper.rb
@@ -19,6 +19,7 @@ module TokenHelper | @@ -19,6 +19,7 @@ module TokenHelper | ||
19 | options[:on_add] ||= 'null' | 19 | options[:on_add] ||= 'null' |
20 | options[:on_delete] ||= 'null' | 20 | options[:on_delete] ||= 'null' |
21 | options[:on_ready] ||= 'null' | 21 | options[:on_ready] ||= 'null' |
22 | + options[:query_param] ||= 'q' | ||
22 | 23 | ||
23 | result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id}))) | 24 | result = text_field_tag(name, nil, text_field_options.merge(html_options.merge({:id => element_id}))) |
24 | result += javascript_tag("jQuery('##{element_id}') | 25 | result += javascript_tag("jQuery('##{element_id}') |
@@ -32,7 +33,7 @@ module TokenHelper | @@ -32,7 +33,7 @@ module TokenHelper | ||
32 | preventDuplicates: #{options[:prevent_duplicates].to_json}, | 33 | preventDuplicates: #{options[:prevent_duplicates].to_json}, |
33 | backspaceDeleteItem: #{options[:backspace_delete_item].to_json}, | 34 | backspaceDeleteItem: #{options[:backspace_delete_item].to_json}, |
34 | zindex: #{options[:zindex].to_json}, | 35 | zindex: #{options[:zindex].to_json}, |
35 | - queryParam: #{name.to_json}, | 36 | + queryParam: #{options[:query_param].to_json}, |
36 | tokenLimit: #{options[:token_limit].to_json}, | 37 | tokenLimit: #{options[:token_limit].to_json}, |
37 | onResult: #{options[:on_result]}, | 38 | onResult: #{options[:on_result]}, |
38 | onAdd: #{options[:on_add]}, | 39 | onAdd: #{options[:on_add]}, |
app/models/box.rb
@@ -28,9 +28,6 @@ class Box < ActiveRecord::Base | @@ -28,9 +28,6 @@ class Box < ActiveRecord::Base | ||
28 | CategoriesBlock, | 28 | CategoriesBlock, |
29 | CommunitiesBlock, | 29 | CommunitiesBlock, |
30 | EnterprisesBlock, | 30 | EnterprisesBlock, |
31 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
32 | - # the Noosfero core soon, see ActionItem3045 | ||
33 | - EnvironmentStatisticsBlock, | ||
34 | FansBlock, | 31 | FansBlock, |
35 | FavoriteEnterprisesBlock, | 32 | FavoriteEnterprisesBlock, |
36 | FeedReaderBlock, | 33 | FeedReaderBlock, |
@@ -53,9 +50,6 @@ class Box < ActiveRecord::Base | @@ -53,9 +50,6 @@ class Box < ActiveRecord::Base | ||
53 | CommunitiesBlock, | 50 | CommunitiesBlock, |
54 | DisabledEnterpriseMessageBlock, | 51 | DisabledEnterpriseMessageBlock, |
55 | EnterprisesBlock, | 52 | EnterprisesBlock, |
56 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
57 | - # the Noosfero core soon, see ActionItem3045 | ||
58 | - EnvironmentStatisticsBlock, | ||
59 | FansBlock, | 53 | FansBlock, |
60 | FavoriteEnterprisesBlock, | 54 | FavoriteEnterprisesBlock, |
61 | FeaturedProductsBlock, | 55 | FeaturedProductsBlock, |
app/models/category.rb
@@ -14,9 +14,6 @@ class Category < ActiveRecord::Base | @@ -14,9 +14,6 @@ class Category < ActiveRecord::Base | ||
14 | validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n | 14 | validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n |
15 | belongs_to :environment | 15 | belongs_to :environment |
16 | 16 | ||
17 | - validates_inclusion_of :display_color, :in => 1..15, :allow_nil => true | ||
18 | - validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('{fn} was already assigned to another category.').fix_i18n | ||
19 | - | ||
20 | # Finds all top level categories for a given environment. | 17 | # Finds all top level categories for a given environment. |
21 | scope :top_level_for, lambda { |environment| | 18 | scope :top_level_for, lambda { |environment| |
22 | {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} | 19 | {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} |
@@ -42,6 +39,13 @@ class Category < ActiveRecord::Base | @@ -42,6 +39,13 @@ class Category < ActiveRecord::Base | ||
42 | 39 | ||
43 | acts_as_having_image | 40 | acts_as_having_image |
44 | 41 | ||
42 | + before_save :normalize_display_color | ||
43 | + | ||
44 | + def normalize_display_color | ||
45 | + display_color.gsub!('#', '') if display_color | ||
46 | + display_color = nil if display_color.blank? | ||
47 | + end | ||
48 | + | ||
45 | scope :from_types, lambda { |types| | 49 | scope :from_types, lambda { |types| |
46 | types.select{ |t| t.blank? }.empty? ? | 50 | types.select{ |t| t.blank? }.empty? ? |
47 | { :conditions => { :type => types } } : | 51 | { :conditions => { :type => types } } : |
@@ -101,4 +105,12 @@ class Category < ActiveRecord::Base | @@ -101,4 +105,12 @@ class Category < ActiveRecord::Base | ||
101 | self.children.find(:all, :conditions => {:display_in_menu => true}).empty? | 105 | self.children.find(:all, :conditions => {:display_in_menu => true}).empty? |
102 | end | 106 | end |
103 | 107 | ||
108 | + def with_color | ||
109 | + if display_color.blank? | ||
110 | + parent.nil? ? nil : parent.with_color | ||
111 | + else | ||
112 | + self | ||
113 | + end | ||
114 | + end | ||
115 | + | ||
104 | end | 116 | end |
app/models/environment.rb
@@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
3 | # domains. | 3 | # domains. |
4 | class Environment < ActiveRecord::Base | 4 | class Environment < ActiveRecord::Base |
5 | 5 | ||
6 | - attr_accessible :name, :is_default, :signup_welcome_text_subject, :signup_welcome_text_body, :terms_of_use, :message_for_disabled_enterprise, :news_amount_by_folder, :default_language, :languages, :description, :organization_approval_method, :enabled_plugins, :enabled_features, :redirection_after_login, :redirection_after_signup, :contact_email, :theme, :reports_lower_bound, :noreply_email, :signup_welcome_screen_body | 6 | + attr_accessible :name, :is_default, :signup_welcome_text_subject, :signup_welcome_text_body, :terms_of_use, :message_for_disabled_enterprise, :news_amount_by_folder, :default_language, :languages, :description, :organization_approval_method, :enabled_plugins, :enabled_features, :redirection_after_login, :redirection_after_signup, :contact_email, :theme, :reports_lower_bound, :noreply_email, :signup_welcome_screen_body, :members_whitelist_enabled, :members_whitelist |
7 | 7 | ||
8 | has_many :users | 8 | has_many :users |
9 | 9 | ||
@@ -125,6 +125,7 @@ class Environment < ActiveRecord::Base | @@ -125,6 +125,7 @@ class Environment < ActiveRecord::Base | ||
125 | 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"), | 125 | 'organizations_are_moderated_by_default' => _("Organizations have moderated publication by default"), |
126 | 'enable_organization_url_change' => _("Allow organizations to change their URL"), | 126 | 'enable_organization_url_change' => _("Allow organizations to change their URL"), |
127 | 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"), | 127 | 'admin_must_approve_new_communities' => _("Admin must approve creation of communities"), |
128 | + 'admin_must_approve_new_users' => _("Admin must approve registration of new users"), | ||
128 | 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'), | 129 | 'show_balloon_with_profile_links_when_clicked' => _('Show a balloon with profile links when a profile image is clicked'), |
129 | 'xmpp_chat' => _('XMPP/Jabber based chat'), | 130 | 'xmpp_chat' => _('XMPP/Jabber based chat'), |
130 | 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images'), | 131 | 'show_zoom_button_on_article_images' => _('Show a zoom link on all article images'), |
@@ -133,7 +134,8 @@ class Environment < ActiveRecord::Base | @@ -133,7 +134,8 @@ class Environment < ActiveRecord::Base | ||
133 | 'send_welcome_email_to_new_users' => _('Send welcome e-mail to new users'), | 134 | 'send_welcome_email_to_new_users' => _('Send welcome e-mail to new users'), |
134 | 'allow_change_of_redirection_after_login' => _('Allow users to set the page to redirect after login'), | 135 | 'allow_change_of_redirection_after_login' => _('Allow users to set the page to redirect after login'), |
135 | 'display_my_communities_on_user_menu' => _('Display on menu the list of communities the user can manage'), | 136 | 'display_my_communities_on_user_menu' => _('Display on menu the list of communities the user can manage'), |
136 | - 'display_my_enterprises_on_user_menu' => _('Display on menu the list of enterprises the user can manage') | 137 | + 'display_my_enterprises_on_user_menu' => _('Display on menu the list of enterprises the user can manage'), |
138 | + 'restrict_to_members' => _('Show content only to members') | ||
137 | } | 139 | } |
138 | end | 140 | end |
139 | 141 | ||
@@ -177,9 +179,6 @@ class Environment < ActiveRecord::Base | @@ -177,9 +179,6 @@ class Environment < ActiveRecord::Base | ||
177 | 179 | ||
178 | # "left" area | 180 | # "left" area |
179 | env.boxes[1].blocks << LoginBlock.new | 181 | env.boxes[1].blocks << LoginBlock.new |
180 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
181 | - # the Noosfero core soon, see ActionItem3045 | ||
182 | - env.boxes[1].blocks << EnvironmentStatisticsBlock.new | ||
183 | env.boxes[1].blocks << RecentDocumentsBlock.new | 182 | env.boxes[1].blocks << RecentDocumentsBlock.new |
184 | 183 | ||
185 | # "right" area | 184 | # "right" area |
@@ -305,6 +304,17 @@ class Environment < ActiveRecord::Base | @@ -305,6 +304,17 @@ class Environment < ActiveRecord::Base | ||
305 | settings[:signup_welcome_screen_body].present? | 304 | settings[:signup_welcome_screen_body].present? |
306 | end | 305 | end |
307 | 306 | ||
307 | + settings_items :members_whitelist_enabled, :type => :boolean, :default => false | ||
308 | + settings_items :members_whitelist, :type => Array, :default => [] | ||
309 | + | ||
310 | + def in_whitelist?(person) | ||
311 | + !members_whitelist_enabled || members_whitelist.include?(person.id) | ||
312 | + end | ||
313 | + | ||
314 | + def members_whitelist=(members) | ||
315 | + settings[:members_whitelist] = members.split(',').map(&:to_i) | ||
316 | + end | ||
317 | + | ||
308 | def news_amount_by_folder=(amount) | 318 | def news_amount_by_folder=(amount) |
309 | settings[:news_amount_by_folder] = amount.to_i | 319 | settings[:news_amount_by_folder] = amount.to_i |
310 | end | 320 | end |
app/models/environment_statistics_block.rb
@@ -1,33 +0,0 @@ | @@ -1,33 +0,0 @@ | ||
1 | -# TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
2 | -# the Noosfero core soon, see ActionItem3045 | ||
3 | - | ||
4 | -class EnvironmentStatisticsBlock < Block | ||
5 | - | ||
6 | - def self.description | ||
7 | - _('Environment stastistics (DEPRECATED)') | ||
8 | - end | ||
9 | - | ||
10 | - def default_title | ||
11 | - _('Statistics for %s') % owner.name | ||
12 | - end | ||
13 | - | ||
14 | - def help | ||
15 | - _('This block presents some statistics about your environment.') | ||
16 | - end | ||
17 | - | ||
18 | - def content(args={}) | ||
19 | - users = owner.people.visible.count | ||
20 | - enterprises = owner.enterprises.visible.count | ||
21 | - communities = owner.communities.visible.count | ||
22 | - | ||
23 | - info = [] | ||
24 | - info << (n_('One user', '%{num} users', users) % { :num => users }) | ||
25 | - unless owner.enabled?('disable_asset_enterprises') | ||
26 | - info << (n_('One enterprise', '%{num} enterprises', enterprises) % { :num => enterprises }) | ||
27 | - end | ||
28 | - info << (n_('One community', '%{num} communities', communities) % { :num => communities }) | ||
29 | - | ||
30 | - block_title(title) + content_tag('ul', info.map {|item| content_tag('li', item) }.join("\n")) | ||
31 | - end | ||
32 | - | ||
33 | -end |
@@ -0,0 +1,59 @@ | @@ -0,0 +1,59 @@ | ||
1 | +class ModerateUserRegistration < Task | ||
2 | + | ||
3 | + settings_items :user_id, :type => String | ||
4 | + settings_items :name, :type => String | ||
5 | + settings_items :author_name, :type => String | ||
6 | + settings_items :email, :type => String | ||
7 | + | ||
8 | + after_create :schedule_spam_checking | ||
9 | + | ||
10 | + alias :environment :target | ||
11 | + alias :environment= :target= | ||
12 | + | ||
13 | + def schedule_spam_checking | ||
14 | + self.delay.check_for_spam | ||
15 | + end | ||
16 | + | ||
17 | + include Noosfero::Plugin::HotSpot | ||
18 | + | ||
19 | + def sender | ||
20 | + "#{name} (#{email})" | ||
21 | + end | ||
22 | + | ||
23 | + def perform | ||
24 | + user=environment.users.find_by_id(user_id) | ||
25 | + user.activate | ||
26 | + end | ||
27 | + | ||
28 | + def title | ||
29 | + _("New user") | ||
30 | + end | ||
31 | + | ||
32 | + def subject | ||
33 | + name | ||
34 | + end | ||
35 | + | ||
36 | + def information | ||
37 | + { :message => _('%{sender} wants to register.'), | ||
38 | + :variables => {:sender => sender} } | ||
39 | + end | ||
40 | + | ||
41 | + def icon | ||
42 | + result = {:type => :defined_image, :src => '/images/icons-app/person-minor.png', :name => name} | ||
43 | + end | ||
44 | + | ||
45 | + def target_notification_description | ||
46 | + _('%{sender} tried to register.') % | ||
47 | + {:sender => sender} | ||
48 | + end | ||
49 | + | ||
50 | + def target_notification_message | ||
51 | + target_notification_description + "\n\n" + | ||
52 | + _('You need to login on %{system} in order to approve or reject this user.') % { :environment => self.environment } | ||
53 | + end | ||
54 | + | ||
55 | + def target_notification_message | ||
56 | + _("User \"%{user}\" just requested to register. You have to approve or reject it through the \"Pending Validations\" section in your control panel.\n") % { :user => self.name } | ||
57 | + end | ||
58 | + | ||
59 | +end | ||
0 | \ No newline at end of file | 60 | \ No newline at end of file |
app/models/task.rb
@@ -73,10 +73,6 @@ class Task < ActiveRecord::Base | @@ -73,10 +73,6 @@ class Task < ActiveRecord::Base | ||
73 | end | 73 | end |
74 | end | 74 | end |
75 | 75 | ||
76 | - def self.all_types | ||
77 | - %w[Invitation EnterpriseActivation AddMember Ticket SuggestArticle AddFriend CreateCommunity AbuseComplaint ApproveComment ApproveArticle CreateEnterprise ChangePassword EmailActivation InviteFriend InviteMember] | ||
78 | - end | ||
79 | - | ||
80 | # this method finished the task. It calls #perform, which must be overriden | 76 | # this method finished the task. It calls #perform, which must be overriden |
81 | # by subclasses. At the end a message (as returned by #finish_message) is | 77 | # by subclasses. At the end a message (as returned by #finish_message) is |
82 | # sent to the requestor with #notify_requestor. | 78 | # sent to the requestor with #notify_requestor. |
@@ -254,6 +250,10 @@ class Task < ActiveRecord::Base | @@ -254,6 +250,10 @@ class Task < ActiveRecord::Base | ||
254 | { :conditions => [environment_condition, profile_condition].compact.join(' OR ') } | 250 | { :conditions => [environment_condition, profile_condition].compact.join(' OR ') } |
255 | } | 251 | } |
256 | 252 | ||
253 | + def self.pending_types_for(profile) | ||
254 | + Task.to(profile).pending.select('distinct type').map { |t| [t.class.name, t.title] } | ||
255 | + end | ||
256 | + | ||
257 | def opened? | 257 | def opened? |
258 | status == Task::Status::ACTIVE || status == Task::Status::HIDDEN | 258 | status == Task::Status::ACTIVE || status == Task::Status::HIDDEN |
259 | end | 259 | end |
app/models/user.rb
@@ -51,8 +51,12 @@ class User < ActiveRecord::Base | @@ -51,8 +51,12 @@ class User < ActiveRecord::Base | ||
51 | 51 | ||
52 | user.person = p | 52 | user.person = p |
53 | end | 53 | end |
54 | - if user.environment.enabled?('skip_new_user_email_confirmation') | ||
55 | - user.activate | 54 | + if user.environment.enabled?('skip_new_user_email_confirmation') |
55 | + if user.environment.enabled?('admin_must_approve_new_users') | ||
56 | + create_moderate_task | ||
57 | + else | ||
58 | + user.activate | ||
59 | + end | ||
56 | end | 60 | end |
57 | end | 61 | end |
58 | after_create :deliver_activation_code | 62 | after_create :deliver_activation_code |
@@ -141,6 +145,15 @@ class User < ActiveRecord::Base | @@ -141,6 +145,15 @@ class User < ActiveRecord::Base | ||
141 | end | 145 | end |
142 | end | 146 | end |
143 | 147 | ||
148 | + def create_moderate_task | ||
149 | + @task = ModerateUserRegistration.new | ||
150 | + @task.user_id = self.id | ||
151 | + @task.name = self.name | ||
152 | + @task.email = self.email | ||
153 | + @task.target = self.environment | ||
154 | + @task.save | ||
155 | + end | ||
156 | + | ||
144 | def activated? | 157 | def activated? |
145 | self.activation_code.nil? && !self.activated_at.nil? | 158 | self.activation_code.nil? && !self.activated_at.nil? |
146 | end | 159 | end |
app/sweepers/profile_sweeper.rb
@@ -8,9 +8,6 @@ class ProfileSweeper # < ActiveRecord::Observer | @@ -8,9 +8,6 @@ class ProfileSweeper # < ActiveRecord::Observer | ||
8 | end | 8 | end |
9 | 9 | ||
10 | def after_create(profile) | 10 | def after_create(profile) |
11 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
12 | - # the Noosfero core soon, see ActionItem3045 | ||
13 | - expire_statistics_block_cache(profile) | ||
14 | end | 11 | end |
15 | 12 | ||
16 | protected | 13 | protected |
@@ -31,13 +28,6 @@ protected | @@ -31,13 +28,6 @@ protected | ||
31 | expire_blogs(profile) if profile.organization? | 28 | expire_blogs(profile) if profile.organization? |
32 | end | 29 | end |
33 | 30 | ||
34 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
35 | - # the Noosfero core soon, see ActionItem3045 | ||
36 | - def expire_statistics_block_cache(profile) | ||
37 | - blocks = profile.environment.blocks.select { |b| b.kind_of?(EnvironmentStatisticsBlock) } | ||
38 | - BlockSweeper.expire_blocks(blocks) | ||
39 | - end | ||
40 | - | ||
41 | def expire_blogs(profile) | 31 | def expire_blogs(profile) |
42 | profile.blogs.select{|b| !b.empty?}.each do |blog| | 32 | profile.blogs.select{|b| !b.empty?}.each do |blog| |
43 | pages = blog.posts.count / blog.posts_per_page + 1 | 33 | pages = blog.posts.count / blog.posts_per_page + 1 |
app/views/categories/_category.html.erb
1 | <li> | 1 | <li> |
2 | <div class='treeitem'> | 2 | <div class='treeitem'> |
3 | - <%= display_color_for_category(category) %> | ||
4 | - <%= category.name %> | 3 | + <% unless category_color_style(category).empty? %> |
4 | + <span class="color_marker" style="<%= category_color_style(category) %>" ></span> | ||
5 | + <% end %> | ||
6 | + <span><%= category.name %></span> | ||
7 | + | ||
5 | <% if category.children.count > 0 %> | 8 | <% if category.children.count > 0 %> |
6 | <div class='button' id="category-loading-<%= category.id %>" style="position: relative;"> | 9 | <div class='button' id="category-loading-<%= category.id %>" style="position: relative;"> |
7 | <a href="#" id="show-button-<%= category.id %>" class="show-button" onclick="return false;" data-category="<%= category.id %>"><%= _('Show') %></a> | 10 | <a href="#" id="show-button-<%= category.id %>" class="show-button" onclick="return false;" data-category="<%= category.id %>"><%= _('Show') %></a> |
app/views/categories/_form.html.erb
1 | +<%= stylesheet_link_tag 'spectrum.css' %> | ||
2 | +<%= javascript_include_tag "spectrum.js" %> | ||
3 | +<%= javascript_include_tag "colorpicker-noosfero.js" %> | ||
4 | + | ||
1 | <%= error_messages_for 'category' %> | 5 | <%= error_messages_for 'category' %> |
2 | 6 | ||
3 | <%= labelled_form_for 'category', :html => { :multipart => true} do |f| %> | 7 | <%= labelled_form_for 'category', :html => { :multipart => true} do |f| %> |
@@ -13,12 +17,13 @@ | @@ -13,12 +17,13 @@ | ||
13 | <% end %> | 17 | <% end %> |
14 | <% end %> | 18 | <% end %> |
15 | 19 | ||
16 | - <%= select_color_for_category if !environment.enabled?('disable_categories_menu') %> | ||
17 | - | ||
18 | <%= required f.text_field('name') %> | 20 | <%= required f.text_field('name') %> |
19 | 21 | ||
20 | <%= labelled_check_box(_('Display in the menu'), 'category[display_in_menu]', '1', @category.display_in_menu) %> | 22 | <%= labelled_check_box(_('Display in the menu'), 'category[display_in_menu]', '1', @category.display_in_menu) %> |
21 | 23 | ||
24 | + <%= labelled_colorpicker_field(_('Pick a color'), :category, 'display_color' ) unless environment.enabled?('disable_categories_menu')%> | ||
25 | + <span id="color_preview" class = "color_marker" style="<%= category_color_style(@category) %>" ></span> | ||
26 | + | ||
22 | <%= f.fields_for :image_builder, @category.image do |i| %> | 27 | <%= f.fields_for :image_builder, @category.image do |i| %> |
23 | <%= file_field_or_thumbnail(_('Image:'), @category.image, i) %> | 28 | <%= file_field_or_thumbnail(_('Image:'), @category.image, i) %> |
24 | <% end %> | 29 | <% end %> |
app/views/cms/edit.html.erb
@@ -31,9 +31,18 @@ | @@ -31,9 +31,18 @@ | ||
31 | 31 | ||
32 | <%= select_categories(:article, _('Categorize your article')) %> | 32 | <%= select_categories(:article, _('Categorize your article')) %> |
33 | 33 | ||
34 | + <br /> | ||
35 | + | ||
34 | <%= f.text_field('tag_list', :size => 64) %> | 36 | <%= f.text_field('tag_list', :size => 64) %> |
35 | <%= content_tag( 'small', _('Separate tags with commas') ) %> | 37 | <%= content_tag( 'small', _('Separate tags with commas') ) %> |
36 | 38 | ||
39 | + <script> | ||
40 | + jQuery('#article_tag_list').inputosaurus({ | ||
41 | + autoCompleteSource: <%= "'/myprofile/#{profile.identifier}/cms/search_tags'," %> | ||
42 | + activateFinalResult : true | ||
43 | + }) | ||
44 | + </script> | ||
45 | + | ||
37 | <div id='edit-article-options'> | 46 | <div id='edit-article-options'> |
38 | <%= options_for_article(@article, @tokenized_children) %> | 47 | <%= options_for_article(@article, @tokenized_children) %> |
39 | </div> | 48 | </div> |
app/views/features/index.html.erb
@@ -37,6 +37,18 @@ Check all the features you want to enable for your environment, uncheck all the | @@ -37,6 +37,18 @@ Check all the features you want to enable for your environment, uncheck all the | ||
37 | <%= select_organization_approval_method('environment', 'organization_approval_method') %> | 37 | <%= select_organization_approval_method('environment', 'organization_approval_method') %> |
38 | <hr/> | 38 | <hr/> |
39 | 39 | ||
40 | +<h3><%= _('Members Whitelist') %></h3> | ||
41 | + <div class="option"> | ||
42 | + <%= check_box :environment, :members_whitelist_enabled %> | ||
43 | + <label><%= _('Enable whitelist') %></label> | ||
44 | + </div> | ||
45 | + <div class="input"> | ||
46 | + <div class="info"><%= _('Allow these people to access this environment:') %></div> | ||
47 | + <% tokenized_members = prepare_to_token_input(environment.people.find(:all, :conditions => {:id => environment.members_whitelist})) %> | ||
48 | + <%= token_input_field_tag('environment[members_whitelist]', 'search-members', {:action => 'search_members'}, {:focus => false, :hint_text => _('Type in a search term for a user'), :pre_populate => tokenized_members}) %> | ||
49 | + </div> | ||
50 | +<hr/> | ||
51 | + | ||
40 | <div> | 52 | <div> |
41 | <% button_bar do %> | 53 | <% button_bar do %> |
42 | <%= submit_button('save', _('Save changes')) %> | 54 | <%= submit_button('save', _('Save changes')) %> |
app/views/home/welcome.html.erb
@@ -6,11 +6,15 @@ | @@ -6,11 +6,15 @@ | ||
6 | <%= environment.settings[:signup_welcome_screen_body].html_safe %> | 6 | <%= environment.settings[:signup_welcome_screen_body].html_safe %> |
7 | <% else %> | 7 | <% else %> |
8 | <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3> | 8 | <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3> |
9 | - <p><%= _("Firstly, some tips for getting started:") %></p> | ||
10 | <% if @display_confirmation_tips %> | 9 | <% if @display_confirmation_tips %> |
10 | + <p><%= _("Firstly, some tips for getting started:") %></p> | ||
11 | <h4><%= _("Confirm your account!") %></h4> | 11 | <h4><%= _("Confirm your account!") %></h4> |
12 | <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p> | 12 | <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p> |
13 | <p><%= _("You won't appear as %s until your account is confirmed.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p> | 13 | <p><%= _("You won't appear as %s until your account is confirmed.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p> |
14 | + <% else %> | ||
15 | + <h4><%= _("Wait for admin approvement!") %></h4> | ||
16 | + <p><%= _("The administrators will evaluate your signup request for approvement.") %></p> | ||
17 | + <p><%= _("You won't appear as %s until your account is approved.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p> | ||
14 | <% end %> | 18 | <% end %> |
15 | <h4><%= _("What to do next?") %></h4> | 19 | <h4><%= _("What to do next?") %></h4> |
16 | <p><%= _("Access your %s and see your face on the network!") % link_to(_('Profile'), {:controller => 'profile', :profile => user.identifier}, :target => '_blank') %></p> | 20 | <p><%= _("Access your %s and see your face on the network!") % link_to(_('Profile'), {:controller => 'profile', :profile => user.identifier}, :target => '_blank') %></p> |
app/views/layouts/_javascript.html.erb
@@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
4 | 'jquery-ui-1.10.4/js/jquery-ui-1.10.4.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', | 4 | 'jquery-ui-1.10.4/js/jquery-ui-1.10.4.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', |
5 | 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', 'jquery.typewatch', 'jquery.textchange', | 5 | 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', 'jquery.typewatch', 'jquery.textchange', |
6 | 'add-and-join', 'report-abuse', 'catalog', 'manage-products', 'autogrow', 'select-or-die/_src/selectordie', | 6 | 'add-and-join', 'report-abuse', 'catalog', 'manage-products', 'autogrow', 'select-or-die/_src/selectordie', |
7 | -'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', :cache => 'cache/application' %> | 7 | +'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', 'inputosaurus.js', :cache => 'cache/application' %> |
8 | 8 | ||
9 | <% language = FastGettext.locale %> | 9 | <% language = FastGettext.locale %> |
10 | <% %w{messages methods}.each do |type| %> | 10 | <% %w{messages methods}.each do |type| %> |
app/views/tasks/index.html.erb
@@ -3,10 +3,9 @@ | @@ -3,10 +3,9 @@ | ||
3 | <h1><%= _("%s's pending tasks") % profile.name %></h1> | 3 | <h1><%= _("%s's pending tasks") % profile.name %></h1> |
4 | <p> | 4 | <p> |
5 | 5 | ||
6 | -<% type_collection = [[nil, _('All')]] %> | ||
7 | -<% type_collection += Task.all_types.sort_by {|klass| klass.constantize.new.title}.map{|s| [s, s.constantize.new.title] } %> | ||
8 | - | ||
9 | - | 6 | +<% |
7 | + type_collection = [[nil, _('All')]] + @task_types | ||
8 | +%> | ||
10 | 9 | ||
11 | <% if !@failed.blank? %> | 10 | <% if !@failed.blank? %> |
12 | <div id="errorExplanation"> | 11 | <div id="errorExplanation"> |
@@ -39,7 +38,7 @@ | @@ -39,7 +38,7 @@ | ||
39 | 38 | ||
40 | <ul class='task-list'> | 39 | <ul class='task-list'> |
41 | <p> | 40 | <p> |
42 | - <%= labelled_select(_('Filter')+': ', :filter_type, :first, :last, @filter, type_collection, :onchange => 'document.location.href = "?filter_type="+this.value')%> | 41 | + <%= labelled_select(_('Filter')+': ', :filter_type, :first, :last, @filter, type_collection, :onchange => "document.location.href = '?filter_type='+this.value") %> |
43 | </p> | 42 | </p> |
44 | <p> | 43 | <p> |
45 | <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "up-set-all-tasks-to") %> | 44 | <%= labelled_select(_("Set all to: "), 'set-decisions', 'first', 'last', nil, [['',""],['accept',_("Accept")],['reject',_("Reject")],['skip',_("Skip")]], :id => "up-set-all-tasks-to") %> |
@@ -0,0 +1,9 @@ | @@ -0,0 +1,9 @@ | ||
1 | +if defined? PhusionPassenger | ||
2 | + | ||
3 | + # from http://russbrooks.com/2010/10/20/rails-cache-memcache-on-passenger-with-smart-spawning | ||
4 | + PhusionPassenger.on_event :starting_worker_process do |forked| | ||
5 | + if forked | ||
6 | + Rails.cache.instance_variable_get(:@data).reset if Rails.cache.class == ActiveSupport::Cache::MemCacheStore | ||
7 | + end | ||
8 | + end | ||
9 | +end |
db/migrate/20140724134601_fix_yaml_encoding.rb
@@ -6,6 +6,7 @@ class FixYamlEncoding < ActiveRecord::Migration | @@ -6,6 +6,7 @@ class FixYamlEncoding < ActiveRecord::Migration | ||
6 | fix_encoding(Profile, 'data') | 6 | fix_encoding(Profile, 'data') |
7 | fix_encoding(ActionTracker::Record, 'params') | 7 | fix_encoding(ActionTracker::Record, 'params') |
8 | fix_encoding(Article, 'setting') | 8 | fix_encoding(Article, 'setting') |
9 | + fix_encoding(Task, 'data') | ||
9 | end | 10 | end |
10 | 11 | ||
11 | def self.down | 12 | def self.down |
db/migrate/20140807134625_change_category_display_color_to_string.rb
0 → 100644
@@ -0,0 +1,36 @@ | @@ -0,0 +1,36 @@ | ||
1 | +class ChangeCategoryDisplayColorToString < ActiveRecord::Migration | ||
2 | + | ||
3 | + COLORS = ['ffa500', '00FF00', 'a020f0', 'ff0000', '006400', '191970', '0000ff', 'a52a2a', '32cd32', 'add8e6', '483d8b', 'b8e9ee', 'f5f5dc', 'ffff00', 'f4a460'] | ||
4 | + | ||
5 | + def self.up | ||
6 | + change_table :categories do |t| | ||
7 | + t.string :display_color_tmp, :limit => 6 | ||
8 | + end | ||
9 | + | ||
10 | + COLORS.each_with_index do |color, i| | ||
11 | + Category.update_all({:display_color_tmp => color}, {:display_color => i+1}) | ||
12 | + end | ||
13 | + | ||
14 | + change_table :categories do |t| | ||
15 | + t.remove :display_color | ||
16 | + t.rename :display_color_tmp, :display_color | ||
17 | + end | ||
18 | + end | ||
19 | + | ||
20 | + def self.down | ||
21 | + puts "WARNING: only old defined colors will be reverted" | ||
22 | + | ||
23 | + change_table :categories do |t| | ||
24 | + t.integer :display_color_tmp | ||
25 | + end | ||
26 | + | ||
27 | + COLORS.each_with_index do |color, i| | ||
28 | + Category.update_all({:display_color_tmp => i+1}, {:display_color => color}) | ||
29 | + end | ||
30 | + | ||
31 | + change_table :categories do |t| | ||
32 | + t.remove :display_color | ||
33 | + t.rename :display_color_tmp, :display_color | ||
34 | + end | ||
35 | + end | ||
36 | +end |
db/migrate/20140827191326_remove_environment_statistics_block.rb
0 → 100644
@@ -0,0 +1,9 @@ | @@ -0,0 +1,9 @@ | ||
1 | +class RemoveEnvironmentStatisticsBlock < ActiveRecord::Migration | ||
2 | + def self.up | ||
3 | + update("UPDATE blocks SET type = 'StatisticsBlock' WHERE type = 'EnvironmentStatisticsBlock'") | ||
4 | + end | ||
5 | + | ||
6 | + def self.down | ||
7 | + say("Nothing to undo (cannot recover the data)") | ||
8 | + end | ||
9 | +end |
db/schema.rb
@@ -11,7 +11,7 @@ | @@ -11,7 +11,7 @@ | ||
11 | # | 11 | # |
12 | # It's strongly recommended to check this file into your version control system. | 12 | # It's strongly recommended to check this file into your version control system. |
13 | 13 | ||
14 | -ActiveRecord::Schema.define(:version => 20140811141211) do | 14 | +ActiveRecord::Schema.define(:version => 20140827191326) do |
15 | 15 | ||
16 | create_table "abuse_reports", :force => true do |t| | 16 | create_table "abuse_reports", :force => true do |t| |
17 | t.integer "reporter_id" | 17 | t.integer "reporter_id" |
@@ -194,19 +194,19 @@ ActiveRecord::Schema.define(:version => 20140811141211) do | @@ -194,19 +194,19 @@ ActiveRecord::Schema.define(:version => 20140811141211) do | ||
194 | create_table "categories", :force => true do |t| | 194 | create_table "categories", :force => true do |t| |
195 | t.string "name" | 195 | t.string "name" |
196 | t.string "slug" | 196 | t.string "slug" |
197 | - t.text "path", :default => "" | ||
198 | - t.integer "display_color" | 197 | + t.text "path", :default => "" |
199 | t.integer "environment_id" | 198 | t.integer "environment_id" |
200 | t.integer "parent_id" | 199 | t.integer "parent_id" |
201 | t.string "type" | 200 | t.string "type" |
202 | t.float "lat" | 201 | t.float "lat" |
203 | t.float "lng" | 202 | t.float "lng" |
204 | - t.boolean "display_in_menu", :default => false | ||
205 | - t.integer "children_count", :default => 0 | ||
206 | - t.boolean "accept_products", :default => true | 203 | + t.boolean "display_in_menu", :default => false |
204 | + t.integer "children_count", :default => 0 | ||
205 | + t.boolean "accept_products", :default => true | ||
207 | t.integer "image_id" | 206 | t.integer "image_id" |
208 | t.string "acronym" | 207 | t.string "acronym" |
209 | t.string "abbreviation" | 208 | t.string "abbreviation" |
209 | + t.string "display_color", :limit => 6 | ||
210 | end | 210 | end |
211 | 211 | ||
212 | create_table "categories_profiles", :id => false, :force => true do |t| | 212 | create_table "categories_profiles", :id => false, :force => true do |t| |
features/signup.feature
@@ -313,3 +313,55 @@ Feature: signup | @@ -313,3 +313,55 @@ Feature: signup | ||
313 | And wait for the captcha signup time | 313 | And wait for the captcha signup time |
314 | And I press "Create my account" | 314 | And I press "Create my account" |
315 | Then "José da Silva" should be a member of "Free Software" | 315 | Then "José da Silva" should be a member of "Free Software" |
316 | + | ||
317 | + @selenium | ||
318 | + Scenario: user registration is moderated by admin | ||
319 | + Given feature "admin_must_approve_new_users" is enabled on environment | ||
320 | + And feature "skip_new_user_email_confirmation" is disabled on environment | ||
321 | + And I go to /account/signup | ||
322 | + And I fill in "Username" with "teste" | ||
323 | + And I fill in "Password" with "123456" | ||
324 | + And I fill in "Password confirmation" with "123456" | ||
325 | + And I fill in "e-Mail" with "teste@teste.com" | ||
326 | + And I fill in "Full name" with "Teste da Silva" | ||
327 | + And wait for the captcha signup time | ||
328 | + And I press "Create my account" | ||
329 | + And I go to teste's confirmation URL | ||
330 | + And I am logged in as admin | ||
331 | + And I follow "Control panel" | ||
332 | + And I follow "Tasks" | ||
333 | + And I choose "Accept" | ||
334 | + And I press "Apply!" | ||
335 | + And I follow "Logout" | ||
336 | + And Teste da Silva's account is activated | ||
337 | + And I follow "Login" | ||
338 | + And I fill in "Username / Email" with "teste" | ||
339 | + And I fill in "Password" with "123456" | ||
340 | + And I press "Log in" | ||
341 | + Then I should see "teste" | ||
342 | + | ||
343 | + | ||
344 | + @selenium | ||
345 | + Scenario: user registration is not accepted by the admin | ||
346 | + Given feature "admin_must_approve_new_users" is enabled on environment | ||
347 | + And feature "skip_new_user_email_confirmation" is disabled on environment | ||
348 | + And I go to /account/signup | ||
349 | + And I fill in "Username" with "teste" | ||
350 | + And I fill in "Password" with "123456" | ||
351 | + And I fill in "Password confirmation" with "123456" | ||
352 | + And I fill in "e-Mail" with "teste@teste.com" | ||
353 | + And I fill in "Full name" with "Teste da Silva" | ||
354 | + And wait for the captcha signup time | ||
355 | + And I press "Create my account" | ||
356 | + And I go to teste's confirmation URL | ||
357 | + And I am logged in as admin | ||
358 | + And I follow "Control panel" | ||
359 | + And I follow "Tasks" | ||
360 | + And I choose "Reject" | ||
361 | + And I press "Apply!" | ||
362 | + And I follow "Logout" | ||
363 | + And I follow "Login" | ||
364 | + And I fill in "Username / Email" with "teste" | ||
365 | + And I fill in "Password" with "123456" | ||
366 | + And I press "Log in" | ||
367 | + Then I should not see "teste" | ||
316 | \ No newline at end of file | 368 | \ No newline at end of file |
features/step_definitions/web_steps.rb
@@ -11,8 +11,14 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "pat | @@ -11,8 +11,14 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "pat | ||
11 | 11 | ||
12 | module WithinHelpers | 12 | module WithinHelpers |
13 | def with_scope(locator) | 13 | def with_scope(locator) |
14 | - locator = locator ? first(locator) : locator | ||
15 | - locator ? within(locator) { yield } : yield | 14 | + if locator |
15 | + locator = first(locator) || locator | ||
16 | + within(locator) do | ||
17 | + yield | ||
18 | + end | ||
19 | + else | ||
20 | + yield | ||
21 | + end | ||
16 | end | 22 | end |
17 | end | 23 | end |
18 | World(WithinHelpers) | 24 | World(WithinHelpers) |
lib/tasks/plugins.rake
@@ -7,7 +7,11 @@ namespace :noosfero do | @@ -7,7 +7,11 @@ namespace :noosfero do | ||
7 | plugin_migration_dirs = Dir.glob(Rails.root.join('{baseplugins,config/plugins}', '*', 'db', 'migrate')) | 7 | plugin_migration_dirs = Dir.glob(Rails.root.join('{baseplugins,config/plugins}', '*', 'db', 'migrate')) |
8 | 8 | ||
9 | task :load_config do | 9 | task :load_config do |
10 | - dirs = Dir.glob("{baseplugins,config/plugins}/*/db/migrate") | 10 | + dirs = Dir.glob("{baseplugins,config/plugins}/*").uniq do |dir| |
11 | + File.basename(dir) | ||
12 | + end.map do |dir| | ||
13 | + File.join(dir, 'db/migrate') | ||
14 | + end | ||
11 | dirs.each do |dir| | 15 | dirs.each do |dir| |
12 | ActiveRecord::Migrator.migrations_paths << dir | 16 | ActiveRecord::Migrator.migrations_paths << dir |
13 | end | 17 | end |
plugins/community_track/lib/community_track_plugin/track_helper.rb
1 | module CommunityTrackPlugin::TrackHelper | 1 | module CommunityTrackPlugin::TrackHelper |
2 | 2 | ||
3 | + include CategoriesHelper | ||
4 | + | ||
3 | def category_class(track) | 5 | def category_class(track) |
4 | 'category_' + (track.categories.empty? ? 'not_defined' : track.categories.first.name.to_slug) | 6 | 'category_' + (track.categories.empty? ? 'not_defined' : track.categories.first.name.to_slug) |
5 | end | 7 | end |
@@ -9,4 +11,13 @@ module CommunityTrackPlugin::TrackHelper | @@ -9,4 +11,13 @@ module CommunityTrackPlugin::TrackHelper | ||
9 | excerpt(lead_stripped, lead_stripped.first(3), track.image ? 180 : 300) | 11 | excerpt(lead_stripped, lead_stripped.first(3), track.image ? 180 : 300) |
10 | end | 12 | end |
11 | 13 | ||
14 | + def track_color_style(track) | ||
15 | + category_color_style(track.categories.first.with_color) if !track.categories.empty? | ||
16 | + end | ||
17 | + | ||
18 | + def track_name_color_style(track) | ||
19 | + category = track.categories.empty? ? nil : track.categories.first.with_color | ||
20 | + category ? "color: ##{category.display_color};" : '' | ||
21 | + end | ||
22 | + | ||
12 | end | 23 | end |
plugins/community_track/test/unit/community_track_plugin/track_helper_test.rb
@@ -52,4 +52,16 @@ class TrackHelperTest < ActiveSupport::TestCase | @@ -52,4 +52,16 @@ class TrackHelperTest < ActiveSupport::TestCase | ||
52 | assert_equal 186, track_card_lead(@track).length | 52 | assert_equal 186, track_card_lead(@track).length |
53 | end | 53 | end |
54 | 54 | ||
55 | + should 'return category color if its defined' do | ||
56 | + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb') | ||
57 | + @track.categories << category1 | ||
58 | + assert_equal 'background-color: #fbfbfb;', track_color_style(@track) | ||
59 | + end | ||
60 | + | ||
61 | + should 'return category color for track name' do | ||
62 | + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb') | ||
63 | + @track.categories << category1 | ||
64 | + assert_equal 'color: #fbfbfb;', track_name_color_style(@track) | ||
65 | + end | ||
66 | + | ||
55 | end | 67 | end |
plugins/community_track/views/blocks/_track_card.html.erb
@@ -2,13 +2,13 @@ | @@ -2,13 +2,13 @@ | ||
2 | <div class="item_card <%= category_class(track_card) %>"> | 2 | <div class="item_card <%= category_class(track_card) %>"> |
3 | <a href="<%= url_for track_card.url %>"> | 3 | <a href="<%= url_for track_card.url %>"> |
4 | <div class="track_content"> | 4 | <div class="track_content"> |
5 | - <div class="title"> | 5 | + <div class="title" style="<%= track_color_style(track_card) %>"> |
6 | <%= track_card.category_name %> | 6 | <%= track_card.category_name %> |
7 | </div> | 7 | </div> |
8 | <div class="image"> | 8 | <div class="image"> |
9 | <%= image_tag track_card.image.public_filename if track_card.image %> | 9 | <%= image_tag track_card.image.public_filename if track_card.image %> |
10 | </div> | 10 | </div> |
11 | - <div class="name"> | 11 | + <div class="name" style="<%= track_name_color_style(track_card) %>"> |
12 | <%= track_card.name %> | 12 | <%= track_card.name %> |
13 | </div> | 13 | </div> |
14 | <div class="lead"> | 14 | <div class="lead"> |
po/pt/noosfero.po
@@ -13,7 +13,7 @@ msgid "" | @@ -13,7 +13,7 @@ msgid "" | ||
13 | msgstr "" | 13 | msgstr "" |
14 | "Project-Id-Version: noosfero 0.47.1\n" | 14 | "Project-Id-Version: noosfero 0.47.1\n" |
15 | "POT-Creation-Date: 2014-06-05 20:27-0000\n" | 15 | "POT-Creation-Date: 2014-06-05 20:27-0000\n" |
16 | -"PO-Revision-Date: 2013-07-30 12:53-0300\n" | 16 | +"PO-Revision-Date: 2014-08-22 11:19-0300\n" |
17 | "Last-Translator: Rodrigo Souto <rodrigo@colivre.coop.br>\n" | 17 | "Last-Translator: Rodrigo Souto <rodrigo@colivre.coop.br>\n" |
18 | "Language-Team: Noosfero Develeopers <noosfero-dev@listas.softwarelivre.org>\n" | 18 | "Language-Team: Noosfero Develeopers <noosfero-dev@listas.softwarelivre.org>\n" |
19 | "Language: pt\n" | 19 | "Language: pt\n" |
@@ -469,7 +469,7 @@ msgstr "Local: " | @@ -469,7 +469,7 @@ msgstr "Local: " | ||
469 | #: app/helpers/boxes_helper.rb:37 app/helpers/boxes_helper.rb:63 | 469 | #: app/helpers/boxes_helper.rb:37 app/helpers/boxes_helper.rb:63 |
470 | #: app/models/main_block.rb:4 | 470 | #: app/models/main_block.rb:4 |
471 | msgid "Main content" | 471 | msgid "Main content" |
472 | -msgstr "Gerenciar conteúdo" | 472 | +msgstr "Conteúdo principal" |
473 | 473 | ||
474 | #: app/helpers/boxes_helper.rb:100 | 474 | #: app/helpers/boxes_helper.rb:100 |
475 | msgid "This block is invisible. Your visitors will not see it." | 475 | msgid "This block is invisible. Your visitors will not see it." |
@@ -8925,7 +8925,7 @@ msgstr "Boas vindas ao ambiente %s" | @@ -8925,7 +8925,7 @@ msgstr "Boas vindas ao ambiente %s" | ||
8925 | msgid "" | 8925 | msgid "" |
8926 | "Thanks for signing up, we're thrilled to have you on our social network!" | 8926 | "Thanks for signing up, we're thrilled to have you on our social network!" |
8927 | msgstr "" | 8927 | msgstr "" |
8928 | -"Obrigado por se registrar! Agora verifique seu e-mail para ativar sua conta!" | 8928 | +"Obrigado por se registrar, estamos empolgados de ter você na nossa rede social!" |
8929 | 8929 | ||
8930 | #: app/views/account/signup.rhtml:5 | 8930 | #: app/views/account/signup.rhtml:5 |
8931 | msgid "Firstly, some tips for getting started:" | 8931 | msgid "Firstly, some tips for getting started:" |
@@ -0,0 +1,25 @@ | @@ -0,0 +1,25 @@ | ||
1 | +jQuery(document).ready(function($) { | ||
2 | + $(".colorpicker_field").spectrum({ | ||
3 | + showInput: true, | ||
4 | + showInitial: false, | ||
5 | + preferredFormat: "hex", | ||
6 | + allowEmpty: true, | ||
7 | + showPalette: true, | ||
8 | + palette: [ | ||
9 | + ["rgb(0, 0, 0)", "rgb(67, 67, 67)", "rgb(102, 102, 102)", | ||
10 | + "rgb(204, 204, 204)", "rgb(217, 217, 217)","rgb(255, 255, 255)"], | ||
11 | + ["rgb(152, 0, 0)", "rgb(255, 0, 0)", "rgb(255, 153, 0)", "rgb(255, 255, 0)", "rgb(0, 255, 0)", | ||
12 | + "rgb(0, 255, 255)", "rgb(74, 134, 232)", "rgb(0, 0, 255)", "rgb(153, 0, 255)", "rgb(255, 0, 255)"], | ||
13 | + ["rgb(230, 184, 175)", "rgb(244, 204, 204)", "rgb(252, 229, 205)", "rgb(255, 242, 204)", "rgb(217, 234, 211)", | ||
14 | + "rgb(208, 224, 227)", "rgb(201, 218, 248)", "rgb(207, 226, 243)", "rgb(217, 210, 233)", "rgb(234, 209, 220)", | ||
15 | + "rgb(221, 126, 107)", "rgb(234, 153, 153)", "rgb(249, 203, 156)", "rgb(255, 229, 153)", "rgb(182, 215, 168)", | ||
16 | + "rgb(162, 196, 201)", "rgb(164, 194, 244)", "rgb(159, 197, 232)", "rgb(180, 167, 214)", "rgb(213, 166, 189)", | ||
17 | + "rgb(204, 65, 37)", "rgb(224, 102, 102)", "rgb(246, 178, 107)", "rgb(255, 217, 102)", "rgb(147, 196, 125)", | ||
18 | + "rgb(118, 165, 175)", "rgb(109, 158, 235)", "rgb(111, 168, 220)", "rgb(142, 124, 195)", "rgb(194, 123, 160)", | ||
19 | + "rgb(166, 28, 0)", "rgb(204, 0, 0)", "rgb(230, 145, 56)", "rgb(241, 194, 50)", "rgb(106, 168, 79)", | ||
20 | + "rgb(69, 129, 142)", "rgb(60, 120, 216)", "rgb(61, 133, 198)", "rgb(103, 78, 167)", "rgb(166, 77, 121)", | ||
21 | + "rgb(91, 15, 0)", "rgb(102, 0, 0)", "rgb(120, 63, 4)", "rgb(127, 96, 0)", "rgb(39, 78, 19)", | ||
22 | + "rgb(12, 52, 61)", "rgb(28, 69, 135)", "rgb(7, 55, 99)", "rgb(32, 18, 77)", "rgb(76, 17, 48)"] | ||
23 | + ] | ||
24 | + }); | ||
25 | +}); |
@@ -0,0 +1,523 @@ | @@ -0,0 +1,523 @@ | ||
1 | +/** | ||
2 | + * Inputosaurus Text | ||
3 | + * | ||
4 | + * Must be instantiated on an <input> element | ||
5 | + * Allows multiple input items. Each item is represented with a removable tag that appears to be inside the input area. | ||
6 | + * | ||
7 | + * @requires: | ||
8 | + * | ||
9 | + * jQuery 1.7+ | ||
10 | + * jQueryUI 1.8+ Core | ||
11 | + * | ||
12 | + * @version 0.1.6 | ||
13 | + * @author Dan Kielp <dan@sproutsocial.com> | ||
14 | + * @created October 3,2012 | ||
15 | + * | ||
16 | + */ | ||
17 | + | ||
18 | + | ||
19 | +(function($) { | ||
20 | + | ||
21 | + var inputosaurustext = { | ||
22 | + | ||
23 | + version: "0.1.6", | ||
24 | + | ||
25 | + eventprefix: "inputosaurus", | ||
26 | + | ||
27 | + options: { | ||
28 | + | ||
29 | + // bindable events | ||
30 | + // | ||
31 | + // 'change' - triggered whenever a tag is added or removed (should be similar to binding the the change event of the instantiated input | ||
32 | + // 'keyup' - keyup event on the newly created input | ||
33 | + | ||
34 | + // while typing, the user can separate values using these delimiters | ||
35 | + // the value tags are created on the fly when an inputDelimiter is detected | ||
36 | + inputDelimiters : [',', ';'], | ||
37 | + | ||
38 | + // this separator is used to rejoin all input items back to the value of the original <input> | ||
39 | + outputDelimiter : ',', | ||
40 | + | ||
41 | + allowDuplicates : false, | ||
42 | + | ||
43 | + parseOnBlur : false, | ||
44 | + | ||
45 | + // optional wrapper for widget | ||
46 | + wrapperElement : null, | ||
47 | + | ||
48 | + width : null, | ||
49 | + | ||
50 | + // simply passing an autoComplete source (array, string or function) will instantiate autocomplete functionality | ||
51 | + autoCompleteSource : '', | ||
52 | + | ||
53 | + // When forcing users to select from the autocomplete list, allow them to press 'Enter' to select an item if it's the only option left. | ||
54 | + activateFinalResult : false, | ||
55 | + | ||
56 | + // manipulate and return the input value after parseInput() parsing | ||
57 | + // the array of tag names is passed and expected to be returned as an array after manipulation | ||
58 | + parseHook : null, | ||
59 | + | ||
60 | + // define a placeholder to display when the input is empty | ||
61 | + placeholder: null, | ||
62 | + | ||
63 | + // when you check for duplicates it check for the case | ||
64 | + caseSensitiveDuplicates: false | ||
65 | + }, | ||
66 | + | ||
67 | + _create: function() { | ||
68 | + var widget = this, | ||
69 | + els = {}, | ||
70 | + o = widget.options, | ||
71 | + placeholder = o.placeholder || this.element.attr('placeholder') || null; | ||
72 | + | ||
73 | + this._chosenValues = []; | ||
74 | + | ||
75 | + // Create the elements | ||
76 | + els.ul = $('<ul class="inputosaurus-container">'); | ||
77 | + els.input = $('<input type="text" />'); | ||
78 | + els.inputCont = $('<li class="inputosaurus-input inputosaurus-required"></li>'); | ||
79 | + els.origInputCont = $('<li class="inputosaurus-input-hidden inputosaurus-required">'); | ||
80 | + | ||
81 | + // define starting placeholder | ||
82 | + if (placeholder) { | ||
83 | + o.placeholder = placeholder; | ||
84 | + els.input.attr('placeholder', o.placeholder); | ||
85 | + if (o.width) { | ||
86 | + els.input.css('min-width', o.width - 50); | ||
87 | + } | ||
88 | + } | ||
89 | + | ||
90 | + o.wrapperElement && o.wrapperElement.append(els.ul); | ||
91 | + this.element.replaceWith(o.wrapperElement || els.ul); | ||
92 | + els.origInputCont.append(this.element).hide(); | ||
93 | + | ||
94 | + els.inputCont.append(els.input); | ||
95 | + els.ul.append(els.inputCont); | ||
96 | + els.ul.append(els.origInputCont); | ||
97 | + | ||
98 | + o.width && els.ul.css('width', o.width); | ||
99 | + | ||
100 | + this.elements = els; | ||
101 | + | ||
102 | + widget._attachEvents(); | ||
103 | + | ||
104 | + // if instantiated input already contains a value, parse that junk | ||
105 | + if($.trim(this.element.val())){ | ||
106 | + els.input.val( this.element.val() ); | ||
107 | + this.parseInput(); | ||
108 | + } | ||
109 | + | ||
110 | + this._instAutocomplete(); | ||
111 | + }, | ||
112 | + | ||
113 | + _instAutocomplete : function() { | ||
114 | + if(this.options.autoCompleteSource){ | ||
115 | + var widget = this; | ||
116 | + | ||
117 | + this.elements.input.autocomplete({ | ||
118 | + position : { | ||
119 | + of : this.elements.ul | ||
120 | + }, | ||
121 | + source : this.options.autoCompleteSource, | ||
122 | + minLength : 1, | ||
123 | + select : function(ev, ui){ | ||
124 | + ev.preventDefault(); | ||
125 | + widget.elements.input.val(ui.item.value); | ||
126 | + widget.parseInput(); | ||
127 | + }, | ||
128 | + open : function() { | ||
129 | + // Older versions of jQueryUI have a different namespace | ||
130 | + var auto = $(this).data('ui-autocomplete') || $(this).data('autocomplete'); | ||
131 | + var menu = auto.menu, | ||
132 | + $menuItems; | ||
133 | + | ||
134 | + | ||
135 | + // zIndex will force the element on top of anything (like a dialog it's in) | ||
136 | + menu.element.zIndex && menu.element.zIndex($(this).zIndex() + 1); | ||
137 | + menu.element.width(widget.elements.ul.outerWidth()); | ||
138 | + | ||
139 | + // auto-activate the result if it's the only one | ||
140 | + if(widget.options.activateFinalResult){ | ||
141 | + $menuItems = menu.element.find('li'); | ||
142 | + | ||
143 | + // activate single item to allow selection upon pressing 'Enter' | ||
144 | + if($menuItems.size() === 1){ | ||
145 | + menu[menu.activate ? 'activate' : 'focus']($.Event('click'), $menuItems); | ||
146 | + } | ||
147 | + } | ||
148 | + } | ||
149 | + }); | ||
150 | + } | ||
151 | + }, | ||
152 | + | ||
153 | + _autoCompleteMenuPosition : function() { | ||
154 | + var widget; | ||
155 | + if(this.options.autoCompleteSource){ | ||
156 | + widget = this.elements.input.data('ui-autocomplete') || this.elements.input.data('autocomplete'); | ||
157 | + widget && widget.menu.element.position({ | ||
158 | + of: this.elements.ul, | ||
159 | + my: 'left top', | ||
160 | + at: 'left bottom', | ||
161 | + collision: 'none' | ||
162 | + }); | ||
163 | + } | ||
164 | + }, | ||
165 | + | ||
166 | + /*_closeAutoCompleteMenu : function() { | ||
167 | + if(this.options.autoCompleteSource){ | ||
168 | + this.elements.input.autocomplete('close'); | ||
169 | + } | ||
170 | + },*/ | ||
171 | + | ||
172 | + parseInput : function(ev) { | ||
173 | + var widget = (ev && ev.data.widget) || this, | ||
174 | + val, | ||
175 | + delimiterFound = false, | ||
176 | + values = []; | ||
177 | + | ||
178 | + val = widget.elements.input.val(); | ||
179 | + | ||
180 | + val && (delimiterFound = widget._containsDelimiter(val)); | ||
181 | + | ||
182 | + if(delimiterFound !== false){ | ||
183 | + values = val.split(delimiterFound); | ||
184 | + } else if(!ev || ev.which === $.ui.keyCode.ENTER && !$('.ui-menu-item.ui-state-focus').size() && !$('.ui-menu-item .ui-state-focus').size() && !$('#ui-active-menuitem').size()){ | ||
185 | + values.push(val); | ||
186 | + ev && ev.preventDefault(); | ||
187 | + | ||
188 | + // prevent autoComplete menu click from causing a false 'blur' | ||
189 | + } else if(ev.type === 'blur' && !$('#ui-active-menuitem').size()){ | ||
190 | + values.push(val); | ||
191 | + } | ||
192 | + | ||
193 | + $.isFunction(widget.options.parseHook) && (values = widget.options.parseHook(values)); | ||
194 | + | ||
195 | + if(values.length){ | ||
196 | + widget._setChosen(values); | ||
197 | + widget.elements.input.val(''); | ||
198 | + widget._resizeInput(); | ||
199 | + } | ||
200 | + | ||
201 | + widget._resetPlaceholder(); | ||
202 | + }, | ||
203 | + | ||
204 | + _inputFocus : function(ev) { | ||
205 | + var widget = ev.data.widget || this; | ||
206 | + | ||
207 | + widget.elements.input.value || (widget.options.autoCompleteSource.length && widget.elements.input.autocomplete('search', '')); | ||
208 | + }, | ||
209 | + | ||
210 | + _inputKeypress : function(ev) { | ||
211 | + var widget = ev.data.widget || this; | ||
212 | + | ||
213 | + ev.type === 'keyup' && widget._trigger('keyup', ev, widget); | ||
214 | + | ||
215 | + switch(ev.which){ | ||
216 | + case $.ui.keyCode.BACKSPACE: | ||
217 | + ev.type === 'keydown' && widget._inputBackspace(ev); | ||
218 | + break; | ||
219 | + | ||
220 | + case $.ui.keyCode.LEFT: | ||
221 | + ev.type === 'keydown' && widget._inputBackspace(ev); | ||
222 | + break; | ||
223 | + | ||
224 | + default : | ||
225 | + widget.parseInput(ev); | ||
226 | + widget._resizeInput(ev); | ||
227 | + } | ||
228 | + | ||
229 | + // reposition autoComplete menu as <ul> grows and shrinks vertically | ||
230 | + if(widget.options.autoCompleteSource){ | ||
231 | + setTimeout(function(){widget._autoCompleteMenuPosition.call(widget);}, 200); | ||
232 | + } | ||
233 | + }, | ||
234 | + | ||
235 | + // the input dynamically resizes based on the length of its value | ||
236 | + _resizeInput : function(ev) { | ||
237 | + var widget = (ev && ev.data.widget) || this, | ||
238 | + maxWidth = widget.elements.ul.width(), | ||
239 | + val = widget.elements.input.val(), | ||
240 | + txtWidth = 25 + val.length * 8; | ||
241 | + | ||
242 | + widget.elements.input.width(txtWidth < maxWidth ? txtWidth : maxWidth); | ||
243 | + }, | ||
244 | + | ||
245 | + // resets placeholder on representative input | ||
246 | + _resetPlaceholder: function () { | ||
247 | + var placeholder = this.options.placeholder, | ||
248 | + input = this.elements.input, | ||
249 | + width = this.options.width || 'inherit'; | ||
250 | + if (placeholder && this.element.val().length === 0) { | ||
251 | + input.attr('placeholder', placeholder).css('min-width', width - 50) | ||
252 | + }else { | ||
253 | + input.attr('placeholder', '').css('min-width', 'inherit') | ||
254 | + } | ||
255 | + }, | ||
256 | + | ||
257 | + // if our input contains no value and backspace has been pressed, select the last tag | ||
258 | + _inputBackspace : function(ev) { | ||
259 | + var widget = (ev && ev.data.widget) || this; | ||
260 | + lastTag = widget.elements.ul.find('li:not(.inputosaurus-required):last'); | ||
261 | + | ||
262 | + // IE goes back in history if the event isn't stopped | ||
263 | + ev.stopPropagation(); | ||
264 | + | ||
265 | + if((!$(ev.currentTarget).val() || (('selectionStart' in ev.currentTarget) && ev.currentTarget.selectionStart === 0 && ev.currentTarget.selectionEnd === 0)) && lastTag.size()){ | ||
266 | + ev.preventDefault(); | ||
267 | + lastTag.find('a').focus(); | ||
268 | + } | ||
269 | + | ||
270 | + }, | ||
271 | + | ||
272 | + _editTag : function(ev) { | ||
273 | + var widget = (ev && ev.data.widget) || this, | ||
274 | + tagName = '', | ||
275 | + $closest = $(ev.currentTarget).closest('li'), | ||
276 | + tagKey = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus'); | ||
277 | + | ||
278 | + if(!tagKey){ | ||
279 | + return true; | ||
280 | + } | ||
281 | + | ||
282 | + ev.preventDefault(); | ||
283 | + | ||
284 | + $.each(widget._chosenValues, function(i,v) { | ||
285 | + v.key === tagKey && (tagName = v.value); | ||
286 | + }); | ||
287 | + | ||
288 | + widget.elements.input.val(tagName); | ||
289 | + | ||
290 | + widget._removeTag(ev); | ||
291 | + widget._resizeInput(ev); | ||
292 | + }, | ||
293 | + | ||
294 | + _tagKeypress : function(ev) { | ||
295 | + var widget = ev.data.widget; | ||
296 | + switch(ev.which){ | ||
297 | + | ||
298 | + case $.ui.keyCode.BACKSPACE: | ||
299 | + ev && ev.preventDefault(); | ||
300 | + ev && ev.stopPropagation(); | ||
301 | + $(ev.currentTarget).trigger('click'); | ||
302 | + break; | ||
303 | + | ||
304 | + // 'e' - edit tag (removes tag and places value into visible input | ||
305 | + case 69: | ||
306 | + widget._editTag(ev); | ||
307 | + break; | ||
308 | + | ||
309 | + case $.ui.keyCode.LEFT: | ||
310 | + ev.type === 'keydown' && widget._prevTag(ev); | ||
311 | + break; | ||
312 | + | ||
313 | + case $.ui.keyCode.RIGHT: | ||
314 | + ev.type === 'keydown' && widget._nextTag(ev); | ||
315 | + break; | ||
316 | + | ||
317 | + case $.ui.keyCode.DOWN: | ||
318 | + ev.type === 'keydown' && widget._focus(ev); | ||
319 | + break; | ||
320 | + } | ||
321 | + }, | ||
322 | + | ||
323 | + // select the previous tag or input if no more tags exist | ||
324 | + _prevTag : function(ev) { | ||
325 | + var widget = (ev && ev.data.widget) || this, | ||
326 | + tag = $(ev.currentTarget).closest('li'), | ||
327 | + previous = tag.prev(); | ||
328 | + | ||
329 | + if(previous.is('li')){ | ||
330 | + previous.find('a').focus(); | ||
331 | + } else { | ||
332 | + widget._focus(); | ||
333 | + } | ||
334 | + }, | ||
335 | + | ||
336 | + // select the next tag or input if no more tags exist | ||
337 | + _nextTag : function(ev) { | ||
338 | + var widget = (ev && ev.data.widget) || this, | ||
339 | + tag = $(ev.currentTarget).closest('li'), | ||
340 | + next = tag.next(); | ||
341 | + | ||
342 | + if(next.is('li:not(.inputosaurus-input)')){ | ||
343 | + next.find('a').focus(); | ||
344 | + } else { | ||
345 | + widget._focus(); | ||
346 | + } | ||
347 | + }, | ||
348 | + | ||
349 | + // return the inputDelimiter that was detected or false if none were found | ||
350 | + _containsDelimiter : function(tagStr) { | ||
351 | + | ||
352 | + var found = false; | ||
353 | + | ||
354 | + $.each(this.options.inputDelimiters, function(k,v) { | ||
355 | + if(tagStr.indexOf(v) !== -1){ | ||
356 | + found = v; | ||
357 | + } | ||
358 | + }); | ||
359 | + | ||
360 | + return found; | ||
361 | + }, | ||
362 | + | ||
363 | + _setChosen : function(valArr) { | ||
364 | + var self = this; | ||
365 | + | ||
366 | + if(!$.isArray(valArr)){ | ||
367 | + return false; | ||
368 | + } | ||
369 | + | ||
370 | + $.each(valArr, function(k,v) { | ||
371 | + var exists = false, | ||
372 | + obj = { | ||
373 | + key : '', | ||
374 | + value : '' | ||
375 | + }; | ||
376 | + | ||
377 | + v = $.trim(v); | ||
378 | + | ||
379 | + $.each(self._chosenValues, function(kk,vv) { | ||
380 | + if(!self.options.caseSensitiveDuplicates){ | ||
381 | + vv.value.toLowerCase() === v.toLowerCase() && (exists = true); | ||
382 | + } | ||
383 | + else{ | ||
384 | + vv.value === v && (exists = true); | ||
385 | + } | ||
386 | + }); | ||
387 | + | ||
388 | + if(v !== '' && (!exists || self.options.allowDuplicates)){ | ||
389 | + | ||
390 | + obj.key = 'mi_' + Math.random().toString( 16 ).slice( 2, 10 ); | ||
391 | + obj.value = v; | ||
392 | + self._chosenValues.push(obj); | ||
393 | + | ||
394 | + self._renderTags(); | ||
395 | + } | ||
396 | + }); | ||
397 | + self._setValue(self._buildValue()); | ||
398 | + }, | ||
399 | + | ||
400 | + _buildValue : function() { | ||
401 | + var widget = this, | ||
402 | + value = ''; | ||
403 | + | ||
404 | + $.each(this._chosenValues, function(k,v) { | ||
405 | + value += value.length ? widget.options.outputDelimiter + v.value : v.value; | ||
406 | + }); | ||
407 | + | ||
408 | + return value; | ||
409 | + }, | ||
410 | + | ||
411 | + _setValue : function(value) { | ||
412 | + var val = this.element.val(); | ||
413 | + | ||
414 | + if(val !== value){ | ||
415 | + this.element.val(value); | ||
416 | + this._trigger('change'); | ||
417 | + } | ||
418 | + }, | ||
419 | + | ||
420 | + // @name text for tag | ||
421 | + // @className optional className for <li> | ||
422 | + _createTag : function(name, key, className) { | ||
423 | + className = className ? ' class="' + className + '"' : ''; | ||
424 | + | ||
425 | + if(name !== undefined){ | ||
426 | + return $('<li' + className + ' data-inputosaurus="' + key + '"><span>' + name + '</span> <a href="javascript:void(0);" class="ficon">✖</a></li>'); | ||
427 | + } | ||
428 | + }, | ||
429 | + | ||
430 | + _renderTags : function() { | ||
431 | + var self = this; | ||
432 | + | ||
433 | + this.elements.ul.find('li:not(.inputosaurus-required)').remove(); | ||
434 | + | ||
435 | + $.each(this._chosenValues, function(k,v) { | ||
436 | + var el = self._createTag(v.value, v.key); | ||
437 | + self.elements.ul.find('li.inputosaurus-input').before(el); | ||
438 | + }); | ||
439 | + }, | ||
440 | + | ||
441 | + _removeTag : function(ev) { | ||
442 | + var $closest = $(ev.currentTarget).closest('li'), | ||
443 | + key = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus'), | ||
444 | + indexFound = false, | ||
445 | + widget = (ev && ev.data.widget) || this; | ||
446 | + | ||
447 | + | ||
448 | + $.each(widget._chosenValues, function(k,v) { | ||
449 | + if(key === v.key){ | ||
450 | + indexFound = k; | ||
451 | + } | ||
452 | + }); | ||
453 | + | ||
454 | + indexFound !== false && widget._chosenValues.splice(indexFound, 1); | ||
455 | + | ||
456 | + widget._setValue(widget._buildValue()); | ||
457 | + | ||
458 | + $(ev.currentTarget).closest('li').remove(); | ||
459 | + widget.elements.input.focus(); | ||
460 | + }, | ||
461 | + | ||
462 | + _focus : function(ev) { | ||
463 | + var widget = (ev && ev.data.widget) || this, | ||
464 | + $closest = $(ev.target).closest('li'), | ||
465 | + $data = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus'); | ||
466 | + | ||
467 | + if(!ev || !$data){ | ||
468 | + widget.elements.input.focus(); | ||
469 | + } | ||
470 | + }, | ||
471 | + | ||
472 | + _tagFocus : function(ev) { | ||
473 | + $(ev.currentTarget).parent()[ev.type === 'focusout' ? 'removeClass' : 'addClass']('inputosaurus-selected'); | ||
474 | + }, | ||
475 | + | ||
476 | + refresh : function() { | ||
477 | + var delim = this.options.outputDelimiter, | ||
478 | + val = this.element.val(), | ||
479 | + values = []; | ||
480 | + | ||
481 | + values.push(val); | ||
482 | + delim && (values = val.split(delim)); | ||
483 | + | ||
484 | + if(values.length){ | ||
485 | + this._chosenValues = []; | ||
486 | + | ||
487 | + $.isFunction(this.options.parseHook) && (values = this.options.parseHook(values)); | ||
488 | + | ||
489 | + this._setChosen(values); | ||
490 | + this._renderTags(); | ||
491 | + this.elements.input.val(''); | ||
492 | + this._resizeInput(); | ||
493 | + } | ||
494 | + }, | ||
495 | + | ||
496 | + _attachEvents : function() { | ||
497 | + var widget = this; | ||
498 | + | ||
499 | + this.elements.input.on('keyup.inputosaurus', {widget : widget}, this._inputKeypress); | ||
500 | + this.elements.input.on('keydown.inputosaurus', {widget : widget}, this._inputKeypress); | ||
501 | + this.elements.input.on('change.inputosaurus', {widget : widget}, this._inputKeypress); | ||
502 | + this.elements.input.on('focus.inputosaurus', {widget : widget}, this._inputFocus); | ||
503 | + this.options.parseOnBlur && this.elements.input.on('blur.inputosaurus', {widget : widget}, this.parseInput); | ||
504 | + | ||
505 | + this.elements.ul.on('click.inputosaurus', {widget : widget}, this._focus); | ||
506 | + this.elements.ul.on('click.inputosaurus', 'a', {widget : widget}, this._removeTag); | ||
507 | + this.elements.ul.on('dblclick.inputosaurus', 'li', {widget : widget}, this._editTag); | ||
508 | + this.elements.ul.on('focus.inputosaurus', 'a', {widget : widget}, this._tagFocus); | ||
509 | + this.elements.ul.on('blur.inputosaurus', 'a', {widget : widget}, this._tagFocus); | ||
510 | + this.elements.ul.on('keydown.inputosaurus', 'a', {widget : widget}, this._tagKeypress); | ||
511 | + }, | ||
512 | + | ||
513 | + _destroy: function() { | ||
514 | + this.elements.input.unbind('.inputosaurus'); | ||
515 | + | ||
516 | + this.elements.ul.replaceWith(this.element); | ||
517 | + | ||
518 | + } | ||
519 | + }; | ||
520 | + | ||
521 | + $.widget("ui.inputosaurus", inputosaurustext); | ||
522 | +})(jQuery); | ||
523 | + |
@@ -0,0 +1,2259 @@ | @@ -0,0 +1,2259 @@ | ||
1 | +// Spectrum Colorpicker v1.4.1 | ||
2 | +// https://github.com/bgrins/spectrum | ||
3 | +// Author: Brian Grinstead | ||
4 | +// License: MIT | ||
5 | + | ||
6 | +(function (window, $, undefined) { | ||
7 | + "use strict"; | ||
8 | + | ||
9 | + var defaultOpts = { | ||
10 | + | ||
11 | + // Callbacks | ||
12 | + beforeShow: noop, | ||
13 | + move: noop, | ||
14 | + change: noop, | ||
15 | + show: noop, | ||
16 | + hide: noop, | ||
17 | + | ||
18 | + // Options | ||
19 | + color: false, | ||
20 | + flat: false, | ||
21 | + showInput: false, | ||
22 | + allowEmpty: false, | ||
23 | + showButtons: true, | ||
24 | + clickoutFiresChange: false, | ||
25 | + showInitial: false, | ||
26 | + showPalette: false, | ||
27 | + showPaletteOnly: false, | ||
28 | + hideAfterPaletteSelect: false, | ||
29 | + togglePaletteOnly: false, | ||
30 | + showSelectionPalette: true, | ||
31 | + localStorageKey: false, | ||
32 | + appendTo: "body", | ||
33 | + maxSelectionSize: 7, | ||
34 | + cancelText: "cancel", | ||
35 | + chooseText: "choose", | ||
36 | + togglePaletteMoreText: "more", | ||
37 | + togglePaletteLessText: "less", | ||
38 | + clearText: "Clear Color Selection", | ||
39 | + noColorSelectedText: "No Color Selected", | ||
40 | + preferredFormat: false, | ||
41 | + className: "", // Deprecated - use containerClassName and replacerClassName instead. | ||
42 | + containerClassName: "", | ||
43 | + replacerClassName: "", | ||
44 | + showAlpha: false, | ||
45 | + theme: "sp-light", | ||
46 | + palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]], | ||
47 | + selectionPalette: [], | ||
48 | + disabled: false | ||
49 | + }, | ||
50 | + spectrums = [], | ||
51 | + IE = !!/msie/i.exec( window.navigator.userAgent ), | ||
52 | + rgbaSupport = (function() { | ||
53 | + function contains( str, substr ) { | ||
54 | + return !!~('' + str).indexOf(substr); | ||
55 | + } | ||
56 | + | ||
57 | + var elem = document.createElement('div'); | ||
58 | + var style = elem.style; | ||
59 | + style.cssText = 'background-color:rgba(0,0,0,.5)'; | ||
60 | + return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla'); | ||
61 | + })(), | ||
62 | + inputTypeColorSupport = (function() { | ||
63 | + var colorInput = $("<input type='color' value='!' />")[0]; | ||
64 | + return colorInput.type === "color" && colorInput.value !== "!"; | ||
65 | + })(), | ||
66 | + replaceInput = [ | ||
67 | + "<div class='sp-replacer'>", | ||
68 | + "<div class='sp-preview'><div class='sp-preview-inner'></div></div>", | ||
69 | + "<div class='sp-dd'>▼</div>", | ||
70 | + "</div>" | ||
71 | + ].join(''), | ||
72 | + markup = (function () { | ||
73 | + | ||
74 | + // IE does not support gradients with multiple stops, so we need to simulate | ||
75 | + // that for the rainbow slider with 8 divs that each have a single gradient | ||
76 | + var gradientFix = ""; | ||
77 | + if (IE) { | ||
78 | + for (var i = 1; i <= 6; i++) { | ||
79 | + gradientFix += "<div class='sp-" + i + "'></div>"; | ||
80 | + } | ||
81 | + } | ||
82 | + | ||
83 | + return [ | ||
84 | + "<div class='sp-container sp-hidden'>", | ||
85 | + "<div class='sp-palette-container'>", | ||
86 | + "<div class='sp-palette sp-thumb sp-cf'></div>", | ||
87 | + "<div class='sp-palette-button-container sp-cf'>", | ||
88 | + "<button type='button' class='sp-palette-toggle'></button>", | ||
89 | + "</div>", | ||
90 | + "</div>", | ||
91 | + "<div class='sp-picker-container'>", | ||
92 | + "<div class='sp-top sp-cf'>", | ||
93 | + "<div class='sp-fill'></div>", | ||
94 | + "<div class='sp-top-inner'>", | ||
95 | + "<div class='sp-color'>", | ||
96 | + "<div class='sp-sat'>", | ||
97 | + "<div class='sp-val'>", | ||
98 | + "<div class='sp-dragger'></div>", | ||
99 | + "</div>", | ||
100 | + "</div>", | ||
101 | + "</div>", | ||
102 | + "<div class='sp-clear sp-clear-display'>", | ||
103 | + "</div>", | ||
104 | + "<div class='sp-hue'>", | ||
105 | + "<div class='sp-slider'></div>", | ||
106 | + gradientFix, | ||
107 | + "</div>", | ||
108 | + "</div>", | ||
109 | + "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>", | ||
110 | + "</div>", | ||
111 | + "<div class='sp-input-container sp-cf'>", | ||
112 | + "<input class='sp-input' type='text' spellcheck='false' />", | ||
113 | + "</div>", | ||
114 | + "<div class='sp-initial sp-thumb sp-cf'></div>", | ||
115 | + "<div class='sp-button-container sp-cf'>", | ||
116 | + "<a class='sp-cancel' href='#'></a>", | ||
117 | + "<button type='button' class='sp-choose'></button>", | ||
118 | + "</div>", | ||
119 | + "</div>", | ||
120 | + "</div>" | ||
121 | + ].join(""); | ||
122 | + })(); | ||
123 | + | ||
124 | + function paletteTemplate (p, color, className, opts) { | ||
125 | + var html = []; | ||
126 | + for (var i = 0; i < p.length; i++) { | ||
127 | + var current = p[i]; | ||
128 | + if(current) { | ||
129 | + var tiny = tinycolor(current); | ||
130 | + var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light"; | ||
131 | + c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : ""; | ||
132 | + var formattedString = tiny.toString(opts.preferredFormat || "rgb"); | ||
133 | + var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter(); | ||
134 | + html.push('<span title="' + formattedString + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>'); | ||
135 | + } else { | ||
136 | + var cls = 'sp-clear-display'; | ||
137 | + html.push($('<div />') | ||
138 | + .append($('<span data-color="" style="background-color:transparent;" class="' + cls + '"></span>') | ||
139 | + .attr('title', opts.noColorSelectedText) | ||
140 | + ) | ||
141 | + .html() | ||
142 | + ); | ||
143 | + } | ||
144 | + } | ||
145 | + return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>"; | ||
146 | + } | ||
147 | + | ||
148 | + function hideAll() { | ||
149 | + for (var i = 0; i < spectrums.length; i++) { | ||
150 | + if (spectrums[i]) { | ||
151 | + spectrums[i].hide(); | ||
152 | + } | ||
153 | + } | ||
154 | + } | ||
155 | + | ||
156 | + function instanceOptions(o, callbackContext) { | ||
157 | + var opts = $.extend({}, defaultOpts, o); | ||
158 | + opts.callbacks = { | ||
159 | + 'move': bind(opts.move, callbackContext), | ||
160 | + 'change': bind(opts.change, callbackContext), | ||
161 | + 'show': bind(opts.show, callbackContext), | ||
162 | + 'hide': bind(opts.hide, callbackContext), | ||
163 | + 'beforeShow': bind(opts.beforeShow, callbackContext) | ||
164 | + }; | ||
165 | + | ||
166 | + return opts; | ||
167 | + } | ||
168 | + | ||
169 | + function spectrum(element, o) { | ||
170 | + | ||
171 | + var opts = instanceOptions(o, element), | ||
172 | + flat = opts.flat, | ||
173 | + showSelectionPalette = opts.showSelectionPalette, | ||
174 | + localStorageKey = opts.localStorageKey, | ||
175 | + theme = opts.theme, | ||
176 | + callbacks = opts.callbacks, | ||
177 | + resize = throttle(reflow, 10), | ||
178 | + visible = false, | ||
179 | + dragWidth = 0, | ||
180 | + dragHeight = 0, | ||
181 | + dragHelperHeight = 0, | ||
182 | + slideHeight = 0, | ||
183 | + slideWidth = 0, | ||
184 | + alphaWidth = 0, | ||
185 | + alphaSlideHelperWidth = 0, | ||
186 | + slideHelperHeight = 0, | ||
187 | + currentHue = 0, | ||
188 | + currentSaturation = 0, | ||
189 | + currentValue = 0, | ||
190 | + currentAlpha = 1, | ||
191 | + palette = [], | ||
192 | + paletteArray = [], | ||
193 | + paletteLookup = {}, | ||
194 | + selectionPalette = opts.selectionPalette.slice(0), | ||
195 | + maxSelectionSize = opts.maxSelectionSize, | ||
196 | + draggingClass = "sp-dragging", | ||
197 | + shiftMovementDirection = null; | ||
198 | + | ||
199 | + var doc = element.ownerDocument, | ||
200 | + body = doc.body, | ||
201 | + boundElement = $(element), | ||
202 | + disabled = false, | ||
203 | + container = $(markup, doc).addClass(theme), | ||
204 | + pickerContainer = container.find(".sp-picker-container"), | ||
205 | + dragger = container.find(".sp-color"), | ||
206 | + dragHelper = container.find(".sp-dragger"), | ||
207 | + slider = container.find(".sp-hue"), | ||
208 | + slideHelper = container.find(".sp-slider"), | ||
209 | + alphaSliderInner = container.find(".sp-alpha-inner"), | ||
210 | + alphaSlider = container.find(".sp-alpha"), | ||
211 | + alphaSlideHelper = container.find(".sp-alpha-handle"), | ||
212 | + textInput = container.find(".sp-input"), | ||
213 | + paletteContainer = container.find(".sp-palette"), | ||
214 | + initialColorContainer = container.find(".sp-initial"), | ||
215 | + cancelButton = container.find(".sp-cancel"), | ||
216 | + clearButton = container.find(".sp-clear"), | ||
217 | + chooseButton = container.find(".sp-choose"), | ||
218 | + toggleButton = container.find(".sp-palette-toggle"), | ||
219 | + isInput = boundElement.is("input"), | ||
220 | + isInputTypeColor = isInput && inputTypeColorSupport && boundElement.attr("type") === "color", | ||
221 | + shouldReplace = isInput && !flat, | ||
222 | + replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]), | ||
223 | + offsetElement = (shouldReplace) ? replacer : boundElement, | ||
224 | + previewElement = replacer.find(".sp-preview-inner"), | ||
225 | + initialColor = opts.color || (isInput && boundElement.val()), | ||
226 | + colorOnShow = false, | ||
227 | + preferredFormat = opts.preferredFormat, | ||
228 | + currentPreferredFormat = preferredFormat, | ||
229 | + clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange, | ||
230 | + isEmpty = !initialColor, | ||
231 | + allowEmpty = opts.allowEmpty && !isInputTypeColor; | ||
232 | + | ||
233 | + function applyOptions() { | ||
234 | + | ||
235 | + if (opts.showPaletteOnly) { | ||
236 | + opts.showPalette = true; | ||
237 | + } | ||
238 | + | ||
239 | + toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); | ||
240 | + | ||
241 | + if (opts.palette) { | ||
242 | + palette = opts.palette.slice(0); | ||
243 | + paletteArray = $.isArray(palette[0]) ? palette : [palette]; | ||
244 | + paletteLookup = {}; | ||
245 | + for (var i = 0; i < paletteArray.length; i++) { | ||
246 | + for (var j = 0; j < paletteArray[i].length; j++) { | ||
247 | + var rgb = tinycolor(paletteArray[i][j]).toRgbString(); | ||
248 | + paletteLookup[rgb] = true; | ||
249 | + } | ||
250 | + } | ||
251 | + } | ||
252 | + | ||
253 | + container.toggleClass("sp-flat", flat); | ||
254 | + container.toggleClass("sp-input-disabled", !opts.showInput); | ||
255 | + container.toggleClass("sp-alpha-enabled", opts.showAlpha); | ||
256 | + container.toggleClass("sp-clear-enabled", allowEmpty); | ||
257 | + container.toggleClass("sp-buttons-disabled", !opts.showButtons); | ||
258 | + container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly); | ||
259 | + container.toggleClass("sp-palette-disabled", !opts.showPalette); | ||
260 | + container.toggleClass("sp-palette-only", opts.showPaletteOnly); | ||
261 | + container.toggleClass("sp-initial-disabled", !opts.showInitial); | ||
262 | + container.addClass(opts.className).addClass(opts.containerClassName); | ||
263 | + | ||
264 | + reflow(); | ||
265 | + } | ||
266 | + | ||
267 | + function initialize() { | ||
268 | + | ||
269 | + if (IE) { | ||
270 | + container.find("*:not(input)").attr("unselectable", "on"); | ||
271 | + } | ||
272 | + | ||
273 | + applyOptions(); | ||
274 | + | ||
275 | + if (shouldReplace) { | ||
276 | + boundElement.after(replacer).hide(); | ||
277 | + } | ||
278 | + | ||
279 | + if (!allowEmpty) { | ||
280 | + clearButton.hide(); | ||
281 | + } | ||
282 | + | ||
283 | + if (flat) { | ||
284 | + boundElement.after(container).hide(); | ||
285 | + } | ||
286 | + else { | ||
287 | + | ||
288 | + var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo); | ||
289 | + if (appendTo.length !== 1) { | ||
290 | + appendTo = $("body"); | ||
291 | + } | ||
292 | + | ||
293 | + appendTo.append(container); | ||
294 | + } | ||
295 | + | ||
296 | + updateSelectionPaletteFromStorage(); | ||
297 | + | ||
298 | + offsetElement.bind("click.spectrum touchstart.spectrum", function (e) { | ||
299 | + if (!disabled) { | ||
300 | + toggle(); | ||
301 | + } | ||
302 | + | ||
303 | + e.stopPropagation(); | ||
304 | + | ||
305 | + if (!$(e.target).is("input")) { | ||
306 | + e.preventDefault(); | ||
307 | + } | ||
308 | + }); | ||
309 | + | ||
310 | + if(boundElement.is(":disabled") || (opts.disabled === true)) { | ||
311 | + disable(); | ||
312 | + } | ||
313 | + | ||
314 | + // Prevent clicks from bubbling up to document. This would cause it to be hidden. | ||
315 | + container.click(stopPropagation); | ||
316 | + | ||
317 | + // Handle user typed input | ||
318 | + textInput.change(setFromTextInput); | ||
319 | + textInput.bind("paste", function () { | ||
320 | + setTimeout(setFromTextInput, 1); | ||
321 | + }); | ||
322 | + textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } }); | ||
323 | + | ||
324 | + cancelButton.text(opts.cancelText); | ||
325 | + cancelButton.bind("click.spectrum", function (e) { | ||
326 | + e.stopPropagation(); | ||
327 | + e.preventDefault(); | ||
328 | + hide("cancel"); | ||
329 | + }); | ||
330 | + | ||
331 | + clearButton.attr("title", opts.clearText); | ||
332 | + clearButton.bind("click.spectrum", function (e) { | ||
333 | + e.stopPropagation(); | ||
334 | + e.preventDefault(); | ||
335 | + isEmpty = true; | ||
336 | + move(); | ||
337 | + | ||
338 | + if(flat) { | ||
339 | + //for the flat style, this is a change event | ||
340 | + updateOriginalInput(true); | ||
341 | + } | ||
342 | + }); | ||
343 | + | ||
344 | + chooseButton.text(opts.chooseText); | ||
345 | + chooseButton.bind("click.spectrum", function (e) { | ||
346 | + e.stopPropagation(); | ||
347 | + e.preventDefault(); | ||
348 | + | ||
349 | + if (isValid()) { | ||
350 | + updateOriginalInput(true); | ||
351 | + hide(); | ||
352 | + } | ||
353 | + }); | ||
354 | + | ||
355 | + toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); | ||
356 | + toggleButton.bind("click.spectrum", function (e) { | ||
357 | + e.stopPropagation(); | ||
358 | + e.preventDefault(); | ||
359 | + | ||
360 | + opts.showPaletteOnly = !opts.showPaletteOnly; | ||
361 | + | ||
362 | + // To make sure the Picker area is drawn on the right, next to the | ||
363 | + // Palette area (and not below the palette), first move the Palette | ||
364 | + // to the left to make space for the picker, plus 5px extra. | ||
365 | + // The 'applyOptions' function puts the whole container back into place | ||
366 | + // and takes care of the button-text and the sp-palette-only CSS class. | ||
367 | + if (!opts.showPaletteOnly && !flat) { | ||
368 | + container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5)); | ||
369 | + } | ||
370 | + applyOptions(); | ||
371 | + }); | ||
372 | + | ||
373 | + draggable(alphaSlider, function (dragX, dragY, e) { | ||
374 | + currentAlpha = (dragX / alphaWidth); | ||
375 | + isEmpty = false; | ||
376 | + if (e.shiftKey) { | ||
377 | + currentAlpha = Math.round(currentAlpha * 10) / 10; | ||
378 | + } | ||
379 | + | ||
380 | + move(); | ||
381 | + }, dragStart, dragStop); | ||
382 | + | ||
383 | + draggable(slider, function (dragX, dragY) { | ||
384 | + currentHue = parseFloat(dragY / slideHeight); | ||
385 | + isEmpty = false; | ||
386 | + if (!opts.showAlpha) { | ||
387 | + currentAlpha = 1; | ||
388 | + } | ||
389 | + move(); | ||
390 | + }, dragStart, dragStop); | ||
391 | + | ||
392 | + draggable(dragger, function (dragX, dragY, e) { | ||
393 | + | ||
394 | + // shift+drag should snap the movement to either the x or y axis. | ||
395 | + if (!e.shiftKey) { | ||
396 | + shiftMovementDirection = null; | ||
397 | + } | ||
398 | + else if (!shiftMovementDirection) { | ||
399 | + var oldDragX = currentSaturation * dragWidth; | ||
400 | + var oldDragY = dragHeight - (currentValue * dragHeight); | ||
401 | + var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY); | ||
402 | + | ||
403 | + shiftMovementDirection = furtherFromX ? "x" : "y"; | ||
404 | + } | ||
405 | + | ||
406 | + var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x"; | ||
407 | + var setValue = !shiftMovementDirection || shiftMovementDirection === "y"; | ||
408 | + | ||
409 | + if (setSaturation) { | ||
410 | + currentSaturation = parseFloat(dragX / dragWidth); | ||
411 | + } | ||
412 | + if (setValue) { | ||
413 | + currentValue = parseFloat((dragHeight - dragY) / dragHeight); | ||
414 | + } | ||
415 | + | ||
416 | + isEmpty = false; | ||
417 | + if (!opts.showAlpha) { | ||
418 | + currentAlpha = 1; | ||
419 | + } | ||
420 | + | ||
421 | + move(); | ||
422 | + | ||
423 | + }, dragStart, dragStop); | ||
424 | + | ||
425 | + if (!!initialColor) { | ||
426 | + set(initialColor); | ||
427 | + | ||
428 | + // In case color was black - update the preview UI and set the format | ||
429 | + // since the set function will not run (default color is black). | ||
430 | + updateUI(); | ||
431 | + currentPreferredFormat = preferredFormat || tinycolor(initialColor).format; | ||
432 | + | ||
433 | + addColorToSelectionPalette(initialColor); | ||
434 | + } | ||
435 | + else { | ||
436 | + updateUI(); | ||
437 | + } | ||
438 | + | ||
439 | + if (flat) { | ||
440 | + show(); | ||
441 | + } | ||
442 | + | ||
443 | + function paletteElementClick(e) { | ||
444 | + if (e.data && e.data.ignore) { | ||
445 | + set($(e.target).closest(".sp-thumb-el").data("color")); | ||
446 | + move(); | ||
447 | + } | ||
448 | + else { | ||
449 | + set($(e.target).closest(".sp-thumb-el").data("color")); | ||
450 | + move(); | ||
451 | + updateOriginalInput(true); | ||
452 | + if (opts.hideAfterPaletteSelect) { | ||
453 | + hide(); | ||
454 | + } | ||
455 | + } | ||
456 | + | ||
457 | + return false; | ||
458 | + } | ||
459 | + | ||
460 | + var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum"; | ||
461 | + paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick); | ||
462 | + initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick); | ||
463 | + } | ||
464 | + | ||
465 | + function updateSelectionPaletteFromStorage() { | ||
466 | + | ||
467 | + if (localStorageKey && window.localStorage) { | ||
468 | + | ||
469 | + // Migrate old palettes over to new format. May want to remove this eventually. | ||
470 | + try { | ||
471 | + var oldPalette = window.localStorage[localStorageKey].split(",#"); | ||
472 | + if (oldPalette.length > 1) { | ||
473 | + delete window.localStorage[localStorageKey]; | ||
474 | + $.each(oldPalette, function(i, c) { | ||
475 | + addColorToSelectionPalette(c); | ||
476 | + }); | ||
477 | + } | ||
478 | + } | ||
479 | + catch(e) { } | ||
480 | + | ||
481 | + try { | ||
482 | + selectionPalette = window.localStorage[localStorageKey].split(";"); | ||
483 | + } | ||
484 | + catch (e) { } | ||
485 | + } | ||
486 | + } | ||
487 | + | ||
488 | + function addColorToSelectionPalette(color) { | ||
489 | + if (showSelectionPalette) { | ||
490 | + var rgb = tinycolor(color).toRgbString(); | ||
491 | + if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) { | ||
492 | + selectionPalette.push(rgb); | ||
493 | + while(selectionPalette.length > maxSelectionSize) { | ||
494 | + selectionPalette.shift(); | ||
495 | + } | ||
496 | + } | ||
497 | + | ||
498 | + if (localStorageKey && window.localStorage) { | ||
499 | + try { | ||
500 | + window.localStorage[localStorageKey] = selectionPalette.join(";"); | ||
501 | + } | ||
502 | + catch(e) { } | ||
503 | + } | ||
504 | + } | ||
505 | + } | ||
506 | + | ||
507 | + function getUniqueSelectionPalette() { | ||
508 | + var unique = []; | ||
509 | + if (opts.showPalette) { | ||
510 | + for (var i = 0; i < selectionPalette.length; i++) { | ||
511 | + var rgb = tinycolor(selectionPalette[i]).toRgbString(); | ||
512 | + | ||
513 | + if (!paletteLookup[rgb]) { | ||
514 | + unique.push(selectionPalette[i]); | ||
515 | + } | ||
516 | + } | ||
517 | + } | ||
518 | + | ||
519 | + return unique.reverse().slice(0, opts.maxSelectionSize); | ||
520 | + } | ||
521 | + | ||
522 | + function drawPalette() { | ||
523 | + | ||
524 | + var currentColor = get(); | ||
525 | + | ||
526 | + var html = $.map(paletteArray, function (palette, i) { | ||
527 | + return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts); | ||
528 | + }); | ||
529 | + | ||
530 | + updateSelectionPaletteFromStorage(); | ||
531 | + | ||
532 | + if (selectionPalette) { | ||
533 | + html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts)); | ||
534 | + } | ||
535 | + | ||
536 | + paletteContainer.html(html.join("")); | ||
537 | + } | ||
538 | + | ||
539 | + function drawInitial() { | ||
540 | + if (opts.showInitial) { | ||
541 | + var initial = colorOnShow; | ||
542 | + var current = get(); | ||
543 | + initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts)); | ||
544 | + } | ||
545 | + } | ||
546 | + | ||
547 | + function dragStart() { | ||
548 | + if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) { | ||
549 | + reflow(); | ||
550 | + } | ||
551 | + container.addClass(draggingClass); | ||
552 | + shiftMovementDirection = null; | ||
553 | + boundElement.trigger('dragstart.spectrum', [ get() ]); | ||
554 | + } | ||
555 | + | ||
556 | + function dragStop() { | ||
557 | + container.removeClass(draggingClass); | ||
558 | + boundElement.trigger('dragstop.spectrum', [ get() ]); | ||
559 | + } | ||
560 | + | ||
561 | + function setFromTextInput() { | ||
562 | + | ||
563 | + var value = textInput.val(); | ||
564 | + | ||
565 | + if ((value === null || value === "") && allowEmpty) { | ||
566 | + set(null); | ||
567 | + updateOriginalInput(true); | ||
568 | + } | ||
569 | + else { | ||
570 | + var tiny = tinycolor(value); | ||
571 | + if (tiny.isValid()) { | ||
572 | + set(tiny); | ||
573 | + updateOriginalInput(true); | ||
574 | + } | ||
575 | + else { | ||
576 | + textInput.addClass("sp-validation-error"); | ||
577 | + } | ||
578 | + } | ||
579 | + } | ||
580 | + | ||
581 | + function toggle() { | ||
582 | + if (visible) { | ||
583 | + hide(); | ||
584 | + } | ||
585 | + else { | ||
586 | + show(); | ||
587 | + } | ||
588 | + } | ||
589 | + | ||
590 | + function show() { | ||
591 | + var event = $.Event('beforeShow.spectrum'); | ||
592 | + | ||
593 | + if (visible) { | ||
594 | + reflow(); | ||
595 | + return; | ||
596 | + } | ||
597 | + | ||
598 | + boundElement.trigger(event, [ get() ]); | ||
599 | + | ||
600 | + if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) { | ||
601 | + return; | ||
602 | + } | ||
603 | + | ||
604 | + hideAll(); | ||
605 | + visible = true; | ||
606 | + | ||
607 | + $(doc).bind("click.spectrum", hide); | ||
608 | + $(window).bind("resize.spectrum", resize); | ||
609 | + replacer.addClass("sp-active"); | ||
610 | + container.removeClass("sp-hidden"); | ||
611 | + | ||
612 | + reflow(); | ||
613 | + updateUI(); | ||
614 | + | ||
615 | + colorOnShow = get(); | ||
616 | + | ||
617 | + drawInitial(); | ||
618 | + callbacks.show(colorOnShow); | ||
619 | + boundElement.trigger('show.spectrum', [ colorOnShow ]); | ||
620 | + } | ||
621 | + | ||
622 | + function hide(e) { | ||
623 | + | ||
624 | + // Return on right click | ||
625 | + if (e && e.type == "click" && e.button == 2) { return; } | ||
626 | + | ||
627 | + // Return if hiding is unnecessary | ||
628 | + if (!visible || flat) { return; } | ||
629 | + visible = false; | ||
630 | + | ||
631 | + $(doc).unbind("click.spectrum", hide); | ||
632 | + $(window).unbind("resize.spectrum", resize); | ||
633 | + | ||
634 | + replacer.removeClass("sp-active"); | ||
635 | + container.addClass("sp-hidden"); | ||
636 | + | ||
637 | + var colorHasChanged = !tinycolor.equals(get(), colorOnShow); | ||
638 | + | ||
639 | + if (colorHasChanged) { | ||
640 | + if (clickoutFiresChange && e !== "cancel") { | ||
641 | + updateOriginalInput(true); | ||
642 | + } | ||
643 | + else { | ||
644 | + revert(); | ||
645 | + } | ||
646 | + } | ||
647 | + | ||
648 | + callbacks.hide(get()); | ||
649 | + boundElement.trigger('hide.spectrum', [ get() ]); | ||
650 | + } | ||
651 | + | ||
652 | + function revert() { | ||
653 | + set(colorOnShow, true); | ||
654 | + } | ||
655 | + | ||
656 | + function set(color, ignoreFormatChange) { | ||
657 | + if (tinycolor.equals(color, get())) { | ||
658 | + // Update UI just in case a validation error needs | ||
659 | + // to be cleared. | ||
660 | + updateUI(); | ||
661 | + return; | ||
662 | + } | ||
663 | + | ||
664 | + var newColor, newHsv; | ||
665 | + if (!color && allowEmpty) { | ||
666 | + isEmpty = true; | ||
667 | + } else { | ||
668 | + isEmpty = false; | ||
669 | + newColor = tinycolor(color); | ||
670 | + newHsv = newColor.toHsv(); | ||
671 | + | ||
672 | + currentHue = (newHsv.h % 360) / 360; | ||
673 | + currentSaturation = newHsv.s; | ||
674 | + currentValue = newHsv.v; | ||
675 | + currentAlpha = newHsv.a; | ||
676 | + } | ||
677 | + updateUI(); | ||
678 | + | ||
679 | + if (newColor && newColor.isValid() && !ignoreFormatChange) { | ||
680 | + currentPreferredFormat = preferredFormat || newColor.getFormat(); | ||
681 | + } | ||
682 | + } | ||
683 | + | ||
684 | + function get(opts) { | ||
685 | + opts = opts || { }; | ||
686 | + | ||
687 | + if (allowEmpty && isEmpty) { | ||
688 | + return null; | ||
689 | + } | ||
690 | + | ||
691 | + return tinycolor.fromRatio({ | ||
692 | + h: currentHue, | ||
693 | + s: currentSaturation, | ||
694 | + v: currentValue, | ||
695 | + a: Math.round(currentAlpha * 100) / 100 | ||
696 | + }, { format: opts.format || currentPreferredFormat }); | ||
697 | + } | ||
698 | + | ||
699 | + function isValid() { | ||
700 | + return !textInput.hasClass("sp-validation-error"); | ||
701 | + } | ||
702 | + | ||
703 | + function move() { | ||
704 | + updateUI(); | ||
705 | + | ||
706 | + callbacks.move(get()); | ||
707 | + boundElement.trigger('move.spectrum', [ get() ]); | ||
708 | + } | ||
709 | + | ||
710 | + function updateUI() { | ||
711 | + | ||
712 | + textInput.removeClass("sp-validation-error"); | ||
713 | + | ||
714 | + updateHelperLocations(); | ||
715 | + | ||
716 | + // Update dragger background color (gradients take care of saturation and value). | ||
717 | + var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 }); | ||
718 | + dragger.css("background-color", flatColor.toHexString()); | ||
719 | + | ||
720 | + // Get a format that alpha will be included in (hex and names ignore alpha) | ||
721 | + var format = currentPreferredFormat; | ||
722 | + if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) { | ||
723 | + if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") { | ||
724 | + format = "rgb"; | ||
725 | + } | ||
726 | + } | ||
727 | + | ||
728 | + var realColor = get({ format: format }), | ||
729 | + displayColor = ''; | ||
730 | + | ||
731 | + //reset background info for preview element | ||
732 | + previewElement.removeClass("sp-clear-display"); | ||
733 | + previewElement.css('background-color', 'transparent'); | ||
734 | + | ||
735 | + if (!realColor && allowEmpty) { | ||
736 | + // Update the replaced elements background with icon indicating no color selection | ||
737 | + previewElement.addClass("sp-clear-display"); | ||
738 | + } | ||
739 | + else { | ||
740 | + var realHex = realColor.toHexString(), | ||
741 | + realRgb = realColor.toRgbString(); | ||
742 | + | ||
743 | + // Update the replaced elements background color (with actual selected color) | ||
744 | + if (rgbaSupport || realColor.alpha === 1) { | ||
745 | + previewElement.css("background-color", realRgb); | ||
746 | + } | ||
747 | + else { | ||
748 | + previewElement.css("background-color", "transparent"); | ||
749 | + previewElement.css("filter", realColor.toFilter()); | ||
750 | + } | ||
751 | + | ||
752 | + if (opts.showAlpha) { | ||
753 | + var rgb = realColor.toRgb(); | ||
754 | + rgb.a = 0; | ||
755 | + var realAlpha = tinycolor(rgb).toRgbString(); | ||
756 | + var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")"; | ||
757 | + | ||
758 | + if (IE) { | ||
759 | + alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex)); | ||
760 | + } | ||
761 | + else { | ||
762 | + alphaSliderInner.css("background", "-webkit-" + gradient); | ||
763 | + alphaSliderInner.css("background", "-moz-" + gradient); | ||
764 | + alphaSliderInner.css("background", "-ms-" + gradient); | ||
765 | + // Use current syntax gradient on unprefixed property. | ||
766 | + alphaSliderInner.css("background", | ||
767 | + "linear-gradient(to right, " + realAlpha + ", " + realHex + ")"); | ||
768 | + } | ||
769 | + } | ||
770 | + | ||
771 | + displayColor = realColor.toString(format); | ||
772 | + } | ||
773 | + | ||
774 | + // Update the text entry input as it changes happen | ||
775 | + if (opts.showInput) { | ||
776 | + textInput.val(displayColor); | ||
777 | + } | ||
778 | + | ||
779 | + if (opts.showPalette) { | ||
780 | + drawPalette(); | ||
781 | + } | ||
782 | + | ||
783 | + drawInitial(); | ||
784 | + } | ||
785 | + | ||
786 | + function updateHelperLocations() { | ||
787 | + var s = currentSaturation; | ||
788 | + var v = currentValue; | ||
789 | + | ||
790 | + if(allowEmpty && isEmpty) { | ||
791 | + //if selected color is empty, hide the helpers | ||
792 | + alphaSlideHelper.hide(); | ||
793 | + slideHelper.hide(); | ||
794 | + dragHelper.hide(); | ||
795 | + } | ||
796 | + else { | ||
797 | + //make sure helpers are visible | ||
798 | + alphaSlideHelper.show(); | ||
799 | + slideHelper.show(); | ||
800 | + dragHelper.show(); | ||
801 | + | ||
802 | + // Where to show the little circle in that displays your current selected color | ||
803 | + var dragX = s * dragWidth; | ||
804 | + var dragY = dragHeight - (v * dragHeight); | ||
805 | + dragX = Math.max( | ||
806 | + -dragHelperHeight, | ||
807 | + Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight) | ||
808 | + ); | ||
809 | + dragY = Math.max( | ||
810 | + -dragHelperHeight, | ||
811 | + Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight) | ||
812 | + ); | ||
813 | + dragHelper.css({ | ||
814 | + "top": dragY + "px", | ||
815 | + "left": dragX + "px" | ||
816 | + }); | ||
817 | + | ||
818 | + var alphaX = currentAlpha * alphaWidth; | ||
819 | + alphaSlideHelper.css({ | ||
820 | + "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px" | ||
821 | + }); | ||
822 | + | ||
823 | + // Where to show the bar that displays your current selected hue | ||
824 | + var slideY = (currentHue) * slideHeight; | ||
825 | + slideHelper.css({ | ||
826 | + "top": (slideY - slideHelperHeight) + "px" | ||
827 | + }); | ||
828 | + } | ||
829 | + } | ||
830 | + | ||
831 | + function updateOriginalInput(fireCallback) { | ||
832 | + var color = get(), | ||
833 | + displayColor = '', | ||
834 | + hasChanged = !tinycolor.equals(color, colorOnShow); | ||
835 | + | ||
836 | + if (color) { | ||
837 | + displayColor = color.toString(currentPreferredFormat); | ||
838 | + // Update the selection palette with the current color | ||
839 | + addColorToSelectionPalette(color); | ||
840 | + } | ||
841 | + | ||
842 | + if (isInput) { | ||
843 | + boundElement.val(displayColor); | ||
844 | + } | ||
845 | + | ||
846 | + colorOnShow = color; | ||
847 | + | ||
848 | + if (fireCallback && hasChanged) { | ||
849 | + callbacks.change(color); | ||
850 | + boundElement.trigger('change', [ color ]); | ||
851 | + } | ||
852 | + } | ||
853 | + | ||
854 | + function reflow() { | ||
855 | + dragWidth = dragger.width(); | ||
856 | + dragHeight = dragger.height(); | ||
857 | + dragHelperHeight = dragHelper.height(); | ||
858 | + slideWidth = slider.width(); | ||
859 | + slideHeight = slider.height(); | ||
860 | + slideHelperHeight = slideHelper.height(); | ||
861 | + alphaWidth = alphaSlider.width(); | ||
862 | + alphaSlideHelperWidth = alphaSlideHelper.width(); | ||
863 | + | ||
864 | + if (!flat) { | ||
865 | + container.css("position", "absolute"); | ||
866 | + container.offset(getOffset(container, offsetElement)); | ||
867 | + } | ||
868 | + | ||
869 | + updateHelperLocations(); | ||
870 | + | ||
871 | + if (opts.showPalette) { | ||
872 | + drawPalette(); | ||
873 | + } | ||
874 | + | ||
875 | + boundElement.trigger('reflow.spectrum'); | ||
876 | + } | ||
877 | + | ||
878 | + function destroy() { | ||
879 | + boundElement.show(); | ||
880 | + offsetElement.unbind("click.spectrum touchstart.spectrum"); | ||
881 | + container.remove(); | ||
882 | + replacer.remove(); | ||
883 | + spectrums[spect.id] = null; | ||
884 | + } | ||
885 | + | ||
886 | + function option(optionName, optionValue) { | ||
887 | + if (optionName === undefined) { | ||
888 | + return $.extend({}, opts); | ||
889 | + } | ||
890 | + if (optionValue === undefined) { | ||
891 | + return opts[optionName]; | ||
892 | + } | ||
893 | + | ||
894 | + opts[optionName] = optionValue; | ||
895 | + applyOptions(); | ||
896 | + } | ||
897 | + | ||
898 | + function enable() { | ||
899 | + disabled = false; | ||
900 | + boundElement.attr("disabled", false); | ||
901 | + offsetElement.removeClass("sp-disabled"); | ||
902 | + } | ||
903 | + | ||
904 | + function disable() { | ||
905 | + hide(); | ||
906 | + disabled = true; | ||
907 | + boundElement.attr("disabled", true); | ||
908 | + offsetElement.addClass("sp-disabled"); | ||
909 | + } | ||
910 | + | ||
911 | + initialize(); | ||
912 | + | ||
913 | + var spect = { | ||
914 | + show: show, | ||
915 | + hide: hide, | ||
916 | + toggle: toggle, | ||
917 | + reflow: reflow, | ||
918 | + option: option, | ||
919 | + enable: enable, | ||
920 | + disable: disable, | ||
921 | + set: function (c) { | ||
922 | + set(c); | ||
923 | + updateOriginalInput(); | ||
924 | + }, | ||
925 | + get: get, | ||
926 | + destroy: destroy, | ||
927 | + container: container | ||
928 | + }; | ||
929 | + | ||
930 | + spect.id = spectrums.push(spect) - 1; | ||
931 | + | ||
932 | + return spect; | ||
933 | + } | ||
934 | + | ||
935 | + /** | ||
936 | + * checkOffset - get the offset below/above and left/right element depending on screen position | ||
937 | + * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js | ||
938 | + */ | ||
939 | + function getOffset(picker, input) { | ||
940 | + var extraY = 0; | ||
941 | + var dpWidth = picker.outerWidth(); | ||
942 | + var dpHeight = picker.outerHeight(); | ||
943 | + var inputHeight = input.outerHeight(); | ||
944 | + var doc = picker[0].ownerDocument; | ||
945 | + var docElem = doc.documentElement; | ||
946 | + var viewWidth = docElem.clientWidth + $(doc).scrollLeft(); | ||
947 | + var viewHeight = docElem.clientHeight + $(doc).scrollTop(); | ||
948 | + var offset = input.offset(); | ||
949 | + offset.top += inputHeight; | ||
950 | + | ||
951 | + offset.left -= | ||
952 | + Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? | ||
953 | + Math.abs(offset.left + dpWidth - viewWidth) : 0); | ||
954 | + | ||
955 | + offset.top -= | ||
956 | + Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? | ||
957 | + Math.abs(dpHeight + inputHeight - extraY) : extraY)); | ||
958 | + | ||
959 | + return offset; | ||
960 | + } | ||
961 | + | ||
962 | + /** | ||
963 | + * noop - do nothing | ||
964 | + */ | ||
965 | + function noop() { | ||
966 | + | ||
967 | + } | ||
968 | + | ||
969 | + /** | ||
970 | + * stopPropagation - makes the code only doing this a little easier to read in line | ||
971 | + */ | ||
972 | + function stopPropagation(e) { | ||
973 | + e.stopPropagation(); | ||
974 | + } | ||
975 | + | ||
976 | + /** | ||
977 | + * Create a function bound to a given object | ||
978 | + * Thanks to underscore.js | ||
979 | + */ | ||
980 | + function bind(func, obj) { | ||
981 | + var slice = Array.prototype.slice; | ||
982 | + var args = slice.call(arguments, 2); | ||
983 | + return function () { | ||
984 | + return func.apply(obj, args.concat(slice.call(arguments))); | ||
985 | + }; | ||
986 | + } | ||
987 | + | ||
988 | + /** | ||
989 | + * Lightweight drag helper. Handles containment within the element, so that | ||
990 | + * when dragging, the x is within [0,element.width] and y is within [0,element.height] | ||
991 | + */ | ||
992 | + function draggable(element, onmove, onstart, onstop) { | ||
993 | + onmove = onmove || function () { }; | ||
994 | + onstart = onstart || function () { }; | ||
995 | + onstop = onstop || function () { }; | ||
996 | + var doc = element.ownerDocument || document; | ||
997 | + var dragging = false; | ||
998 | + var offset = {}; | ||
999 | + var maxHeight = 0; | ||
1000 | + var maxWidth = 0; | ||
1001 | + var hasTouch = ('ontouchstart' in window); | ||
1002 | + | ||
1003 | + var duringDragEvents = {}; | ||
1004 | + duringDragEvents["selectstart"] = prevent; | ||
1005 | + duringDragEvents["dragstart"] = prevent; | ||
1006 | + duringDragEvents["touchmove mousemove"] = move; | ||
1007 | + duringDragEvents["touchend mouseup"] = stop; | ||
1008 | + | ||
1009 | + function prevent(e) { | ||
1010 | + if (e.stopPropagation) { | ||
1011 | + e.stopPropagation(); | ||
1012 | + } | ||
1013 | + if (e.preventDefault) { | ||
1014 | + e.preventDefault(); | ||
1015 | + } | ||
1016 | + e.returnValue = false; | ||
1017 | + } | ||
1018 | + | ||
1019 | + function move(e) { | ||
1020 | + if (dragging) { | ||
1021 | + // Mouseup happened outside of window | ||
1022 | + if (IE && document.documentMode < 9 && !e.button) { | ||
1023 | + return stop(); | ||
1024 | + } | ||
1025 | + | ||
1026 | + var touches = e.originalEvent.touches; | ||
1027 | + var pageX = touches ? touches[0].pageX : e.pageX; | ||
1028 | + var pageY = touches ? touches[0].pageY : e.pageY; | ||
1029 | + | ||
1030 | + var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)); | ||
1031 | + var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)); | ||
1032 | + | ||
1033 | + if (hasTouch) { | ||
1034 | + // Stop scrolling in iOS | ||
1035 | + prevent(e); | ||
1036 | + } | ||
1037 | + | ||
1038 | + onmove.apply(element, [dragX, dragY, e]); | ||
1039 | + } | ||
1040 | + } | ||
1041 | + | ||
1042 | + function start(e) { | ||
1043 | + var rightclick = (e.which) ? (e.which == 3) : (e.button == 2); | ||
1044 | + var touches = e.originalEvent.touches; | ||
1045 | + | ||
1046 | + if (!rightclick && !dragging) { | ||
1047 | + if (onstart.apply(element, arguments) !== false) { | ||
1048 | + dragging = true; | ||
1049 | + maxHeight = $(element).height(); | ||
1050 | + maxWidth = $(element).width(); | ||
1051 | + offset = $(element).offset(); | ||
1052 | + | ||
1053 | + $(doc).bind(duringDragEvents); | ||
1054 | + $(doc.body).addClass("sp-dragging"); | ||
1055 | + | ||
1056 | + if (!hasTouch) { | ||
1057 | + move(e); | ||
1058 | + } | ||
1059 | + | ||
1060 | + prevent(e); | ||
1061 | + } | ||
1062 | + } | ||
1063 | + } | ||
1064 | + | ||
1065 | + function stop() { | ||
1066 | + if (dragging) { | ||
1067 | + $(doc).unbind(duringDragEvents); | ||
1068 | + $(doc.body).removeClass("sp-dragging"); | ||
1069 | + onstop.apply(element, arguments); | ||
1070 | + } | ||
1071 | + dragging = false; | ||
1072 | + } | ||
1073 | + | ||
1074 | + $(element).bind("touchstart mousedown", start); | ||
1075 | + } | ||
1076 | + | ||
1077 | + function throttle(func, wait, debounce) { | ||
1078 | + var timeout; | ||
1079 | + return function () { | ||
1080 | + var context = this, args = arguments; | ||
1081 | + var throttler = function () { | ||
1082 | + timeout = null; | ||
1083 | + func.apply(context, args); | ||
1084 | + }; | ||
1085 | + if (debounce) clearTimeout(timeout); | ||
1086 | + if (debounce || !timeout) timeout = setTimeout(throttler, wait); | ||
1087 | + }; | ||
1088 | + } | ||
1089 | + | ||
1090 | + /** | ||
1091 | + * Define a jQuery plugin | ||
1092 | + */ | ||
1093 | + var dataID = "spectrum.id"; | ||
1094 | + $.fn.spectrum = function (opts, extra) { | ||
1095 | + | ||
1096 | + if (typeof opts == "string") { | ||
1097 | + | ||
1098 | + var returnValue = this; | ||
1099 | + var args = Array.prototype.slice.call( arguments, 1 ); | ||
1100 | + | ||
1101 | + this.each(function () { | ||
1102 | + var spect = spectrums[$(this).data(dataID)]; | ||
1103 | + if (spect) { | ||
1104 | + var method = spect[opts]; | ||
1105 | + if (!method) { | ||
1106 | + throw new Error( "Spectrum: no such method: '" + opts + "'" ); | ||
1107 | + } | ||
1108 | + | ||
1109 | + if (opts == "get") { | ||
1110 | + returnValue = spect.get(); | ||
1111 | + } | ||
1112 | + else if (opts == "container") { | ||
1113 | + returnValue = spect.container; | ||
1114 | + } | ||
1115 | + else if (opts == "option") { | ||
1116 | + returnValue = spect.option.apply(spect, args); | ||
1117 | + } | ||
1118 | + else if (opts == "destroy") { | ||
1119 | + spect.destroy(); | ||
1120 | + $(this).removeData(dataID); | ||
1121 | + } | ||
1122 | + else { | ||
1123 | + method.apply(spect, args); | ||
1124 | + } | ||
1125 | + } | ||
1126 | + }); | ||
1127 | + | ||
1128 | + return returnValue; | ||
1129 | + } | ||
1130 | + | ||
1131 | + // Initializing a new instance of spectrum | ||
1132 | + return this.spectrum("destroy").each(function () { | ||
1133 | + var options = $.extend({}, opts, $(this).data()); | ||
1134 | + var spect = spectrum(this, options); | ||
1135 | + $(this).data(dataID, spect.id); | ||
1136 | + }); | ||
1137 | + }; | ||
1138 | + | ||
1139 | + $.fn.spectrum.load = true; | ||
1140 | + $.fn.spectrum.loadOpts = {}; | ||
1141 | + $.fn.spectrum.draggable = draggable; | ||
1142 | + $.fn.spectrum.defaults = defaultOpts; | ||
1143 | + | ||
1144 | + $.spectrum = { }; | ||
1145 | + $.spectrum.localization = { }; | ||
1146 | + $.spectrum.palettes = { }; | ||
1147 | + | ||
1148 | + $.fn.spectrum.processNativeColorInputs = function () { | ||
1149 | + if (!inputTypeColorSupport) { | ||
1150 | + $("input[type=color]").spectrum({ | ||
1151 | + preferredFormat: "hex6" | ||
1152 | + }); | ||
1153 | + } | ||
1154 | + }; | ||
1155 | + | ||
1156 | + // TinyColor v1.0.0 | ||
1157 | + // https://github.com/bgrins/TinyColor | ||
1158 | + // Brian Grinstead, MIT License | ||
1159 | + | ||
1160 | + (function() { | ||
1161 | + | ||
1162 | + var trimLeft = /^[\s,#]+/, | ||
1163 | + trimRight = /\s+$/, | ||
1164 | + tinyCounter = 0, | ||
1165 | + math = Math, | ||
1166 | + mathRound = math.round, | ||
1167 | + mathMin = math.min, | ||
1168 | + mathMax = math.max, | ||
1169 | + mathRandom = math.random; | ||
1170 | + | ||
1171 | + var tinycolor = function tinycolor (color, opts) { | ||
1172 | + | ||
1173 | + color = (color) ? color : ''; | ||
1174 | + opts = opts || { }; | ||
1175 | + | ||
1176 | + // If input is already a tinycolor, return itself | ||
1177 | + if (color instanceof tinycolor) { | ||
1178 | + return color; | ||
1179 | + } | ||
1180 | + // If we are called as a function, call using new instead | ||
1181 | + if (!(this instanceof tinycolor)) { | ||
1182 | + return new tinycolor(color, opts); | ||
1183 | + } | ||
1184 | + | ||
1185 | + var rgb = inputToRGB(color); | ||
1186 | + this._r = rgb.r, | ||
1187 | + this._g = rgb.g, | ||
1188 | + this._b = rgb.b, | ||
1189 | + this._a = rgb.a, | ||
1190 | + this._roundA = mathRound(100*this._a) / 100, | ||
1191 | + this._format = opts.format || rgb.format; | ||
1192 | + this._gradientType = opts.gradientType; | ||
1193 | + | ||
1194 | + // Don't let the range of [0,255] come back in [0,1]. | ||
1195 | + // Potentially lose a little bit of precision here, but will fix issues where | ||
1196 | + // .5 gets interpreted as half of the total, instead of half of 1 | ||
1197 | + // If it was supposed to be 128, this was already taken care of by `inputToRgb` | ||
1198 | + if (this._r < 1) { this._r = mathRound(this._r); } | ||
1199 | + if (this._g < 1) { this._g = mathRound(this._g); } | ||
1200 | + if (this._b < 1) { this._b = mathRound(this._b); } | ||
1201 | + | ||
1202 | + this._ok = rgb.ok; | ||
1203 | + this._tc_id = tinyCounter++; | ||
1204 | + }; | ||
1205 | + | ||
1206 | + tinycolor.prototype = { | ||
1207 | + isDark: function() { | ||
1208 | + return this.getBrightness() < 128; | ||
1209 | + }, | ||
1210 | + isLight: function() { | ||
1211 | + return !this.isDark(); | ||
1212 | + }, | ||
1213 | + isValid: function() { | ||
1214 | + return this._ok; | ||
1215 | + }, | ||
1216 | + getFormat: function() { | ||
1217 | + return this._format; | ||
1218 | + }, | ||
1219 | + getAlpha: function() { | ||
1220 | + return this._a; | ||
1221 | + }, | ||
1222 | + getBrightness: function() { | ||
1223 | + var rgb = this.toRgb(); | ||
1224 | + return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; | ||
1225 | + }, | ||
1226 | + setAlpha: function(value) { | ||
1227 | + this._a = boundAlpha(value); | ||
1228 | + this._roundA = mathRound(100*this._a) / 100; | ||
1229 | + return this; | ||
1230 | + }, | ||
1231 | + toHsv: function() { | ||
1232 | + var hsv = rgbToHsv(this._r, this._g, this._b); | ||
1233 | + return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; | ||
1234 | + }, | ||
1235 | + toHsvString: function() { | ||
1236 | + var hsv = rgbToHsv(this._r, this._g, this._b); | ||
1237 | + var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); | ||
1238 | + return (this._a == 1) ? | ||
1239 | + "hsv(" + h + ", " + s + "%, " + v + "%)" : | ||
1240 | + "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")"; | ||
1241 | + }, | ||
1242 | + toHsl: function() { | ||
1243 | + var hsl = rgbToHsl(this._r, this._g, this._b); | ||
1244 | + return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a }; | ||
1245 | + }, | ||
1246 | + toHslString: function() { | ||
1247 | + var hsl = rgbToHsl(this._r, this._g, this._b); | ||
1248 | + var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); | ||
1249 | + return (this._a == 1) ? | ||
1250 | + "hsl(" + h + ", " + s + "%, " + l + "%)" : | ||
1251 | + "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")"; | ||
1252 | + }, | ||
1253 | + toHex: function(allow3Char) { | ||
1254 | + return rgbToHex(this._r, this._g, this._b, allow3Char); | ||
1255 | + }, | ||
1256 | + toHexString: function(allow3Char) { | ||
1257 | + return '#' + this.toHex(allow3Char); | ||
1258 | + }, | ||
1259 | + toHex8: function() { | ||
1260 | + return rgbaToHex(this._r, this._g, this._b, this._a); | ||
1261 | + }, | ||
1262 | + toHex8String: function() { | ||
1263 | + return '#' + this.toHex8(); | ||
1264 | + }, | ||
1265 | + toRgb: function() { | ||
1266 | + return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; | ||
1267 | + }, | ||
1268 | + toRgbString: function() { | ||
1269 | + return (this._a == 1) ? | ||
1270 | + "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : | ||
1271 | + "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; | ||
1272 | + }, | ||
1273 | + toPercentageRgb: function() { | ||
1274 | + return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a }; | ||
1275 | + }, | ||
1276 | + toPercentageRgbString: function() { | ||
1277 | + return (this._a == 1) ? | ||
1278 | + "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : | ||
1279 | + "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; | ||
1280 | + }, | ||
1281 | + toName: function() { | ||
1282 | + if (this._a === 0) { | ||
1283 | + return "transparent"; | ||
1284 | + } | ||
1285 | + | ||
1286 | + if (this._a < 1) { | ||
1287 | + return false; | ||
1288 | + } | ||
1289 | + | ||
1290 | + return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; | ||
1291 | + }, | ||
1292 | + toFilter: function(secondColor) { | ||
1293 | + var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a); | ||
1294 | + var secondHex8String = hex8String; | ||
1295 | + var gradientType = this._gradientType ? "GradientType = 1, " : ""; | ||
1296 | + | ||
1297 | + if (secondColor) { | ||
1298 | + var s = tinycolor(secondColor); | ||
1299 | + secondHex8String = s.toHex8String(); | ||
1300 | + } | ||
1301 | + | ||
1302 | + return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"; | ||
1303 | + }, | ||
1304 | + toString: function(format) { | ||
1305 | + var formatSet = !!format; | ||
1306 | + format = format || this._format; | ||
1307 | + | ||
1308 | + var formattedString = false; | ||
1309 | + var hasAlpha = this._a < 1 && this._a >= 0; | ||
1310 | + var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name"); | ||
1311 | + | ||
1312 | + if (needsAlphaFormat) { | ||
1313 | + // Special case for "transparent", all other non-alpha formats | ||
1314 | + // will return rgba when there is transparency. | ||
1315 | + if (format === "name" && this._a === 0) { | ||
1316 | + return this.toName(); | ||
1317 | + } | ||
1318 | + return this.toRgbString(); | ||
1319 | + } | ||
1320 | + if (format === "rgb") { | ||
1321 | + formattedString = this.toRgbString(); | ||
1322 | + } | ||
1323 | + if (format === "prgb") { | ||
1324 | + formattedString = this.toPercentageRgbString(); | ||
1325 | + } | ||
1326 | + if (format === "hex" || format === "hex6") { | ||
1327 | + formattedString = this.toHexString(); | ||
1328 | + } | ||
1329 | + if (format === "hex3") { | ||
1330 | + formattedString = this.toHexString(true); | ||
1331 | + } | ||
1332 | + if (format === "hex8") { | ||
1333 | + formattedString = this.toHex8String(); | ||
1334 | + } | ||
1335 | + if (format === "name") { | ||
1336 | + formattedString = this.toName(); | ||
1337 | + } | ||
1338 | + if (format === "hsl") { | ||
1339 | + formattedString = this.toHslString(); | ||
1340 | + } | ||
1341 | + if (format === "hsv") { | ||
1342 | + formattedString = this.toHsvString(); | ||
1343 | + } | ||
1344 | + | ||
1345 | + return formattedString || this.toHexString(); | ||
1346 | + }, | ||
1347 | + | ||
1348 | + _applyModification: function(fn, args) { | ||
1349 | + var color = fn.apply(null, [this].concat([].slice.call(args))); | ||
1350 | + this._r = color._r; | ||
1351 | + this._g = color._g; | ||
1352 | + this._b = color._b; | ||
1353 | + this.setAlpha(color._a); | ||
1354 | + return this; | ||
1355 | + }, | ||
1356 | + lighten: function() { | ||
1357 | + return this._applyModification(lighten, arguments); | ||
1358 | + }, | ||
1359 | + brighten: function() { | ||
1360 | + return this._applyModification(brighten, arguments); | ||
1361 | + }, | ||
1362 | + darken: function() { | ||
1363 | + return this._applyModification(darken, arguments); | ||
1364 | + }, | ||
1365 | + desaturate: function() { | ||
1366 | + return this._applyModification(desaturate, arguments); | ||
1367 | + }, | ||
1368 | + saturate: function() { | ||
1369 | + return this._applyModification(saturate, arguments); | ||
1370 | + }, | ||
1371 | + greyscale: function() { | ||
1372 | + return this._applyModification(greyscale, arguments); | ||
1373 | + }, | ||
1374 | + spin: function() { | ||
1375 | + return this._applyModification(spin, arguments); | ||
1376 | + }, | ||
1377 | + | ||
1378 | + _applyCombination: function(fn, args) { | ||
1379 | + return fn.apply(null, [this].concat([].slice.call(args))); | ||
1380 | + }, | ||
1381 | + analogous: function() { | ||
1382 | + return this._applyCombination(analogous, arguments); | ||
1383 | + }, | ||
1384 | + complement: function() { | ||
1385 | + return this._applyCombination(complement, arguments); | ||
1386 | + }, | ||
1387 | + monochromatic: function() { | ||
1388 | + return this._applyCombination(monochromatic, arguments); | ||
1389 | + }, | ||
1390 | + splitcomplement: function() { | ||
1391 | + return this._applyCombination(splitcomplement, arguments); | ||
1392 | + }, | ||
1393 | + triad: function() { | ||
1394 | + return this._applyCombination(triad, arguments); | ||
1395 | + }, | ||
1396 | + tetrad: function() { | ||
1397 | + return this._applyCombination(tetrad, arguments); | ||
1398 | + } | ||
1399 | + }; | ||
1400 | + | ||
1401 | + // If input is an object, force 1 into "1.0" to handle ratios properly | ||
1402 | + // String input requires "1.0" as input, so 1 will be treated as 1 | ||
1403 | + tinycolor.fromRatio = function(color, opts) { | ||
1404 | + if (typeof color == "object") { | ||
1405 | + var newColor = {}; | ||
1406 | + for (var i in color) { | ||
1407 | + if (color.hasOwnProperty(i)) { | ||
1408 | + if (i === "a") { | ||
1409 | + newColor[i] = color[i]; | ||
1410 | + } | ||
1411 | + else { | ||
1412 | + newColor[i] = convertToPercentage(color[i]); | ||
1413 | + } | ||
1414 | + } | ||
1415 | + } | ||
1416 | + color = newColor; | ||
1417 | + } | ||
1418 | + | ||
1419 | + return tinycolor(color, opts); | ||
1420 | + }; | ||
1421 | + | ||
1422 | + // Given a string or object, convert that input to RGB | ||
1423 | + // Possible string inputs: | ||
1424 | + // | ||
1425 | + // "red" | ||
1426 | + // "#f00" or "f00" | ||
1427 | + // "#ff0000" or "ff0000" | ||
1428 | + // "#ff000000" or "ff000000" | ||
1429 | + // "rgb 255 0 0" or "rgb (255, 0, 0)" | ||
1430 | + // "rgb 1.0 0 0" or "rgb (1, 0, 0)" | ||
1431 | + // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" | ||
1432 | + // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" | ||
1433 | + // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" | ||
1434 | + // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" | ||
1435 | + // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" | ||
1436 | + // | ||
1437 | + function inputToRGB(color) { | ||
1438 | + | ||
1439 | + var rgb = { r: 0, g: 0, b: 0 }; | ||
1440 | + var a = 1; | ||
1441 | + var ok = false; | ||
1442 | + var format = false; | ||
1443 | + | ||
1444 | + if (typeof color == "string") { | ||
1445 | + color = stringInputToObject(color); | ||
1446 | + } | ||
1447 | + | ||
1448 | + if (typeof color == "object") { | ||
1449 | + if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { | ||
1450 | + rgb = rgbToRgb(color.r, color.g, color.b); | ||
1451 | + ok = true; | ||
1452 | + format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; | ||
1453 | + } | ||
1454 | + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { | ||
1455 | + color.s = convertToPercentage(color.s); | ||
1456 | + color.v = convertToPercentage(color.v); | ||
1457 | + rgb = hsvToRgb(color.h, color.s, color.v); | ||
1458 | + ok = true; | ||
1459 | + format = "hsv"; | ||
1460 | + } | ||
1461 | + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { | ||
1462 | + color.s = convertToPercentage(color.s); | ||
1463 | + color.l = convertToPercentage(color.l); | ||
1464 | + rgb = hslToRgb(color.h, color.s, color.l); | ||
1465 | + ok = true; | ||
1466 | + format = "hsl"; | ||
1467 | + } | ||
1468 | + | ||
1469 | + if (color.hasOwnProperty("a")) { | ||
1470 | + a = color.a; | ||
1471 | + } | ||
1472 | + } | ||
1473 | + | ||
1474 | + a = boundAlpha(a); | ||
1475 | + | ||
1476 | + return { | ||
1477 | + ok: ok, | ||
1478 | + format: color.format || format, | ||
1479 | + r: mathMin(255, mathMax(rgb.r, 0)), | ||
1480 | + g: mathMin(255, mathMax(rgb.g, 0)), | ||
1481 | + b: mathMin(255, mathMax(rgb.b, 0)), | ||
1482 | + a: a | ||
1483 | + }; | ||
1484 | + } | ||
1485 | + | ||
1486 | + | ||
1487 | + // Conversion Functions | ||
1488 | + // -------------------- | ||
1489 | + | ||
1490 | + // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: | ||
1491 | + // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript> | ||
1492 | + | ||
1493 | + // `rgbToRgb` | ||
1494 | + // Handle bounds / percentage checking to conform to CSS color spec | ||
1495 | + // <http://www.w3.org/TR/css3-color/> | ||
1496 | + // *Assumes:* r, g, b in [0, 255] or [0, 1] | ||
1497 | + // *Returns:* { r, g, b } in [0, 255] | ||
1498 | + function rgbToRgb(r, g, b){ | ||
1499 | + return { | ||
1500 | + r: bound01(r, 255) * 255, | ||
1501 | + g: bound01(g, 255) * 255, | ||
1502 | + b: bound01(b, 255) * 255 | ||
1503 | + }; | ||
1504 | + } | ||
1505 | + | ||
1506 | + // `rgbToHsl` | ||
1507 | + // Converts an RGB color value to HSL. | ||
1508 | + // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] | ||
1509 | + // *Returns:* { h, s, l } in [0,1] | ||
1510 | + function rgbToHsl(r, g, b) { | ||
1511 | + | ||
1512 | + r = bound01(r, 255); | ||
1513 | + g = bound01(g, 255); | ||
1514 | + b = bound01(b, 255); | ||
1515 | + | ||
1516 | + var max = mathMax(r, g, b), min = mathMin(r, g, b); | ||
1517 | + var h, s, l = (max + min) / 2; | ||
1518 | + | ||
1519 | + if(max == min) { | ||
1520 | + h = s = 0; // achromatic | ||
1521 | + } | ||
1522 | + else { | ||
1523 | + var d = max - min; | ||
1524 | + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | ||
1525 | + switch(max) { | ||
1526 | + case r: h = (g - b) / d + (g < b ? 6 : 0); break; | ||
1527 | + case g: h = (b - r) / d + 2; break; | ||
1528 | + case b: h = (r - g) / d + 4; break; | ||
1529 | + } | ||
1530 | + | ||
1531 | + h /= 6; | ||
1532 | + } | ||
1533 | + | ||
1534 | + return { h: h, s: s, l: l }; | ||
1535 | + } | ||
1536 | + | ||
1537 | + // `hslToRgb` | ||
1538 | + // Converts an HSL color value to RGB. | ||
1539 | + // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] | ||
1540 | + // *Returns:* { r, g, b } in the set [0, 255] | ||
1541 | + function hslToRgb(h, s, l) { | ||
1542 | + var r, g, b; | ||
1543 | + | ||
1544 | + h = bound01(h, 360); | ||
1545 | + s = bound01(s, 100); | ||
1546 | + l = bound01(l, 100); | ||
1547 | + | ||
1548 | + function hue2rgb(p, q, t) { | ||
1549 | + if(t < 0) t += 1; | ||
1550 | + if(t > 1) t -= 1; | ||
1551 | + if(t < 1/6) return p + (q - p) * 6 * t; | ||
1552 | + if(t < 1/2) return q; | ||
1553 | + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; | ||
1554 | + return p; | ||
1555 | + } | ||
1556 | + | ||
1557 | + if(s === 0) { | ||
1558 | + r = g = b = l; // achromatic | ||
1559 | + } | ||
1560 | + else { | ||
1561 | + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; | ||
1562 | + var p = 2 * l - q; | ||
1563 | + r = hue2rgb(p, q, h + 1/3); | ||
1564 | + g = hue2rgb(p, q, h); | ||
1565 | + b = hue2rgb(p, q, h - 1/3); | ||
1566 | + } | ||
1567 | + | ||
1568 | + return { r: r * 255, g: g * 255, b: b * 255 }; | ||
1569 | + } | ||
1570 | + | ||
1571 | + // `rgbToHsv` | ||
1572 | + // Converts an RGB color value to HSV | ||
1573 | + // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] | ||
1574 | + // *Returns:* { h, s, v } in [0,1] | ||
1575 | + function rgbToHsv(r, g, b) { | ||
1576 | + | ||
1577 | + r = bound01(r, 255); | ||
1578 | + g = bound01(g, 255); | ||
1579 | + b = bound01(b, 255); | ||
1580 | + | ||
1581 | + var max = mathMax(r, g, b), min = mathMin(r, g, b); | ||
1582 | + var h, s, v = max; | ||
1583 | + | ||
1584 | + var d = max - min; | ||
1585 | + s = max === 0 ? 0 : d / max; | ||
1586 | + | ||
1587 | + if(max == min) { | ||
1588 | + h = 0; // achromatic | ||
1589 | + } | ||
1590 | + else { | ||
1591 | + switch(max) { | ||
1592 | + case r: h = (g - b) / d + (g < b ? 6 : 0); break; | ||
1593 | + case g: h = (b - r) / d + 2; break; | ||
1594 | + case b: h = (r - g) / d + 4; break; | ||
1595 | + } | ||
1596 | + h /= 6; | ||
1597 | + } | ||
1598 | + return { h: h, s: s, v: v }; | ||
1599 | + } | ||
1600 | + | ||
1601 | + // `hsvToRgb` | ||
1602 | + // Converts an HSV color value to RGB. | ||
1603 | + // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] | ||
1604 | + // *Returns:* { r, g, b } in the set [0, 255] | ||
1605 | + function hsvToRgb(h, s, v) { | ||
1606 | + | ||
1607 | + h = bound01(h, 360) * 6; | ||
1608 | + s = bound01(s, 100); | ||
1609 | + v = bound01(v, 100); | ||
1610 | + | ||
1611 | + var i = math.floor(h), | ||
1612 | + f = h - i, | ||
1613 | + p = v * (1 - s), | ||
1614 | + q = v * (1 - f * s), | ||
1615 | + t = v * (1 - (1 - f) * s), | ||
1616 | + mod = i % 6, | ||
1617 | + r = [v, q, p, p, t, v][mod], | ||
1618 | + g = [t, v, v, q, p, p][mod], | ||
1619 | + b = [p, p, t, v, v, q][mod]; | ||
1620 | + | ||
1621 | + return { r: r * 255, g: g * 255, b: b * 255 }; | ||
1622 | + } | ||
1623 | + | ||
1624 | + // `rgbToHex` | ||
1625 | + // Converts an RGB color to hex | ||
1626 | + // Assumes r, g, and b are contained in the set [0, 255] | ||
1627 | + // Returns a 3 or 6 character hex | ||
1628 | + function rgbToHex(r, g, b, allow3Char) { | ||
1629 | + | ||
1630 | + var hex = [ | ||
1631 | + pad2(mathRound(r).toString(16)), | ||
1632 | + pad2(mathRound(g).toString(16)), | ||
1633 | + pad2(mathRound(b).toString(16)) | ||
1634 | + ]; | ||
1635 | + | ||
1636 | + // Return a 3 character hex if possible | ||
1637 | + if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { | ||
1638 | + return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); | ||
1639 | + } | ||
1640 | + | ||
1641 | + return hex.join(""); | ||
1642 | + } | ||
1643 | + // `rgbaToHex` | ||
1644 | + // Converts an RGBA color plus alpha transparency to hex | ||
1645 | + // Assumes r, g, b and a are contained in the set [0, 255] | ||
1646 | + // Returns an 8 character hex | ||
1647 | + function rgbaToHex(r, g, b, a) { | ||
1648 | + | ||
1649 | + var hex = [ | ||
1650 | + pad2(convertDecimalToHex(a)), | ||
1651 | + pad2(mathRound(r).toString(16)), | ||
1652 | + pad2(mathRound(g).toString(16)), | ||
1653 | + pad2(mathRound(b).toString(16)) | ||
1654 | + ]; | ||
1655 | + | ||
1656 | + return hex.join(""); | ||
1657 | + } | ||
1658 | + | ||
1659 | + // `equals` | ||
1660 | + // Can be called with any tinycolor input | ||
1661 | + tinycolor.equals = function (color1, color2) { | ||
1662 | + if (!color1 || !color2) { return false; } | ||
1663 | + return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); | ||
1664 | + }; | ||
1665 | + tinycolor.random = function() { | ||
1666 | + return tinycolor.fromRatio({ | ||
1667 | + r: mathRandom(), | ||
1668 | + g: mathRandom(), | ||
1669 | + b: mathRandom() | ||
1670 | + }); | ||
1671 | + }; | ||
1672 | + | ||
1673 | + | ||
1674 | + // Modification Functions | ||
1675 | + // ---------------------- | ||
1676 | + // Thanks to less.js for some of the basics here | ||
1677 | + // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js> | ||
1678 | + | ||
1679 | + function desaturate(color, amount) { | ||
1680 | + amount = (amount === 0) ? 0 : (amount || 10); | ||
1681 | + var hsl = tinycolor(color).toHsl(); | ||
1682 | + hsl.s -= amount / 100; | ||
1683 | + hsl.s = clamp01(hsl.s); | ||
1684 | + return tinycolor(hsl); | ||
1685 | + } | ||
1686 | + | ||
1687 | + function saturate(color, amount) { | ||
1688 | + amount = (amount === 0) ? 0 : (amount || 10); | ||
1689 | + var hsl = tinycolor(color).toHsl(); | ||
1690 | + hsl.s += amount / 100; | ||
1691 | + hsl.s = clamp01(hsl.s); | ||
1692 | + return tinycolor(hsl); | ||
1693 | + } | ||
1694 | + | ||
1695 | + function greyscale(color) { | ||
1696 | + return tinycolor(color).desaturate(100); | ||
1697 | + } | ||
1698 | + | ||
1699 | + function lighten (color, amount) { | ||
1700 | + amount = (amount === 0) ? 0 : (amount || 10); | ||
1701 | + var hsl = tinycolor(color).toHsl(); | ||
1702 | + hsl.l += amount / 100; | ||
1703 | + hsl.l = clamp01(hsl.l); | ||
1704 | + return tinycolor(hsl); | ||
1705 | + } | ||
1706 | + | ||
1707 | + function brighten(color, amount) { | ||
1708 | + amount = (amount === 0) ? 0 : (amount || 10); | ||
1709 | + var rgb = tinycolor(color).toRgb(); | ||
1710 | + rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100)))); | ||
1711 | + rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100)))); | ||
1712 | + rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100)))); | ||
1713 | + return tinycolor(rgb); | ||
1714 | + } | ||
1715 | + | ||
1716 | + function darken (color, amount) { | ||
1717 | + amount = (amount === 0) ? 0 : (amount || 10); | ||
1718 | + var hsl = tinycolor(color).toHsl(); | ||
1719 | + hsl.l -= amount / 100; | ||
1720 | + hsl.l = clamp01(hsl.l); | ||
1721 | + return tinycolor(hsl); | ||
1722 | + } | ||
1723 | + | ||
1724 | + // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. | ||
1725 | + // Values outside of this range will be wrapped into this range. | ||
1726 | + function spin(color, amount) { | ||
1727 | + var hsl = tinycolor(color).toHsl(); | ||
1728 | + var hue = (mathRound(hsl.h) + amount) % 360; | ||
1729 | + hsl.h = hue < 0 ? 360 + hue : hue; | ||
1730 | + return tinycolor(hsl); | ||
1731 | + } | ||
1732 | + | ||
1733 | + // Combination Functions | ||
1734 | + // --------------------- | ||
1735 | + // Thanks to jQuery xColor for some of the ideas behind these | ||
1736 | + // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js> | ||
1737 | + | ||
1738 | + function complement(color) { | ||
1739 | + var hsl = tinycolor(color).toHsl(); | ||
1740 | + hsl.h = (hsl.h + 180) % 360; | ||
1741 | + return tinycolor(hsl); | ||
1742 | + } | ||
1743 | + | ||
1744 | + function triad(color) { | ||
1745 | + var hsl = tinycolor(color).toHsl(); | ||
1746 | + var h = hsl.h; | ||
1747 | + return [ | ||
1748 | + tinycolor(color), | ||
1749 | + tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), | ||
1750 | + tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) | ||
1751 | + ]; | ||
1752 | + } | ||
1753 | + | ||
1754 | + function tetrad(color) { | ||
1755 | + var hsl = tinycolor(color).toHsl(); | ||
1756 | + var h = hsl.h; | ||
1757 | + return [ | ||
1758 | + tinycolor(color), | ||
1759 | + tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), | ||
1760 | + tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), | ||
1761 | + tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) | ||
1762 | + ]; | ||
1763 | + } | ||
1764 | + | ||
1765 | + function splitcomplement(color) { | ||
1766 | + var hsl = tinycolor(color).toHsl(); | ||
1767 | + var h = hsl.h; | ||
1768 | + return [ | ||
1769 | + tinycolor(color), | ||
1770 | + tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}), | ||
1771 | + tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l}) | ||
1772 | + ]; | ||
1773 | + } | ||
1774 | + | ||
1775 | + function analogous(color, results, slices) { | ||
1776 | + results = results || 6; | ||
1777 | + slices = slices || 30; | ||
1778 | + | ||
1779 | + var hsl = tinycolor(color).toHsl(); | ||
1780 | + var part = 360 / slices; | ||
1781 | + var ret = [tinycolor(color)]; | ||
1782 | + | ||
1783 | + for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { | ||
1784 | + hsl.h = (hsl.h + part) % 360; | ||
1785 | + ret.push(tinycolor(hsl)); | ||
1786 | + } | ||
1787 | + return ret; | ||
1788 | + } | ||
1789 | + | ||
1790 | + function monochromatic(color, results) { | ||
1791 | + results = results || 6; | ||
1792 | + var hsv = tinycolor(color).toHsv(); | ||
1793 | + var h = hsv.h, s = hsv.s, v = hsv.v; | ||
1794 | + var ret = []; | ||
1795 | + var modification = 1 / results; | ||
1796 | + | ||
1797 | + while (results--) { | ||
1798 | + ret.push(tinycolor({ h: h, s: s, v: v})); | ||
1799 | + v = (v + modification) % 1; | ||
1800 | + } | ||
1801 | + | ||
1802 | + return ret; | ||
1803 | + } | ||
1804 | + | ||
1805 | + // Utility Functions | ||
1806 | + // --------------------- | ||
1807 | + | ||
1808 | + tinycolor.mix = function(color1, color2, amount) { | ||
1809 | + amount = (amount === 0) ? 0 : (amount || 50); | ||
1810 | + | ||
1811 | + var rgb1 = tinycolor(color1).toRgb(); | ||
1812 | + var rgb2 = tinycolor(color2).toRgb(); | ||
1813 | + | ||
1814 | + var p = amount / 100; | ||
1815 | + var w = p * 2 - 1; | ||
1816 | + var a = rgb2.a - rgb1.a; | ||
1817 | + | ||
1818 | + var w1; | ||
1819 | + | ||
1820 | + if (w * a == -1) { | ||
1821 | + w1 = w; | ||
1822 | + } else { | ||
1823 | + w1 = (w + a) / (1 + w * a); | ||
1824 | + } | ||
1825 | + | ||
1826 | + w1 = (w1 + 1) / 2; | ||
1827 | + | ||
1828 | + var w2 = 1 - w1; | ||
1829 | + | ||
1830 | + var rgba = { | ||
1831 | + r: rgb2.r * w1 + rgb1.r * w2, | ||
1832 | + g: rgb2.g * w1 + rgb1.g * w2, | ||
1833 | + b: rgb2.b * w1 + rgb1.b * w2, | ||
1834 | + a: rgb2.a * p + rgb1.a * (1 - p) | ||
1835 | + }; | ||
1836 | + | ||
1837 | + return tinycolor(rgba); | ||
1838 | + }; | ||
1839 | + | ||
1840 | + | ||
1841 | + // Readability Functions | ||
1842 | + // --------------------- | ||
1843 | + // <http://www.w3.org/TR/AERT#color-contrast> | ||
1844 | + | ||
1845 | + // `readability` | ||
1846 | + // Analyze the 2 colors and returns an object with the following properties: | ||
1847 | + // `brightness`: difference in brightness between the two colors | ||
1848 | + // `color`: difference in color/hue between the two colors | ||
1849 | + tinycolor.readability = function(color1, color2) { | ||
1850 | + var c1 = tinycolor(color1); | ||
1851 | + var c2 = tinycolor(color2); | ||
1852 | + var rgb1 = c1.toRgb(); | ||
1853 | + var rgb2 = c2.toRgb(); | ||
1854 | + var brightnessA = c1.getBrightness(); | ||
1855 | + var brightnessB = c2.getBrightness(); | ||
1856 | + var colorDiff = ( | ||
1857 | + Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) + | ||
1858 | + Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) + | ||
1859 | + Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b) | ||
1860 | + ); | ||
1861 | + | ||
1862 | + return { | ||
1863 | + brightness: Math.abs(brightnessA - brightnessB), | ||
1864 | + color: colorDiff | ||
1865 | + }; | ||
1866 | + }; | ||
1867 | + | ||
1868 | + // `readable` | ||
1869 | + // http://www.w3.org/TR/AERT#color-contrast | ||
1870 | + // Ensure that foreground and background color combinations provide sufficient contrast. | ||
1871 | + // *Example* | ||
1872 | + // tinycolor.isReadable("#000", "#111") => false | ||
1873 | + tinycolor.isReadable = function(color1, color2) { | ||
1874 | + var readability = tinycolor.readability(color1, color2); | ||
1875 | + return readability.brightness > 125 && readability.color > 500; | ||
1876 | + }; | ||
1877 | + | ||
1878 | + // `mostReadable` | ||
1879 | + // Given a base color and a list of possible foreground or background | ||
1880 | + // colors for that base, returns the most readable color. | ||
1881 | + // *Example* | ||
1882 | + // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000" | ||
1883 | + tinycolor.mostReadable = function(baseColor, colorList) { | ||
1884 | + var bestColor = null; | ||
1885 | + var bestScore = 0; | ||
1886 | + var bestIsReadable = false; | ||
1887 | + for (var i=0; i < colorList.length; i++) { | ||
1888 | + | ||
1889 | + // We normalize both around the "acceptable" breaking point, | ||
1890 | + // but rank brightness constrast higher than hue. | ||
1891 | + | ||
1892 | + var readability = tinycolor.readability(baseColor, colorList[i]); | ||
1893 | + var readable = readability.brightness > 125 && readability.color > 500; | ||
1894 | + var score = 3 * (readability.brightness / 125) + (readability.color / 500); | ||
1895 | + | ||
1896 | + if ((readable && ! bestIsReadable) || | ||
1897 | + (readable && bestIsReadable && score > bestScore) || | ||
1898 | + ((! readable) && (! bestIsReadable) && score > bestScore)) { | ||
1899 | + bestIsReadable = readable; | ||
1900 | + bestScore = score; | ||
1901 | + bestColor = tinycolor(colorList[i]); | ||
1902 | + } | ||
1903 | + } | ||
1904 | + return bestColor; | ||
1905 | + }; | ||
1906 | + | ||
1907 | + | ||
1908 | + // Big List of Colors | ||
1909 | + // ------------------ | ||
1910 | + // <http://www.w3.org/TR/css3-color/#svg-color> | ||
1911 | + var names = tinycolor.names = { | ||
1912 | + aliceblue: "f0f8ff", | ||
1913 | + antiquewhite: "faebd7", | ||
1914 | + aqua: "0ff", | ||
1915 | + aquamarine: "7fffd4", | ||
1916 | + azure: "f0ffff", | ||
1917 | + beige: "f5f5dc", | ||
1918 | + bisque: "ffe4c4", | ||
1919 | + black: "000", | ||
1920 | + blanchedalmond: "ffebcd", | ||
1921 | + blue: "00f", | ||
1922 | + blueviolet: "8a2be2", | ||
1923 | + brown: "a52a2a", | ||
1924 | + burlywood: "deb887", | ||
1925 | + burntsienna: "ea7e5d", | ||
1926 | + cadetblue: "5f9ea0", | ||
1927 | + chartreuse: "7fff00", | ||
1928 | + chocolate: "d2691e", | ||
1929 | + coral: "ff7f50", | ||
1930 | + cornflowerblue: "6495ed", | ||
1931 | + cornsilk: "fff8dc", | ||
1932 | + crimson: "dc143c", | ||
1933 | + cyan: "0ff", | ||
1934 | + darkblue: "00008b", | ||
1935 | + darkcyan: "008b8b", | ||
1936 | + darkgoldenrod: "b8860b", | ||
1937 | + darkgray: "a9a9a9", | ||
1938 | + darkgreen: "006400", | ||
1939 | + darkgrey: "a9a9a9", | ||
1940 | + darkkhaki: "bdb76b", | ||
1941 | + darkmagenta: "8b008b", | ||
1942 | + darkolivegreen: "556b2f", | ||
1943 | + darkorange: "ff8c00", | ||
1944 | + darkorchid: "9932cc", | ||
1945 | + darkred: "8b0000", | ||
1946 | + darksalmon: "e9967a", | ||
1947 | + darkseagreen: "8fbc8f", | ||
1948 | + darkslateblue: "483d8b", | ||
1949 | + darkslategray: "2f4f4f", | ||
1950 | + darkslategrey: "2f4f4f", | ||
1951 | + darkturquoise: "00ced1", | ||
1952 | + darkviolet: "9400d3", | ||
1953 | + deeppink: "ff1493", | ||
1954 | + deepskyblue: "00bfff", | ||
1955 | + dimgray: "696969", | ||
1956 | + dimgrey: "696969", | ||
1957 | + dodgerblue: "1e90ff", | ||
1958 | + firebrick: "b22222", | ||
1959 | + floralwhite: "fffaf0", | ||
1960 | + forestgreen: "228b22", | ||
1961 | + fuchsia: "f0f", | ||
1962 | + gainsboro: "dcdcdc", | ||
1963 | + ghostwhite: "f8f8ff", | ||
1964 | + gold: "ffd700", | ||
1965 | + goldenrod: "daa520", | ||
1966 | + gray: "808080", | ||
1967 | + green: "008000", | ||
1968 | + greenyellow: "adff2f", | ||
1969 | + grey: "808080", | ||
1970 | + honeydew: "f0fff0", | ||
1971 | + hotpink: "ff69b4", | ||
1972 | + indianred: "cd5c5c", | ||
1973 | + indigo: "4b0082", | ||
1974 | + ivory: "fffff0", | ||
1975 | + khaki: "f0e68c", | ||
1976 | + lavender: "e6e6fa", | ||
1977 | + lavenderblush: "fff0f5", | ||
1978 | + lawngreen: "7cfc00", | ||
1979 | + lemonchiffon: "fffacd", | ||
1980 | + lightblue: "add8e6", | ||
1981 | + lightcoral: "f08080", | ||
1982 | + lightcyan: "e0ffff", | ||
1983 | + lightgoldenrodyellow: "fafad2", | ||
1984 | + lightgray: "d3d3d3", | ||
1985 | + lightgreen: "90ee90", | ||
1986 | + lightgrey: "d3d3d3", | ||
1987 | + lightpink: "ffb6c1", | ||
1988 | + lightsalmon: "ffa07a", | ||
1989 | + lightseagreen: "20b2aa", | ||
1990 | + lightskyblue: "87cefa", | ||
1991 | + lightslategray: "789", | ||
1992 | + lightslategrey: "789", | ||
1993 | + lightsteelblue: "b0c4de", | ||
1994 | + lightyellow: "ffffe0", | ||
1995 | + lime: "0f0", | ||
1996 | + limegreen: "32cd32", | ||
1997 | + linen: "faf0e6", | ||
1998 | + magenta: "f0f", | ||
1999 | + maroon: "800000", | ||
2000 | + mediumaquamarine: "66cdaa", | ||
2001 | + mediumblue: "0000cd", | ||
2002 | + mediumorchid: "ba55d3", | ||
2003 | + mediumpurple: "9370db", | ||
2004 | + mediumseagreen: "3cb371", | ||
2005 | + mediumslateblue: "7b68ee", | ||
2006 | + mediumspringgreen: "00fa9a", | ||
2007 | + mediumturquoise: "48d1cc", | ||
2008 | + mediumvioletred: "c71585", | ||
2009 | + midnightblue: "191970", | ||
2010 | + mintcream: "f5fffa", | ||
2011 | + mistyrose: "ffe4e1", | ||
2012 | + moccasin: "ffe4b5", | ||
2013 | + navajowhite: "ffdead", | ||
2014 | + navy: "000080", | ||
2015 | + oldlace: "fdf5e6", | ||
2016 | + olive: "808000", | ||
2017 | + olivedrab: "6b8e23", | ||
2018 | + orange: "ffa500", | ||
2019 | + orangered: "ff4500", | ||
2020 | + orchid: "da70d6", | ||
2021 | + palegoldenrod: "eee8aa", | ||
2022 | + palegreen: "98fb98", | ||
2023 | + paleturquoise: "afeeee", | ||
2024 | + palevioletred: "db7093", | ||
2025 | + papayawhip: "ffefd5", | ||
2026 | + peachpuff: "ffdab9", | ||
2027 | + peru: "cd853f", | ||
2028 | + pink: "ffc0cb", | ||
2029 | + plum: "dda0dd", | ||
2030 | + powderblue: "b0e0e6", | ||
2031 | + purple: "800080", | ||
2032 | + red: "f00", | ||
2033 | + rosybrown: "bc8f8f", | ||
2034 | + royalblue: "4169e1", | ||
2035 | + saddlebrown: "8b4513", | ||
2036 | + salmon: "fa8072", | ||
2037 | + sandybrown: "f4a460", | ||
2038 | + seagreen: "2e8b57", | ||
2039 | + seashell: "fff5ee", | ||
2040 | + sienna: "a0522d", | ||
2041 | + silver: "c0c0c0", | ||
2042 | + skyblue: "87ceeb", | ||
2043 | + slateblue: "6a5acd", | ||
2044 | + slategray: "708090", | ||
2045 | + slategrey: "708090", | ||
2046 | + snow: "fffafa", | ||
2047 | + springgreen: "00ff7f", | ||
2048 | + steelblue: "4682b4", | ||
2049 | + tan: "d2b48c", | ||
2050 | + teal: "008080", | ||
2051 | + thistle: "d8bfd8", | ||
2052 | + tomato: "ff6347", | ||
2053 | + turquoise: "40e0d0", | ||
2054 | + violet: "ee82ee", | ||
2055 | + wheat: "f5deb3", | ||
2056 | + white: "fff", | ||
2057 | + whitesmoke: "f5f5f5", | ||
2058 | + yellow: "ff0", | ||
2059 | + yellowgreen: "9acd32" | ||
2060 | + }; | ||
2061 | + | ||
2062 | + // Make it easy to access colors via `hexNames[hex]` | ||
2063 | + var hexNames = tinycolor.hexNames = flip(names); | ||
2064 | + | ||
2065 | + | ||
2066 | + // Utilities | ||
2067 | + // --------- | ||
2068 | + | ||
2069 | + // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` | ||
2070 | + function flip(o) { | ||
2071 | + var flipped = { }; | ||
2072 | + for (var i in o) { | ||
2073 | + if (o.hasOwnProperty(i)) { | ||
2074 | + flipped[o[i]] = i; | ||
2075 | + } | ||
2076 | + } | ||
2077 | + return flipped; | ||
2078 | + } | ||
2079 | + | ||
2080 | + // Return a valid alpha value [0,1] with all invalid values being set to 1 | ||
2081 | + function boundAlpha(a) { | ||
2082 | + a = parseFloat(a); | ||
2083 | + | ||
2084 | + if (isNaN(a) || a < 0 || a > 1) { | ||
2085 | + a = 1; | ||
2086 | + } | ||
2087 | + | ||
2088 | + return a; | ||
2089 | + } | ||
2090 | + | ||
2091 | + // Take input from [0, n] and return it as [0, 1] | ||
2092 | + function bound01(n, max) { | ||
2093 | + if (isOnePointZero(n)) { n = "100%"; } | ||
2094 | + | ||
2095 | + var processPercent = isPercentage(n); | ||
2096 | + n = mathMin(max, mathMax(0, parseFloat(n))); | ||
2097 | + | ||
2098 | + // Automatically convert percentage into number | ||
2099 | + if (processPercent) { | ||
2100 | + n = parseInt(n * max, 10) / 100; | ||
2101 | + } | ||
2102 | + | ||
2103 | + // Handle floating point rounding errors | ||
2104 | + if ((math.abs(n - max) < 0.000001)) { | ||
2105 | + return 1; | ||
2106 | + } | ||
2107 | + | ||
2108 | + // Convert into [0, 1] range if it isn't already | ||
2109 | + return (n % max) / parseFloat(max); | ||
2110 | + } | ||
2111 | + | ||
2112 | + // Force a number between 0 and 1 | ||
2113 | + function clamp01(val) { | ||
2114 | + return mathMin(1, mathMax(0, val)); | ||
2115 | + } | ||
2116 | + | ||
2117 | + // Parse a base-16 hex value into a base-10 integer | ||
2118 | + function parseIntFromHex(val) { | ||
2119 | + return parseInt(val, 16); | ||
2120 | + } | ||
2121 | + | ||
2122 | + // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 | ||
2123 | + // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0> | ||
2124 | + function isOnePointZero(n) { | ||
2125 | + return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; | ||
2126 | + } | ||
2127 | + | ||
2128 | + // Check to see if string passed in is a percentage | ||
2129 | + function isPercentage(n) { | ||
2130 | + return typeof n === "string" && n.indexOf('%') != -1; | ||
2131 | + } | ||
2132 | + | ||
2133 | + // Force a hex value to have 2 characters | ||
2134 | + function pad2(c) { | ||
2135 | + return c.length == 1 ? '0' + c : '' + c; | ||
2136 | + } | ||
2137 | + | ||
2138 | + // Replace a decimal with it's percentage value | ||
2139 | + function convertToPercentage(n) { | ||
2140 | + if (n <= 1) { | ||
2141 | + n = (n * 100) + "%"; | ||
2142 | + } | ||
2143 | + | ||
2144 | + return n; | ||
2145 | + } | ||
2146 | + | ||
2147 | + // Converts a decimal to a hex value | ||
2148 | + function convertDecimalToHex(d) { | ||
2149 | + return Math.round(parseFloat(d) * 255).toString(16); | ||
2150 | + } | ||
2151 | + // Converts a hex value to a decimal | ||
2152 | + function convertHexToDecimal(h) { | ||
2153 | + return (parseIntFromHex(h) / 255); | ||
2154 | + } | ||
2155 | + | ||
2156 | + var matchers = (function() { | ||
2157 | + | ||
2158 | + // <http://www.w3.org/TR/css3-values/#integers> | ||
2159 | + var CSS_INTEGER = "[-\\+]?\\d+%?"; | ||
2160 | + | ||
2161 | + // <http://www.w3.org/TR/css3-values/#number-value> | ||
2162 | + var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; | ||
2163 | + | ||
2164 | + // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. | ||
2165 | + var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; | ||
2166 | + | ||
2167 | + // Actual matching. | ||
2168 | + // Parentheses and commas are optional, but not required. | ||
2169 | + // Whitespace can take the place of commas or opening paren | ||
2170 | + var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; | ||
2171 | + var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; | ||
2172 | + | ||
2173 | + return { | ||
2174 | + rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), | ||
2175 | + rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), | ||
2176 | + hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), | ||
2177 | + hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), | ||
2178 | + hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), | ||
2179 | + hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, | ||
2180 | + hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, | ||
2181 | + hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ | ||
2182 | + }; | ||
2183 | + })(); | ||
2184 | + | ||
2185 | + // `stringInputToObject` | ||
2186 | + // Permissive string parsing. Take in a number of formats, and output an object | ||
2187 | + // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` | ||
2188 | + function stringInputToObject(color) { | ||
2189 | + | ||
2190 | + color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); | ||
2191 | + var named = false; | ||
2192 | + if (names[color]) { | ||
2193 | + color = names[color]; | ||
2194 | + named = true; | ||
2195 | + } | ||
2196 | + else if (color == 'transparent') { | ||
2197 | + return { r: 0, g: 0, b: 0, a: 0, format: "name" }; | ||
2198 | + } | ||
2199 | + | ||
2200 | + // Try to match string input using regular expressions. | ||
2201 | + // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] | ||
2202 | + // Just return an object and let the conversion functions handle that. | ||
2203 | + // This way the result will be the same whether the tinycolor is initialized with string or object. | ||
2204 | + var match; | ||
2205 | + if ((match = matchers.rgb.exec(color))) { | ||
2206 | + return { r: match[1], g: match[2], b: match[3] }; | ||
2207 | + } | ||
2208 | + if ((match = matchers.rgba.exec(color))) { | ||
2209 | + return { r: match[1], g: match[2], b: match[3], a: match[4] }; | ||
2210 | + } | ||
2211 | + if ((match = matchers.hsl.exec(color))) { | ||
2212 | + return { h: match[1], s: match[2], l: match[3] }; | ||
2213 | + } | ||
2214 | + if ((match = matchers.hsla.exec(color))) { | ||
2215 | + return { h: match[1], s: match[2], l: match[3], a: match[4] }; | ||
2216 | + } | ||
2217 | + if ((match = matchers.hsv.exec(color))) { | ||
2218 | + return { h: match[1], s: match[2], v: match[3] }; | ||
2219 | + } | ||
2220 | + if ((match = matchers.hex8.exec(color))) { | ||
2221 | + return { | ||
2222 | + a: convertHexToDecimal(match[1]), | ||
2223 | + r: parseIntFromHex(match[2]), | ||
2224 | + g: parseIntFromHex(match[3]), | ||
2225 | + b: parseIntFromHex(match[4]), | ||
2226 | + format: named ? "name" : "hex8" | ||
2227 | + }; | ||
2228 | + } | ||
2229 | + if ((match = matchers.hex6.exec(color))) { | ||
2230 | + return { | ||
2231 | + r: parseIntFromHex(match[1]), | ||
2232 | + g: parseIntFromHex(match[2]), | ||
2233 | + b: parseIntFromHex(match[3]), | ||
2234 | + format: named ? "name" : "hex" | ||
2235 | + }; | ||
2236 | + } | ||
2237 | + if ((match = matchers.hex3.exec(color))) { | ||
2238 | + return { | ||
2239 | + r: parseIntFromHex(match[1] + '' + match[1]), | ||
2240 | + g: parseIntFromHex(match[2] + '' + match[2]), | ||
2241 | + b: parseIntFromHex(match[3] + '' + match[3]), | ||
2242 | + format: named ? "name" : "hex" | ||
2243 | + }; | ||
2244 | + } | ||
2245 | + | ||
2246 | + return false; | ||
2247 | + } | ||
2248 | + | ||
2249 | + window.tinycolor = tinycolor; | ||
2250 | + })(); | ||
2251 | + | ||
2252 | + | ||
2253 | + $(function () { | ||
2254 | + if ($.fn.spectrum.load) { | ||
2255 | + $.fn.spectrum.processNativeColorInputs(); | ||
2256 | + } | ||
2257 | + }); | ||
2258 | + | ||
2259 | +})(window, jQuery); |
public/stylesheets/application.css
@@ -2649,6 +2649,15 @@ div#activation_enterprise label, div#activation_enterprise input, div#activation | @@ -2649,6 +2649,15 @@ div#activation_enterprise label, div#activation_enterprise input, div#activation | ||
2649 | color: #585858; | 2649 | color: #585858; |
2650 | font-size: 11px; | 2650 | font-size: 11px; |
2651 | } | 2651 | } |
2652 | +.colorpicker_field { | ||
2653 | + display: none; | ||
2654 | +} | ||
2655 | +.color_marker { | ||
2656 | + display: inline-block; | ||
2657 | + width: 10px; | ||
2658 | + height: 10px; | ||
2659 | + border: 1px solid; | ||
2660 | +} | ||
2652 | .formfield input { | 2661 | .formfield input { |
2653 | text-indent: 5px; | 2662 | text-indent: 5px; |
2654 | padding: 2px 0px; | 2663 | padding: 2px 0px; |
@@ -0,0 +1,76 @@ | @@ -0,0 +1,76 @@ | ||
1 | +.inputosaurus-container .ui-autocomplete-input { | ||
2 | + background-color: #ffffff; | ||
3 | +} | ||
4 | + | ||
5 | +.inputosaurus-container { | ||
6 | + /*background-color: #fff;*/ | ||
7 | + /*border: 1px solid #bcbec0;*/ | ||
8 | + margin: 0; | ||
9 | + padding: 0; | ||
10 | + display: inline-block; | ||
11 | + cursor: text; | ||
12 | + /**font-size: 14px;**/ | ||
13 | + /**font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;**/ | ||
14 | + max-height: 300px; | ||
15 | + overflow: hidden; | ||
16 | + overflow-y: auto; | ||
17 | +} | ||
18 | +.inputosaurus-container li { | ||
19 | + display: block; | ||
20 | + float: left; | ||
21 | + overflow: hidden; | ||
22 | + margin: 2px 2px 0; | ||
23 | + padding: 2px 3px; | ||
24 | + white-space: nowrap; | ||
25 | + overflow: hidden; | ||
26 | + -o-text-overflow: ellipsis; | ||
27 | + -ms-text-overflow: ellipsis; | ||
28 | + text-overflow: ellipsis; | ||
29 | + background-color: #e5eff7; | ||
30 | + border: #a9cae4 solid 1px; | ||
31 | + -webkit-border-radius: 2px; | ||
32 | + -moz-border-radius: 2px; | ||
33 | + border-radius: 2px; | ||
34 | + color: #5b9bcd; | ||
35 | + -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset; | ||
36 | + -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset; | ||
37 | + box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset; | ||
38 | + line-height: 20px; | ||
39 | + cursor: default; | ||
40 | +} | ||
41 | +.inputosaurus-container li.inputosaurus-selected { background-color: #bdd6eb; } | ||
42 | +.inputosaurus-container li a { | ||
43 | + font-size: 16px; | ||
44 | + color: #5b9bcd; | ||
45 | + padding: 1px; | ||
46 | + text-decoration: none; | ||
47 | + outline: none; | ||
48 | +} | ||
49 | +.inputosaurus-container .inputosaurus-input { | ||
50 | + border: none; | ||
51 | + box-shadow: none; | ||
52 | +} | ||
53 | +.inputosaurus-container .inputosaurus-input input { | ||
54 | + border: none; | ||
55 | + height: 23px; | ||
56 | + font-size: 14px; | ||
57 | + line-height: 20px; | ||
58 | + color: #555; | ||
59 | + margin: 0; | ||
60 | + outline: none; | ||
61 | + padding: 0 0 1px 1px; | ||
62 | + width: 25px; | ||
63 | + -webkit-box-shadow: none; | ||
64 | + -moz-box-shadow: none; | ||
65 | + box-shadow: none; | ||
66 | + background-image: none; | ||
67 | + text-indent: 0px; | ||
68 | +} | ||
69 | + | ||
70 | +.inputosaurus-container .inputosaurus-input input:hover { | ||
71 | + -webkit-box-shadow: none; | ||
72 | + -moz-box-shadow: none; | ||
73 | + box-shadow: none; | ||
74 | + background-color: #f0f0f0; | ||
75 | +} | ||
76 | +.inputosaurus-input-hidden { display: none; } |
@@ -0,0 +1,506 @@ | @@ -0,0 +1,506 @@ | ||
1 | +/*** | ||
2 | +Spectrum Colorpicker v1.4.1 | ||
3 | +https://github.com/bgrins/spectrum | ||
4 | +Author: Brian Grinstead | ||
5 | +License: MIT | ||
6 | +***/ | ||
7 | + | ||
8 | +.sp-container { | ||
9 | + position:absolute; | ||
10 | + top:0; | ||
11 | + left:0; | ||
12 | + display:inline-block; | ||
13 | + *display: inline; | ||
14 | + *zoom: 1; | ||
15 | + /* https://github.com/bgrins/spectrum/issues/40 */ | ||
16 | + z-index: 9999994; | ||
17 | + overflow: hidden; | ||
18 | +} | ||
19 | +.sp-container.sp-flat { | ||
20 | + position: relative; | ||
21 | +} | ||
22 | + | ||
23 | +/* Fix for * { box-sizing: border-box; } */ | ||
24 | +.sp-container, | ||
25 | +.sp-container * { | ||
26 | + -webkit-box-sizing: content-box; | ||
27 | + -moz-box-sizing: content-box; | ||
28 | + box-sizing: content-box; | ||
29 | +} | ||
30 | + | ||
31 | +/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */ | ||
32 | +.sp-top { | ||
33 | + position:relative; | ||
34 | + width: 100%; | ||
35 | + display:inline-block; | ||
36 | +} | ||
37 | +.sp-top-inner { | ||
38 | + position:absolute; | ||
39 | + top:0; | ||
40 | + left:0; | ||
41 | + bottom:0; | ||
42 | + right:0; | ||
43 | +} | ||
44 | +.sp-color { | ||
45 | + position: absolute; | ||
46 | + top:0; | ||
47 | + left:0; | ||
48 | + bottom:0; | ||
49 | + right:20%; | ||
50 | +} | ||
51 | +.sp-hue { | ||
52 | + position: absolute; | ||
53 | + top:0; | ||
54 | + right:0; | ||
55 | + bottom:0; | ||
56 | + left:84%; | ||
57 | + height: 100%; | ||
58 | +} | ||
59 | + | ||
60 | +.sp-clear-enabled .sp-hue { | ||
61 | + top:33px; | ||
62 | + height: 77.5%; | ||
63 | +} | ||
64 | + | ||
65 | +.sp-fill { | ||
66 | + padding-top: 80%; | ||
67 | +} | ||
68 | +.sp-sat, .sp-val { | ||
69 | + position: absolute; | ||
70 | + top:0; | ||
71 | + left:0; | ||
72 | + right:0; | ||
73 | + bottom:0; | ||
74 | +} | ||
75 | + | ||
76 | +.sp-alpha-enabled .sp-top { | ||
77 | + margin-bottom: 18px; | ||
78 | +} | ||
79 | +.sp-alpha-enabled .sp-alpha { | ||
80 | + display: block; | ||
81 | +} | ||
82 | +.sp-alpha-handle { | ||
83 | + position:absolute; | ||
84 | + top:-4px; | ||
85 | + bottom: -4px; | ||
86 | + width: 6px; | ||
87 | + left: 50%; | ||
88 | + cursor: pointer; | ||
89 | + border: 1px solid black; | ||
90 | + background: white; | ||
91 | + opacity: .8; | ||
92 | +} | ||
93 | +.sp-alpha { | ||
94 | + display: none; | ||
95 | + position: absolute; | ||
96 | + bottom: -14px; | ||
97 | + right: 0; | ||
98 | + left: 0; | ||
99 | + height: 8px; | ||
100 | +} | ||
101 | +.sp-alpha-inner { | ||
102 | + border: solid 1px #333; | ||
103 | +} | ||
104 | + | ||
105 | +.sp-clear { | ||
106 | + display: none; | ||
107 | +} | ||
108 | + | ||
109 | +.sp-clear.sp-clear-display { | ||
110 | + background-position: center; | ||
111 | +} | ||
112 | + | ||
113 | +.sp-clear-enabled .sp-clear { | ||
114 | + display: block; | ||
115 | + position:absolute; | ||
116 | + top:0px; | ||
117 | + right:0; | ||
118 | + bottom:0; | ||
119 | + left:84%; | ||
120 | + height: 28px; | ||
121 | +} | ||
122 | + | ||
123 | +/* Don't allow text selection */ | ||
124 | +.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button { | ||
125 | + -webkit-user-select:none; | ||
126 | + -moz-user-select: -moz-none; | ||
127 | + -o-user-select:none; | ||
128 | + user-select: none; | ||
129 | +} | ||
130 | + | ||
131 | +.sp-container.sp-input-disabled .sp-input-container { | ||
132 | + display: none; | ||
133 | +} | ||
134 | +.sp-container.sp-buttons-disabled .sp-button-container { | ||
135 | + display: none; | ||
136 | +} | ||
137 | +.sp-container.sp-palette-buttons-disabled .sp-palette-button-container { | ||
138 | + display: none; | ||
139 | +} | ||
140 | +.sp-palette-only .sp-picker-container { | ||
141 | + display: none; | ||
142 | +} | ||
143 | +.sp-palette-disabled .sp-palette-container { | ||
144 | + display: none; | ||
145 | +} | ||
146 | + | ||
147 | +.sp-initial-disabled .sp-initial { | ||
148 | + display: none; | ||
149 | +} | ||
150 | + | ||
151 | + | ||
152 | +/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */ | ||
153 | +.sp-sat { | ||
154 | + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); | ||
155 | + background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); | ||
156 | + background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); | ||
157 | + background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); | ||
158 | + background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); | ||
159 | + background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0)); | ||
160 | + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)"; | ||
161 | + filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81'); | ||
162 | +} | ||
163 | +.sp-val { | ||
164 | + background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); | ||
165 | + background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); | ||
166 | + background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); | ||
167 | + background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); | ||
168 | + background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); | ||
169 | + background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0)); | ||
170 | + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)"; | ||
171 | + filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000'); | ||
172 | +} | ||
173 | + | ||
174 | +.sp-hue { | ||
175 | + background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); | ||
176 | + background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); | ||
177 | + background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); | ||
178 | + background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000)); | ||
179 | + background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); | ||
180 | +} | ||
181 | + | ||
182 | +/* IE filters do not support multiple color stops. | ||
183 | + Generate 6 divs, line them up, and do two color gradients for each. | ||
184 | + Yes, really. | ||
185 | + */ | ||
186 | +.sp-1 { | ||
187 | + height:17%; | ||
188 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00'); | ||
189 | +} | ||
190 | +.sp-2 { | ||
191 | + height:16%; | ||
192 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00'); | ||
193 | +} | ||
194 | +.sp-3 { | ||
195 | + height:17%; | ||
196 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff'); | ||
197 | +} | ||
198 | +.sp-4 { | ||
199 | + height:17%; | ||
200 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff'); | ||
201 | +} | ||
202 | +.sp-5 { | ||
203 | + height:16%; | ||
204 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff'); | ||
205 | +} | ||
206 | +.sp-6 { | ||
207 | + height:17%; | ||
208 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000'); | ||
209 | +} | ||
210 | + | ||
211 | +.sp-hidden { | ||
212 | + display: none !important; | ||
213 | +} | ||
214 | + | ||
215 | +/* Clearfix hack */ | ||
216 | +.sp-cf:before, .sp-cf:after { content: ""; display: table; } | ||
217 | +.sp-cf:after { clear: both; } | ||
218 | +.sp-cf { *zoom: 1; } | ||
219 | + | ||
220 | +/* Mobile devices, make hue slider bigger so it is easier to slide */ | ||
221 | +@media (max-device-width: 480px) { | ||
222 | + .sp-color { right: 40%; } | ||
223 | + .sp-hue { left: 63%; } | ||
224 | + .sp-fill { padding-top: 60%; } | ||
225 | +} | ||
226 | +.sp-dragger { | ||
227 | + border-radius: 5px; | ||
228 | + height: 5px; | ||
229 | + width: 5px; | ||
230 | + border: 1px solid #fff; | ||
231 | + background: #000; | ||
232 | + cursor: pointer; | ||
233 | + position:absolute; | ||
234 | + top:0; | ||
235 | + left: 0; | ||
236 | +} | ||
237 | +.sp-slider { | ||
238 | + position: absolute; | ||
239 | + top:0; | ||
240 | + cursor:pointer; | ||
241 | + height: 3px; | ||
242 | + left: -1px; | ||
243 | + right: -1px; | ||
244 | + border: 1px solid #000; | ||
245 | + background: white; | ||
246 | + opacity: .8; | ||
247 | +} | ||
248 | + | ||
249 | +/* | ||
250 | +Theme authors: | ||
251 | +Here are the basic themeable display options (colors, fonts, global widths). | ||
252 | +See http://bgrins.github.io/spectrum/themes/ for instructions. | ||
253 | +*/ | ||
254 | + | ||
255 | +.sp-container { | ||
256 | + border-radius: 0; | ||
257 | + background-color: #ECECEC; | ||
258 | + border: solid 1px #f0c49B; | ||
259 | + padding: 0; | ||
260 | +} | ||
261 | +.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear { | ||
262 | + font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; | ||
263 | + -webkit-box-sizing: border-box; | ||
264 | + -moz-box-sizing: border-box; | ||
265 | + -ms-box-sizing: border-box; | ||
266 | + box-sizing: border-box; | ||
267 | +} | ||
268 | +.sp-top { | ||
269 | + margin-bottom: 3px; | ||
270 | +} | ||
271 | +.sp-color, .sp-hue, .sp-clear { | ||
272 | + border: solid 1px #666; | ||
273 | +} | ||
274 | + | ||
275 | +/* Input */ | ||
276 | +.sp-input-container { | ||
277 | + float:right; | ||
278 | + width: 100px; | ||
279 | + margin-bottom: 4px; | ||
280 | +} | ||
281 | +.sp-initial-disabled .sp-input-container { | ||
282 | + width: 100%; | ||
283 | +} | ||
284 | +.sp-input { | ||
285 | + font-size: 12px !important; | ||
286 | + border: 1px inset; | ||
287 | + padding: 4px 5px; | ||
288 | + margin: 0; | ||
289 | + width: 100%; | ||
290 | + background:transparent; | ||
291 | + border-radius: 3px; | ||
292 | + color: #222; | ||
293 | +} | ||
294 | +.sp-input:focus { | ||
295 | + border: 1px solid orange; | ||
296 | +} | ||
297 | +.sp-input.sp-validation-error { | ||
298 | + border: 1px solid red; | ||
299 | + background: #fdd; | ||
300 | +} | ||
301 | +.sp-picker-container , .sp-palette-container { | ||
302 | + float:left; | ||
303 | + position: relative; | ||
304 | + padding: 10px; | ||
305 | + padding-bottom: 300px; | ||
306 | + margin-bottom: -290px; | ||
307 | +} | ||
308 | +.sp-picker-container { | ||
309 | + width: 172px; | ||
310 | + border-left: solid 1px #fff; | ||
311 | +} | ||
312 | + | ||
313 | +/* Palettes */ | ||
314 | +.sp-palette-container { | ||
315 | + border-right: solid 1px #ccc; | ||
316 | +} | ||
317 | + | ||
318 | +.sp-palette-only .sp-palette-container { | ||
319 | + border: 0; | ||
320 | +} | ||
321 | + | ||
322 | +.sp-palette .sp-thumb-el { | ||
323 | + display: block; | ||
324 | + position:relative; | ||
325 | + float:left; | ||
326 | + width: 24px; | ||
327 | + height: 15px; | ||
328 | + margin: 3px; | ||
329 | + cursor: pointer; | ||
330 | + border:solid 2px transparent; | ||
331 | +} | ||
332 | +.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active { | ||
333 | + border-color: orange; | ||
334 | +} | ||
335 | +.sp-thumb-el { | ||
336 | + position:relative; | ||
337 | +} | ||
338 | + | ||
339 | +/* Initial */ | ||
340 | +.sp-initial { | ||
341 | + float: left; | ||
342 | + border: solid 1px #333; | ||
343 | +} | ||
344 | +.sp-initial span { | ||
345 | + width: 30px; | ||
346 | + height: 25px; | ||
347 | + border:none; | ||
348 | + display:block; | ||
349 | + float:left; | ||
350 | + margin:0; | ||
351 | +} | ||
352 | + | ||
353 | +.sp-initial .sp-clear-display { | ||
354 | + background-position: center; | ||
355 | +} | ||
356 | + | ||
357 | +/* Buttons */ | ||
358 | +.sp-palette-button-container, | ||
359 | +.sp-button-container { | ||
360 | + float: right; | ||
361 | +} | ||
362 | + | ||
363 | +/* Replacer (the little preview div that shows up instead of the <input>) */ | ||
364 | +.sp-replacer { | ||
365 | + margin:0; | ||
366 | + overflow:hidden; | ||
367 | + cursor:pointer; | ||
368 | + padding: 4px; | ||
369 | + display:inline-block; | ||
370 | + *zoom: 1; | ||
371 | + *display: inline; | ||
372 | + border: solid 1px #91765d; | ||
373 | + background: #eee; | ||
374 | + color: #333; | ||
375 | + vertical-align: middle; | ||
376 | +} | ||
377 | +.sp-replacer:hover, .sp-replacer.sp-active { | ||
378 | + border-color: #F0C49B; | ||
379 | + color: #111; | ||
380 | +} | ||
381 | +.sp-replacer.sp-disabled { | ||
382 | + cursor:default; | ||
383 | + border-color: silver; | ||
384 | + color: silver; | ||
385 | +} | ||
386 | +.sp-dd { | ||
387 | + padding: 2px 0; | ||
388 | + height: 16px; | ||
389 | + line-height: 16px; | ||
390 | + float:left; | ||
391 | + font-size:10px; | ||
392 | +} | ||
393 | +.sp-preview { | ||
394 | + position:relative; | ||
395 | + width:25px; | ||
396 | + height: 20px; | ||
397 | + border: solid 1px #222; | ||
398 | + margin-right: 5px; | ||
399 | + float:left; | ||
400 | + z-index: 0; | ||
401 | +} | ||
402 | + | ||
403 | +.sp-palette { | ||
404 | + *width: 220px; | ||
405 | + max-width: 220px; | ||
406 | +} | ||
407 | +.sp-palette .sp-thumb-el { | ||
408 | + width:16px; | ||
409 | + height: 16px; | ||
410 | + margin:2px 1px; | ||
411 | + border: solid 1px #d0d0d0; | ||
412 | +} | ||
413 | + | ||
414 | +.sp-container { | ||
415 | + padding-bottom:0; | ||
416 | +} | ||
417 | + | ||
418 | + | ||
419 | +/* Buttons: http://hellohappy.org/css3-buttons/ */ | ||
420 | +.sp-container button { | ||
421 | + background-color: #eeeeee; | ||
422 | + background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); | ||
423 | + background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); | ||
424 | + background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); | ||
425 | + background-image: -o-linear-gradient(top, #eeeeee, #cccccc); | ||
426 | + background-image: linear-gradient(to bottom, #eeeeee, #cccccc); | ||
427 | + border: 1px solid #ccc; | ||
428 | + border-bottom: 1px solid #bbb; | ||
429 | + border-radius: 3px; | ||
430 | + color: #333; | ||
431 | + font-size: 14px; | ||
432 | + line-height: 1; | ||
433 | + padding: 5px 4px; | ||
434 | + text-align: center; | ||
435 | + text-shadow: 0 1px 0 #eee; | ||
436 | + vertical-align: middle; | ||
437 | +} | ||
438 | +.sp-container button:hover { | ||
439 | + background-color: #dddddd; | ||
440 | + background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); | ||
441 | + background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); | ||
442 | + background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); | ||
443 | + background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); | ||
444 | + background-image: linear-gradient(to bottom, #dddddd, #bbbbbb); | ||
445 | + border: 1px solid #bbb; | ||
446 | + border-bottom: 1px solid #999; | ||
447 | + cursor: pointer; | ||
448 | + text-shadow: 0 1px 0 #ddd; | ||
449 | +} | ||
450 | +.sp-container button:active { | ||
451 | + border: 1px solid #aaa; | ||
452 | + border-bottom: 1px solid #888; | ||
453 | + -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | ||
454 | + -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | ||
455 | + -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | ||
456 | + -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | ||
457 | + box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | ||
458 | +} | ||
459 | +.sp-cancel { | ||
460 | + font-size: 11px; | ||
461 | + color: #d93f3f !important; | ||
462 | + margin:0; | ||
463 | + padding:2px; | ||
464 | + margin-right: 5px; | ||
465 | + vertical-align: middle; | ||
466 | + text-decoration:none; | ||
467 | + | ||
468 | +} | ||
469 | +.sp-cancel:hover { | ||
470 | + color: #d93f3f !important; | ||
471 | + text-decoration: underline; | ||
472 | +} | ||
473 | + | ||
474 | + | ||
475 | +.sp-palette span:hover, .sp-palette span.sp-thumb-active { | ||
476 | + border-color: #000; | ||
477 | +} | ||
478 | + | ||
479 | +.sp-preview, .sp-alpha, .sp-thumb-el { | ||
480 | + position:relative; | ||
481 | + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); | ||
482 | +} | ||
483 | +.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner { | ||
484 | + display:block; | ||
485 | + position:absolute; | ||
486 | + top:0;left:0;bottom:0;right:0; | ||
487 | +} | ||
488 | + | ||
489 | +.sp-palette .sp-thumb-inner { | ||
490 | + background-position: 50% 50%; | ||
491 | + background-repeat: no-repeat; | ||
492 | +} | ||
493 | + | ||
494 | +.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner { | ||
495 | + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=); | ||
496 | +} | ||
497 | + | ||
498 | +.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner { | ||
499 | + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=); | ||
500 | +} | ||
501 | + | ||
502 | +.sp-clear-display { | ||
503 | + background-repeat:no-repeat; | ||
504 | + background-position: center; | ||
505 | + background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==); | ||
506 | +} |
script/noosfero-plugins
@@ -77,8 +77,7 @@ run(){ | @@ -77,8 +77,7 @@ run(){ | ||
77 | 77 | ||
78 | _enable(){ | 78 | _enable(){ |
79 | plugin="$1" | 79 | plugin="$1" |
80 | - cd $enabled_plugins_dir | ||
81 | - source="../../plugins/$plugin" | 80 | + source="$available_plugins_dir/$plugin" |
82 | target="$enabled_plugins_dir/$plugin" | 81 | target="$enabled_plugins_dir/$plugin" |
83 | base="$base_plugins_dir/$plugin" | 82 | base="$base_plugins_dir/$plugin" |
84 | run "$source/before_enable.rb" | 83 | run "$source/before_enable.rb" |
@@ -101,15 +100,11 @@ _enable(){ | @@ -101,15 +100,11 @@ _enable(){ | ||
101 | fi | 100 | fi |
102 | fi | 101 | fi |
103 | if [ "$installation_ok" = true ] && [ "$dependencies_ok" = true ]; then | 102 | if [ "$installation_ok" = true ] && [ "$dependencies_ok" = true ]; then |
104 | - ln -s "$source" "$plugin" | 103 | + ln -s "$source" "$target" |
105 | plugins_public_dir="$NOOSFERO_DIR/public/plugins" | 104 | plugins_public_dir="$NOOSFERO_DIR/public/plugins" |
106 | plugins_features_dir="$NOOSFERO_DIR/features/plugins" | 105 | plugins_features_dir="$NOOSFERO_DIR/features/plugins" |
107 | - cd $plugins_public_dir | ||
108 | - test -d "$source/public" && ln -s "$source/public" "$plugin" | ||
109 | - if [ -d "$NOOSFERO_DIR/features" ]; then | ||
110 | - cd $plugins_features_dir | ||
111 | - test -d "$source/features" && ln -s "$source/features" "$plugin" | ||
112 | - fi | 106 | + test -d "$target/public" && ln -s "$target/public" "$plugins_public_dir/$plugin" |
107 | + test -d "$NOOSFERO_DIR/features" && test -d "$target/features" && ln -s "$target/features" "$plugins_features_dir/$plugin" | ||
113 | _say "$plugin enabled" | 108 | _say "$plugin enabled" |
114 | run "$source/after_enable.rb" | 109 | run "$source/after_enable.rb" |
115 | needs_migrate=true | 110 | needs_migrate=true |
script/quick-start
@@ -85,6 +85,7 @@ fi | @@ -85,6 +85,7 @@ fi | ||
85 | run rake db:schema:load | 85 | run rake db:schema:load |
86 | run rake db:data:minimal | 86 | run rake db:data:minimal |
87 | run rake db:test:prepare | 87 | run rake db:test:prepare |
88 | +rails runner 'Environment.default.enable("skip_new_user_email_confirmation")' | ||
88 | 89 | ||
89 | # FIXME compile translations depends on ruby-gettext-rails, please see debian/control | 90 | # FIXME compile translations depends on ruby-gettext-rails, please see debian/control |
90 | # run rake noosfero:translations:compile | 91 | # run rake noosfero:translations:compile |
test/functional/application_controller_test.rb
@@ -166,7 +166,7 @@ class ApplicationControllerTest < ActionController::TestCase | @@ -166,7 +166,7 @@ class ApplicationControllerTest < ActionController::TestCase | ||
166 | 166 | ||
167 | should 'display only some categories in menu' do | 167 | should 'display only some categories in menu' do |
168 | @controller.stubs(:get_layout).returns('application') | 168 | @controller.stubs(:get_layout).returns('application') |
169 | - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true ) | 169 | + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true ) |
170 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true ) | 170 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true ) |
171 | get :index | 171 | get :index |
172 | assert_tag :tag => 'a', :content => /Category 2/ | 172 | assert_tag :tag => 'a', :content => /Category 2/ |
@@ -174,7 +174,7 @@ class ApplicationControllerTest < ActionController::TestCase | @@ -174,7 +174,7 @@ class ApplicationControllerTest < ActionController::TestCase | ||
174 | 174 | ||
175 | should 'not display some categories in menu' do | 175 | should 'not display some categories in menu' do |
176 | @controller.stubs(:get_layout).returns('application') | 176 | @controller.stubs(:get_layout).returns('application') |
177 | - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true) | 177 | + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true) |
178 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1) | 178 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1) |
179 | get :index | 179 | get :index |
180 | assert_no_tag :tag => 'a', :content => /Category 2/ | 180 | assert_no_tag :tag => 'a', :content => /Category 2/ |
@@ -239,7 +239,7 @@ class ApplicationControllerTest < ActionController::TestCase | @@ -239,7 +239,7 @@ class ApplicationControllerTest < ActionController::TestCase | ||
239 | 239 | ||
240 | should 'not display categories menu if categories feature disabled' do | 240 | should 'not display categories menu if categories feature disabled' do |
241 | Environment.any_instance.stubs(:enabled?).with(anything).returns(true) | 241 | Environment.any_instance.stubs(:enabled?).with(anything).returns(true) |
242 | - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true ) | 242 | + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true ) |
243 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true ) | 243 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true ) |
244 | get :index | 244 | get :index |
245 | assert_no_tag :tag => 'a', :content => /Category 2/ | 245 | assert_no_tag :tag => 'a', :content => /Category 2/ |
@@ -579,4 +579,63 @@ class ApplicationControllerTest < ActionController::TestCase | @@ -579,4 +579,63 @@ class ApplicationControllerTest < ActionController::TestCase | ||
579 | 579 | ||
580 | assert_equal ['a', 'b', 'c'], controller.send(:find_suggestions, 'random', Environment.default, 'random') | 580 | assert_equal ['a', 'b', 'c'], controller.send(:find_suggestions, 'random', Environment.default, 'random') |
581 | end | 581 | end |
582 | + | ||
583 | + should 'redirect to login if environment is restrict to members' do | ||
584 | + Environment.default.enable(:restrict_to_members) | ||
585 | + get :index | ||
586 | + assert_redirected_to :controller => 'account', :action => 'login' | ||
587 | + end | ||
588 | + | ||
589 | + should 'do not allow member not included in whitelist to access an restricted environment' do | ||
590 | + user = create_user | ||
591 | + e = Environment.default | ||
592 | + e.enable(:restrict_to_members) | ||
593 | + e.members_whitelist_enabled = true | ||
594 | + e.save! | ||
595 | + login_as(user.login) | ||
596 | + get :index | ||
597 | + assert_response :forbidden | ||
598 | + end | ||
599 | + | ||
600 | + should 'allow member in whitelist to access an environment' do | ||
601 | + user = create_user | ||
602 | + e = Environment.default | ||
603 | + e.members_whitelist_enabled = true | ||
604 | + e.members_whitelist = "#{user.person.id}" | ||
605 | + e.save! | ||
606 | + login_as(user.login) | ||
607 | + get :index | ||
608 | + assert_response :success | ||
609 | + end | ||
610 | + | ||
611 | + should 'allow members to access an environment if whitelist is disabled' do | ||
612 | + user = create_user | ||
613 | + e = Environment.default | ||
614 | + e.members_whitelist_enabled = false | ||
615 | + e.save! | ||
616 | + login_as(user.login) | ||
617 | + get :index | ||
618 | + assert_response :success | ||
619 | + end | ||
620 | + | ||
621 | + should 'allow admin to access an environment if whitelist is enabled' do | ||
622 | + e = Environment.default | ||
623 | + e.members_whitelist_enabled = true | ||
624 | + e.save! | ||
625 | + login_as(create_admin_user(e)) | ||
626 | + get :index | ||
627 | + assert_response :success | ||
628 | + end | ||
629 | + | ||
630 | + should 'not check whitelist members if the environment is not restrict to members' do | ||
631 | + e = Environment.default | ||
632 | + e.disable(:restrict_to_members) | ||
633 | + e.members_whitelist_enabled = true | ||
634 | + e.save! | ||
635 | + @controller.expects(:verify_members_whitelist).never | ||
636 | + login_as create_user.login | ||
637 | + get :index | ||
638 | + assert_response :success | ||
639 | + end | ||
640 | + | ||
582 | end | 641 | end |
test/functional/categories_controller_test.rb
@@ -41,7 +41,7 @@ class CategoriesControllerTest < ActionController::TestCase | @@ -41,7 +41,7 @@ class CategoriesControllerTest < ActionController::TestCase | ||
41 | end | 41 | end |
42 | 42 | ||
43 | def test_edit_save | 43 | def test_edit_save |
44 | - post :edit, :id => cat1.id, :category => { :name => 'new name for category' } | 44 | + post :edit, :id => cat1.id, :category => { :name => 'new name for category', :display_color => nil } |
45 | assert_redirected_to :action => 'index' | 45 | assert_redirected_to :action => 'index' |
46 | assert_equal 'new name for category', Category.find(cat1.id).name | 46 | assert_equal 'new name for category', Category.find(cat1.id).name |
47 | end | 47 | end |
@@ -134,7 +134,7 @@ class CategoriesControllerTest < ActionController::TestCase | @@ -134,7 +134,7 @@ class CategoriesControllerTest < ActionController::TestCase | ||
134 | env.save! | 134 | env.save! |
135 | get :new | 135 | get :new |
136 | 136 | ||
137 | - assert_no_tag :tag => 'select', :attributes => { :name => "category[display_color]" } | 137 | + assert_no_tag :tag => 'input', :attributes => { :name => "category[display_color]" } |
138 | end | 138 | end |
139 | 139 | ||
140 | should 'display color selection if environment.categories_menu is true' do | 140 | should 'display color selection if environment.categories_menu is true' do |
@@ -142,7 +142,7 @@ class CategoriesControllerTest < ActionController::TestCase | @@ -142,7 +142,7 @@ class CategoriesControllerTest < ActionController::TestCase | ||
142 | env.save! | 142 | env.save! |
143 | get :new | 143 | get :new |
144 | 144 | ||
145 | - assert_tag :tag => 'select', :attributes => { :name => "category[display_color]" } | 145 | + assert_tag :tag => 'input', :attributes => { :name => "category[display_color]" } |
146 | end | 146 | end |
147 | 147 | ||
148 | should 'not list regions and product categories' do | 148 | should 'not list regions and product categories' do |
test/functional/cms_controller_test.rb
@@ -1807,6 +1807,23 @@ class CmsControllerTest < ActionController::TestCase | @@ -1807,6 +1807,23 @@ class CmsControllerTest < ActionController::TestCase | ||
1807 | assert_template 'cms/publish' | 1807 | assert_template 'cms/publish' |
1808 | end | 1808 | end |
1809 | 1809 | ||
1810 | + should 'response of search_tags be json' do | ||
1811 | + get :search_tags, :profile => profile.identifier, :term => 'linux' | ||
1812 | + assert_equal 'application/json', @response.content_type | ||
1813 | + end | ||
1814 | + | ||
1815 | + should 'return empty json if does not find tag' do | ||
1816 | + get :search_tags, :profile => profile.identifier, :term => 'linux' | ||
1817 | + assert_equal "[]", @response.body | ||
1818 | + end | ||
1819 | + | ||
1820 | + should 'return tags found' do | ||
1821 | + tag = mock; tag.stubs(:name).returns('linux') | ||
1822 | + ActsAsTaggableOn::Tag.stubs(:find).returns([tag]) | ||
1823 | + get :search_tags, :profile => profile.identifier, :term => 'linux' | ||
1824 | + assert_equal '[{"label":"linux","value":"linux"}]', @response.body | ||
1825 | + end | ||
1826 | + | ||
1810 | protected | 1827 | protected |
1811 | 1828 | ||
1812 | # FIXME this is to avoid adding an extra dependency for a proper JSON parser. | 1829 | # FIXME this is to avoid adding an extra dependency for a proper JSON parser. |
test/functional/environment_design_controller_test.rb
@@ -6,9 +6,7 @@ class EnvironmentDesignController; def rescue_action(e) raise e end; end | @@ -6,9 +6,7 @@ class EnvironmentDesignController; def rescue_action(e) raise e end; end | ||
6 | 6 | ||
7 | class EnvironmentDesignControllerTest < ActionController::TestCase | 7 | class EnvironmentDesignControllerTest < ActionController::TestCase |
8 | 8 | ||
9 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
10 | - # the Noosfero core soon, see ActionItem3045 | ||
11 | - ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ] | 9 | + ALL_BLOCKS = [ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ] |
12 | 10 | ||
13 | def setup | 11 | def setup |
14 | @controller = EnvironmentDesignController.new | 12 | @controller = EnvironmentDesignController.new |
@@ -77,18 +75,6 @@ class EnvironmentDesignControllerTest < ActionController::TestCase | @@ -77,18 +75,6 @@ class EnvironmentDesignControllerTest < ActionController::TestCase | ||
77 | assert_tag :tag => 'p', :attributes => { :id => 'no_portal_community' } | 75 | assert_tag :tag => 'p', :attributes => { :id => 'no_portal_community' } |
78 | end | 76 | end |
79 | 77 | ||
80 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
81 | - # the Noosfero core soon, see ActionItem3045 | ||
82 | - should 'be able to edit EnvironmentStatisticsBlock' do | ||
83 | - login_as(create_admin_user(Environment.default)) | ||
84 | - b = EnvironmentStatisticsBlock.create! | ||
85 | - e = Environment.default | ||
86 | - e.boxes.create! | ||
87 | - e.boxes.first.blocks << b | ||
88 | - get :edit, :id => b.id | ||
89 | - assert_tag :tag => 'input', :attributes => { :id => 'block_title' } | ||
90 | - end | ||
91 | - | ||
92 | should 'be able to edit EnterprisesBlock' do | 78 | should 'be able to edit EnterprisesBlock' do |
93 | login_as(create_admin_user(Environment.default)) | 79 | login_as(create_admin_user(Environment.default)) |
94 | b = EnterprisesBlock.create! | 80 | b = EnterprisesBlock.create! |
test/functional/features_controller_test.rb
@@ -146,4 +146,20 @@ class FeaturesControllerTest < ActionController::TestCase | @@ -146,4 +146,20 @@ class FeaturesControllerTest < ActionController::TestCase | ||
146 | assert_equal true, e.custom_community_fields['contact_person']['required'] | 146 | assert_equal true, e.custom_community_fields['contact_person']['required'] |
147 | end | 147 | end |
148 | 148 | ||
149 | + should 'search members by name' do | ||
150 | + uses_host 'anhetegua.net' | ||
151 | + person = fast_create(Person, :environment_id => Environment.find(2).id) | ||
152 | + xhr :get, :search_members, :q => person.name[0..2] | ||
153 | + json_response = ActiveSupport::JSON.decode(@response.body) | ||
154 | + assert_includes json_response, {"id"=>person.id, "name"=>person.name} | ||
155 | + end | ||
156 | + | ||
157 | + should 'search members by identifier' do | ||
158 | + uses_host 'anhetegua.net' | ||
159 | + person = fast_create(Person, :name => 'Some Name', :identifier => 'person-identifier', :environment_id => Environment.find(2).id) | ||
160 | + xhr :get, :search_members, :q => person.identifier | ||
161 | + json_response = ActiveSupport::JSON.decode(@response.body) | ||
162 | + assert_includes json_response, {"id"=>person.id, "name"=>person.name} | ||
163 | + end | ||
164 | + | ||
149 | end | 165 | end |
test/unit/box_test.rb
@@ -33,9 +33,6 @@ class BoxTest < ActiveSupport::TestCase | @@ -33,9 +33,6 @@ class BoxTest < ActiveSupport::TestCase | ||
33 | assert blocks.include?('categories-block') | 33 | assert blocks.include?('categories-block') |
34 | assert blocks.include?('communities-block') | 34 | assert blocks.include?('communities-block') |
35 | assert blocks.include?('enterprises-block') | 35 | assert blocks.include?('enterprises-block') |
36 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
37 | - # the Noosfero core soon, see ActionItem3045 | ||
38 | - assert blocks.include?('environment-statistics-block') | ||
39 | assert blocks.include?('fans-block') | 36 | assert blocks.include?('fans-block') |
40 | assert blocks.include?('favorite-enterprises-block') | 37 | assert blocks.include?('favorite-enterprises-block') |
41 | assert blocks.include?('feed-reader-block') | 38 | assert blocks.include?('feed-reader-block') |
@@ -64,9 +61,6 @@ class BoxTest < ActiveSupport::TestCase | @@ -64,9 +61,6 @@ class BoxTest < ActiveSupport::TestCase | ||
64 | assert blocks.include?('communities-block') | 61 | assert blocks.include?('communities-block') |
65 | assert blocks.include?('disabled-enterprise-message-block') | 62 | assert blocks.include?('disabled-enterprise-message-block') |
66 | assert blocks.include?('enterprises-block') | 63 | assert blocks.include?('enterprises-block') |
67 | - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
68 | - # the Noosfero core soon, see ActionItem3045 | ||
69 | - assert blocks.include?('environment-statistics-block') | ||
70 | assert blocks.include?('fans-block') | 64 | assert blocks.include?('fans-block') |
71 | assert blocks.include?('favorite-enterprises-block') | 65 | assert blocks.include?('favorite-enterprises-block') |
72 | assert blocks.include?('featured-products-block') | 66 | assert blocks.include?('featured-products-block') |
test/unit/categories_helper_test.rb
@@ -15,8 +15,26 @@ class CategoriesHelperTest < ActiveSupport::TestCase | @@ -15,8 +15,26 @@ class CategoriesHelperTest < ActiveSupport::TestCase | ||
15 | expects(:options_for_select).with([['General Category', 'Category'],[ 'Product Category', 'ProductCategory'],[ 'Region', 'Region' ]], 'fieldvalue').returns('OPTIONS') | 15 | expects(:options_for_select).with([['General Category', 'Category'],[ 'Product Category', 'ProductCategory'],[ 'Region', 'Region' ]], 'fieldvalue').returns('OPTIONS') |
16 | expects(:select_tag).with('type', 'OPTIONS').returns('TAG') | 16 | expects(:select_tag).with('type', 'OPTIONS').returns('TAG') |
17 | expects(:labelled_form_field).with(anything, 'TAG').returns('RESULT') | 17 | expects(:labelled_form_field).with(anything, 'TAG').returns('RESULT') |
18 | - | 18 | + |
19 | assert_equal 'RESULT', select_category_type('fieldname') | 19 | assert_equal 'RESULT', select_category_type('fieldname') |
20 | end | 20 | end |
21 | 21 | ||
22 | + should 'return category color if its defined' do | ||
23 | + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb') | ||
24 | + assert_equal 'background-color: #fbfbfb;', category_color_style(category1) | ||
25 | + end | ||
26 | + | ||
27 | + should 'not return category parent color if category color is not defined' do | ||
28 | + e = fast_create(Environment) | ||
29 | + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb', :environment_id => e.id) | ||
30 | + category2 = fast_create(Category, :name => 'education', :display_color => nil, :parent_id => category1.id, :environment_id => e.id) | ||
31 | + assert_equal '', category_color_style(category2) | ||
32 | + end | ||
33 | + | ||
34 | + should 'not return category parent color if category is nil' do | ||
35 | + assert_nothing_raised do | ||
36 | + assert_equal '', category_color_style(nil) | ||
37 | + end | ||
38 | + end | ||
39 | + | ||
22 | end | 40 | end |
test/unit/category_test.rb
@@ -159,36 +159,6 @@ class CategoryTest < ActiveSupport::TestCase | @@ -159,36 +159,6 @@ class CategoryTest < ActiveSupport::TestCase | ||
159 | 159 | ||
160 | end | 160 | end |
161 | 161 | ||
162 | - should "limit the possibile display colors" do | ||
163 | - c = build(Category, :name => 'test category', :environment_id => @env.id) | ||
164 | - | ||
165 | - c.display_color = 16 | ||
166 | - c.valid? | ||
167 | - assert c.errors[:display_color.to_s].present? | ||
168 | - | ||
169 | - valid = (1..15).map { |item| item.to_i } | ||
170 | - valid.each do |item| | ||
171 | - c.display_color = item | ||
172 | - c.valid? | ||
173 | - assert !c.errors[:display_color.to_s].present? | ||
174 | - end | ||
175 | - | ||
176 | - end | ||
177 | - | ||
178 | - should 'avoid duplicated display colors' do | ||
179 | - c1 = fast_create(Category, :name => 'test category', :environment_id => @env.id, :display_color => 1) | ||
180 | - | ||
181 | - c = build(Category, :name => 'lalala', :environment_id => @env.id) | ||
182 | - c.display_color = 1 | ||
183 | - assert !c.valid? | ||
184 | - assert c.errors[:display_color.to_s].present? | ||
185 | - | ||
186 | - c.display_color = 2 | ||
187 | - c.valid? | ||
188 | - assert !c.errors[:display_color.to_s].present? | ||
189 | - | ||
190 | - end | ||
191 | - | ||
192 | should 'be able to get top ancestor' do | 162 | should 'be able to get top ancestor' do |
193 | c1 = fast_create(Category, :name => 'test category', :environment_id => @env.id) | 163 | c1 = fast_create(Category, :name => 'test category', :environment_id => @env.id) |
194 | c2 = fast_create(Category, :name => 'test category', :environment_id => @env.id, :parent_id => c1.id) | 164 | c2 = fast_create(Category, :name => 'test category', :environment_id => @env.id, :parent_id => c1.id) |
@@ -535,4 +505,28 @@ class CategoryTest < ActiveSupport::TestCase | @@ -535,4 +505,28 @@ class CategoryTest < ActiveSupport::TestCase | ||
535 | assert_includes Category.on_level(parent.id), category | 505 | assert_includes Category.on_level(parent.id), category |
536 | end | 506 | end |
537 | 507 | ||
508 | + should 'return self if the category has display_color defined' do | ||
509 | + c1 = fast_create(Category) | ||
510 | + c2 = fast_create(Category, :parent_id => c1) | ||
511 | + c3 = fast_create(Category, :parent_id => c2, :display_color => 'FFFFFF') | ||
512 | + c4 = fast_create(Category, :parent_id => c3, :display_color => '000000') | ||
513 | + assert_equal c4, c4.with_color | ||
514 | + end | ||
515 | + | ||
516 | + should 'return first category on hierarchy with display_color defined' do | ||
517 | + c1 = fast_create(Category, :display_color => '111111') | ||
518 | + c2 = fast_create(Category, :parent_id => c1) | ||
519 | + c3 = fast_create(Category, :parent_id => c2) | ||
520 | + c4 = fast_create(Category, :parent_id => c3) | ||
521 | + assert_equal c1, c4.with_color | ||
522 | + end | ||
523 | + | ||
524 | + should 'return nil if no category on hierarchy has display_color defined' do | ||
525 | + c1 = fast_create(Category) | ||
526 | + c2 = fast_create(Category, :parent_id => c1) | ||
527 | + c3 = fast_create(Category, :parent_id => c2) | ||
528 | + c4 = fast_create(Category, :parent_id => c3) | ||
529 | + assert_equal nil, c4.with_color | ||
530 | + end | ||
531 | + | ||
538 | end | 532 | end |
test/unit/environment_statistics_block_test.rb
@@ -1,99 +0,0 @@ | @@ -1,99 +0,0 @@ | ||
1 | -# TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from | ||
2 | -# the Noosfero core soon, see ActionItem3045 | ||
3 | - | ||
4 | -require File.dirname(__FILE__) + '/../test_helper' | ||
5 | - | ||
6 | -class EnvironmentStatisticsBlockTest < ActiveSupport::TestCase | ||
7 | - | ||
8 | - should 'inherit from Block' do | ||
9 | - assert_kind_of Block, EnvironmentStatisticsBlock.new | ||
10 | - end | ||
11 | - | ||
12 | - should 'describe itself' do | ||
13 | - assert_not_equal Block.description, EnvironmentStatisticsBlock.description | ||
14 | - end | ||
15 | - | ||
16 | - should 'provide a default title' do | ||
17 | - owner = mock | ||
18 | - owner.expects(:name).returns('my environment') | ||
19 | - | ||
20 | - block = EnvironmentStatisticsBlock.new | ||
21 | - block.expects(:owner).returns(owner) | ||
22 | - assert_equal 'Statistics for my environment', block.title | ||
23 | - end | ||
24 | - | ||
25 | - should 'generate statistics' do | ||
26 | - env = create(Environment) | ||
27 | - user1 = create_user('testuser1', :environment_id => env.id) | ||
28 | - user2 = create_user('testuser2', :environment_id => env.id) | ||
29 | - | ||
30 | - fast_create(Enterprise, :environment_id => env.id) | ||
31 | - fast_create(Community, :environment_id => env.id) | ||
32 | - | ||
33 | - block = EnvironmentStatisticsBlock.new | ||
34 | - env.boxes.first.blocks << block | ||
35 | - | ||
36 | - content = block.content | ||
37 | - | ||
38 | - assert_match(/One enterprise/, content) | ||
39 | - assert_match(/2 users/, content) | ||
40 | - assert_match(/One community/, content) | ||
41 | - end | ||
42 | - | ||
43 | - should 'generate statistics including private profiles' do | ||
44 | - env = create(Environment) | ||
45 | - user1 = create_user('testuser1', :environment_id => env.id) | ||
46 | - user2 = create_user('testuser2', :environment_id => env.id) | ||
47 | - user3 = create_user('testuser3', :environment_id => env.id) | ||
48 | - p = user3.person; p.public_profile = false; p.save! | ||
49 | - | ||
50 | - fast_create(Enterprise, :environment_id => env.id) | ||
51 | - fast_create(Enterprise, :environment_id => env.id, :public_profile => false) | ||
52 | - | ||
53 | - fast_create(Community, :environment_id => env.id) | ||
54 | - fast_create(Community, :environment_id => env.id, :public_profile => false) | ||
55 | - | ||
56 | - block = EnvironmentStatisticsBlock.new | ||
57 | - env.boxes.first.blocks << block | ||
58 | - | ||
59 | - content = block.content | ||
60 | - | ||
61 | - assert_match /2 enterprises/, content | ||
62 | - assert_match /3 users/, content | ||
63 | - assert_match /2 communities/, content | ||
64 | - end | ||
65 | - | ||
66 | - should 'generate statistics but not for not visible profiles' do | ||
67 | - env = create(Environment) | ||
68 | - user1 = create_user('testuser1', :environment_id => env.id) | ||
69 | - user2 = create_user('testuser2', :environment_id => env.id) | ||
70 | - user3 = create_user('testuser3', :environment_id => env.id) | ||
71 | - p = user3.person; p.visible = false; p.save! | ||
72 | - | ||
73 | - fast_create(Enterprise, :environment_id => env.id) | ||
74 | - fast_create(Enterprise, :environment_id => env.id, :visible => false) | ||
75 | - | ||
76 | - fast_create(Community, :environment_id => env.id) | ||
77 | - fast_create(Community, :environment_id => env.id, :visible => false) | ||
78 | - | ||
79 | - block = EnvironmentStatisticsBlock.new | ||
80 | - env.boxes.first.blocks << block | ||
81 | - | ||
82 | - content = block.content | ||
83 | - | ||
84 | - assert_match /One enterprise/, content | ||
85 | - assert_match /2 users/, content | ||
86 | - assert_match /One community/, content | ||
87 | - end | ||
88 | - | ||
89 | - should 'not display enterprises if disabled' do | ||
90 | - env = fast_create(Environment) | ||
91 | - env.enable('disable_asset_enterprises', false) | ||
92 | - | ||
93 | - block = EnvironmentStatisticsBlock.new | ||
94 | - block.stubs(:owner).returns(env) | ||
95 | - | ||
96 | - assert_no_match /enterprises/i, block.content | ||
97 | - end | ||
98 | - | ||
99 | -end |
test/unit/environment_test.rb
@@ -156,7 +156,7 @@ class EnvironmentTest < ActiveSupport::TestCase | @@ -156,7 +156,7 @@ class EnvironmentTest < ActiveSupport::TestCase | ||
156 | 156 | ||
157 | should 'list displayable categories' do | 157 | should 'list displayable categories' do |
158 | env = fast_create(Environment) | 158 | env = fast_create(Environment) |
159 | - cat1 = create(Category, :environment => env, :name => 'category one', :display_color => 1) | 159 | + cat1 = create(Category, :environment => env, :name => 'category one', :display_color => 'ffa500') |
160 | assert ! cat1.new_record? | 160 | assert ! cat1.new_record? |
161 | 161 | ||
162 | # subcategories should be ignored | 162 | # subcategories should be ignored |
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +# encoding: UTF-8 | ||
2 | +require File.dirname(__FILE__) + '/../test_helper' | ||
3 | + | ||
4 | +class ModerateUserRegistrationTest < ActiveSupport::TestCase | ||
5 | + fixtures :users, :environments | ||
6 | + | ||
7 | + def test_should_on_perform_activate_user | ||
8 | + user = User.new(:login => 'lalala', :email => 'lalala@example.com', :password => 'test', :password_confirmation => 'test') | ||
9 | + user.save! | ||
10 | + environment = Environment.default | ||
11 | + t= ModerateUserRegistration.new | ||
12 | + t.user_id = user.id | ||
13 | + t.name = user.name | ||
14 | + t.author_name = user.name | ||
15 | + t.email = user.email | ||
16 | + t.target= environment | ||
17 | + t.save! | ||
18 | + assert !user.activated? | ||
19 | + t.perform | ||
20 | + assert environment.users.find_by_id(user.id).activated? | ||
21 | + end | ||
22 | +end |
vendor/plugins/delayed_job/lib/delayed/command.rb
@@ -97,7 +97,7 @@ module Delayed | @@ -97,7 +97,7 @@ module Delayed | ||
97 | Dir.chdir(Rails.root) | 97 | Dir.chdir(Rails.root) |
98 | 98 | ||
99 | Delayed::Worker.after_fork | 99 | Delayed::Worker.after_fork |
100 | - Delayed::Worker.logger ||= Logger.new(File.join(Rails.root, 'log', 'delayed_job.log')) | 100 | + Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', "#{Rails.env}_delayed_job.log")) |
101 | 101 | ||
102 | worker = Delayed::Worker.new(@options) | 102 | worker = Delayed::Worker.new(@options) |
103 | worker.name_prefix = "#{worker_name} " | 103 | worker.name_prefix = "#{worker_name} " |