Commit c686ddb28d936095255c5792fcac75f271c43068
Exists in
master
and in
20 other branches
Merge branch 'noosfero' into rails4
Showing
183 changed files
with
5419 additions
and
573 deletions
Show diff stats
app/controllers/public/account_controller.rb
@@ -46,8 +46,12 @@ class AccountController < ApplicationController | @@ -46,8 +46,12 @@ class AccountController < ApplicationController | ||
46 | 46 | ||
47 | self.current_user = plugins_alternative_authentication | 47 | self.current_user = plugins_alternative_authentication |
48 | 48 | ||
49 | - self.current_user ||= User.authenticate(params[:user][:login], params[:user][:password], environment) if params[:user] | ||
50 | - | 49 | + begin |
50 | + self.current_user ||= User.authenticate(params[:user][:login], params[:user][:password], environment) if params[:user] | ||
51 | + rescue User::UserNotActivated => e | ||
52 | + session[:notice] = e.message | ||
53 | + return | ||
54 | + end | ||
51 | if logged_in? | 55 | if logged_in? |
52 | check_join_in_community(self.current_user) | 56 | check_join_in_community(self.current_user) |
53 | 57 |
app/controllers/public/search_controller.rb
@@ -92,10 +92,10 @@ class SearchController < PublicController | @@ -92,10 +92,10 @@ class SearchController < PublicController | ||
92 | 92 | ||
93 | def events | 93 | def events |
94 | if params[:year].blank? && params[:year].blank? && params[:day].blank? | 94 | if params[:year].blank? && params[:year].blank? && params[:day].blank? |
95 | - @date = Date.today | 95 | + @date = DateTime.now |
96 | else | 96 | else |
97 | - year = (params[:year] ? params[:year].to_i : Date.today.year) | ||
98 | - month = (params[:month] ? params[:month].to_i : Date.today.month) | 97 | + year = (params[:year] ? params[:year].to_i : DateTime.now.year) |
98 | + month = (params[:month] ? params[:month].to_i : DateTime.now.month) | ||
99 | day = (params[:day] ? params[:day].to_i : 1) | 99 | day = (params[:day] ? params[:day].to_i : 1) |
100 | @date = build_date(year, month, day) | 100 | @date = build_date(year, month, day) |
101 | end | 101 | end |
@@ -106,9 +106,7 @@ class SearchController < PublicController | @@ -106,9 +106,7 @@ class SearchController < PublicController | ||
106 | @events = @category ? | 106 | @events = @category ? |
107 | environment.events.by_day(@date).in_category(Category.find(@category_id)).paginate(:per_page => per_page, :page => params[:page]) : | 107 | environment.events.by_day(@date).in_category(Category.find(@category_id)).paginate(:per_page => per_page, :page => params[:page]) : |
108 | environment.events.by_day(@date).paginate(:per_page => per_page, :page => params[:page]) | 108 | environment.events.by_day(@date).paginate(:per_page => per_page, :page => params[:page]) |
109 | - end | ||
110 | - | ||
111 | - if params[:year] || params[:month] | 109 | + elsif params[:year] || params[:month] |
112 | @events = @category ? | 110 | @events = @category ? |
113 | environment.events.by_month(@date).in_category(Category.find(@category_id)).paginate(:per_page => per_page, :page => params[:page]) : | 111 | environment.events.by_month(@date).in_category(Category.find(@category_id)).paginate(:per_page => per_page, :page => params[:page]) : |
114 | environment.events.by_month(@date).paginate(:per_page => per_page, :page => params[:page]) | 112 | environment.events.by_month(@date).paginate(:per_page => per_page, :page => params[:page]) |
app/helpers/application_helper.rb
@@ -903,7 +903,7 @@ module ApplicationHelper | @@ -903,7 +903,7 @@ module ApplicationHelper | ||
903 | end | 903 | end |
904 | 904 | ||
905 | def base_url | 905 | def base_url |
906 | - environment.top_url(request.scheme) | 906 | + profile ? profile.top_url(request.scheme) : environment.top_url(request.scheme) |
907 | end | 907 | end |
908 | alias :top_url :base_url | 908 | alias :top_url :base_url |
909 | 909 |
app/helpers/blog_helper.rb
@@ -6,7 +6,13 @@ module BlogHelper | @@ -6,7 +6,13 @@ module BlogHelper | ||
6 | @article = article | 6 | @article = article |
7 | hidden_field_tag('article[published]', 1) + | 7 | hidden_field_tag('article[published]', 1) + |
8 | hidden_field_tag('article[accept_comments]', 0) + | 8 | hidden_field_tag('article[accept_comments]', 0) + |
9 | - visibility_options(article,tokenized_children) | 9 | + visibility_options(article,tokenized_children) + |
10 | + content_tag('h4', _('Visualization of posts')) + | ||
11 | + content_tag( | ||
12 | + 'div', | ||
13 | + check_box(:article, :display_preview) + | ||
14 | + content_tag('label', _('I want to display the preview of posts before the text'), :for => 'article_display_preview') | ||
15 | + ) | ||
10 | end | 16 | end |
11 | 17 | ||
12 | def cms_label_for_new_children | 18 | def cms_label_for_new_children |
app/helpers/content_viewer_helper.rb
@@ -51,7 +51,7 @@ module ContentViewerHelper | @@ -51,7 +51,7 @@ module ContentViewerHelper | ||
51 | elsif date_format == 'past_time' | 51 | elsif date_format == 'past_time' |
52 | left_time = true | 52 | left_time = true |
53 | end | 53 | end |
54 | - content_tag('span', show_date(article.published_at, use_numbers , year, left_time), :class => 'date') | 54 | + content_tag('span', show_time(article.published_at, use_numbers , year, left_time), :class => 'date') |
55 | end | 55 | end |
56 | 56 | ||
57 | def link_to_comments(article, args = {}) | 57 | def link_to_comments(article, args = {}) |
app/helpers/dates_helper.rb
@@ -43,9 +43,14 @@ module DatesHelper | @@ -43,9 +43,14 @@ module DatesHelper | ||
43 | end | 43 | end |
44 | 44 | ||
45 | # formats a datetime for displaying. | 45 | # formats a datetime for displaying. |
46 | - def show_time(time) | ||
47 | - if time | ||
48 | - _('%{day} %{month} %{year}, %{hour}:%{minutes}') % { :year => time.year, :month => month_name(time.month), :day => time.day, :hour => time.hour, :minutes => time.strftime("%M") } | 46 | + def show_time(time, use_numbers = false, year = true, left_time = false) |
47 | + if time && use_numbers | ||
48 | + _('%{month}/%{day}/%{year}, %{hour}:%{minutes}') % { :year => (year ? time.year : ''), :month => time.month, :day => time.day, :hour => time.hour, :minutes => time.strftime("%M") } | ||
49 | + elsif time && left_time | ||
50 | + date_format = time_ago_in_words(time) | ||
51 | + elsif time | ||
52 | + date_format = year ? _('%{month_name} %{day}, %{year} %{hour}:%{minutes}') : _('%{month_name} %{day} %{hour}:%{minutes}') | ||
53 | + date_format % { :day => time.day, :month_name => month_name(time.month), :year => time.year, :hour => time.hour, :minutes => time.strftime("%M") } | ||
49 | else | 54 | else |
50 | '' | 55 | '' |
51 | end | 56 | end |
@@ -53,7 +58,7 @@ module DatesHelper | @@ -53,7 +58,7 @@ module DatesHelper | ||
53 | 58 | ||
54 | def show_period(date1, date2 = nil, use_numbers = false) | 59 | def show_period(date1, date2 = nil, use_numbers = false) |
55 | if (date1 == date2) || (date2.nil?) | 60 | if (date1 == date2) || (date2.nil?) |
56 | - show_date(date1, use_numbers) | 61 | + show_time(date1, use_numbers) |
57 | else | 62 | else |
58 | if date1.year == date2.year | 63 | if date1.year == date2.year |
59 | if date1.month == date2.month | 64 | if date1.month == date2.month |
@@ -72,8 +77,8 @@ module DatesHelper | @@ -72,8 +77,8 @@ module DatesHelper | ||
72 | end | 77 | end |
73 | else | 78 | else |
74 | _('from %{date1} to %{date2}') % { | 79 | _('from %{date1} to %{date2}') % { |
75 | - :date1 => show_date(date1, use_numbers), | ||
76 | - :date2 => show_date(date2, use_numbers) | 80 | + :date1 => show_time(date1, use_numbers), |
81 | + :date2 => show_time(date2, use_numbers) | ||
77 | } | 82 | } |
78 | end | 83 | end |
79 | end | 84 | end |
@@ -106,18 +111,18 @@ module DatesHelper | @@ -106,18 +111,18 @@ module DatesHelper | ||
106 | 111 | ||
107 | def build_date(year, month, day = 1) | 112 | def build_date(year, month, day = 1) |
108 | if year.blank? and month.blank? and day.blank? | 113 | if year.blank? and month.blank? and day.blank? |
109 | - Date.today | 114 | + DateTime.now |
110 | else | 115 | else |
111 | if year.blank? | 116 | if year.blank? |
112 | - year = Date.today.year | 117 | + year = DateTime.now.year |
113 | end | 118 | end |
114 | if month.blank? | 119 | if month.blank? |
115 | - month = Date.today.month | 120 | + month = DateTime.now.month |
116 | end | 121 | end |
117 | if day.blank? | 122 | if day.blank? |
118 | day = 1 | 123 | day = 1 |
119 | end | 124 | end |
120 | - Date.new(year.to_i, month.to_i, day.to_i) | 125 | + DateTime.new(year.to_i, month.to_i, day.to_i) |
121 | end | 126 | end |
122 | end | 127 | end |
123 | 128 |
app/helpers/events_helper.rb
@@ -16,7 +16,7 @@ module EventsHelper | @@ -16,7 +16,7 @@ module EventsHelper | ||
16 | 16 | ||
17 | content_tag( 'tr', | 17 | content_tag( 'tr', |
18 | content_tag('td', | 18 | content_tag('td', |
19 | - content_tag('div', show_date(article.start_date) + ( article.end_date.nil? ? '' : (_(" to ") + show_date(article.end_date))),:class => 'event-date' ) + | 19 | + content_tag('div', show_time(article.start_date) + ( article.end_date.nil? ? '' : (_(" to ") + show_time(article.end_date))),:class => 'event-date' ) + |
20 | content_tag('div',link_to(article.name,article.url),:class => 'event-title') + | 20 | content_tag('div',link_to(article.name,article.url),:class => 'event-title') + |
21 | content_tag('div',(article.address.nil? or article.address == '') ? '' : (_('Place: ') + article.address),:class => 'event-place') | 21 | content_tag('div',(article.address.nil? or article.address == '') ? '' : (_('Place: ') + article.address),:class => 'event-place') |
22 | ) | 22 | ) |
@@ -30,7 +30,7 @@ module EventsHelper | @@ -30,7 +30,7 @@ module EventsHelper | ||
30 | # the day itself | 30 | # the day itself |
31 | date, | 31 | date, |
32 | # is there any events in this date? | 32 | # is there any events in this date? |
33 | - events.any? {|event| event.date_range.include?(date)}, | 33 | + events.any? {|event| event.date_range.cover?(date)}, |
34 | # is this date in the current month? | 34 | # is this date in the current month? |
35 | true | 35 | true |
36 | ] | 36 | ] |
app/helpers/forms_helper.rb
@@ -151,7 +151,7 @@ module FormsHelper | @@ -151,7 +151,7 @@ module FormsHelper | ||
151 | datepicker_options[:close_text] ||= _('Done') | 151 | datepicker_options[:close_text] ||= _('Done') |
152 | datepicker_options[:constrain_input] ||= true | 152 | datepicker_options[:constrain_input] ||= true |
153 | datepicker_options[:current_text] ||= _('Today') | 153 | datepicker_options[:current_text] ||= _('Today') |
154 | - datepicker_options[:date_format] ||= 'mm/dd/yy' | 154 | + datepicker_options[:date_format] ||= 'yy/mm/dd' |
155 | datepicker_options[:day_names] ||= [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')] | 155 | datepicker_options[:day_names] ||= [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')] |
156 | datepicker_options[:day_names_min] ||= [_('Su'), _('Mo'), _('Tu'), _('We'), _('Th'), _('Fr'), _('Sa')] | 156 | datepicker_options[:day_names_min] ||= [_('Su'), _('Mo'), _('Tu'), _('We'), _('Th'), _('Fr'), _('Sa')] |
157 | datepicker_options[:day_names_short] ||= [_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat')] | 157 | datepicker_options[:day_names_short] ||= [_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat')] |
@@ -236,7 +236,7 @@ module FormsHelper | @@ -236,7 +236,7 @@ module FormsHelper | ||
236 | weekHeader: #{datepicker_options[:week_header].to_json}, | 236 | weekHeader: #{datepicker_options[:week_header].to_json}, |
237 | yearRange: #{datepicker_options[:year_range].to_json}, | 237 | yearRange: #{datepicker_options[:year_range].to_json}, |
238 | yearSuffix: #{datepicker_options[:year_suffix].to_json} | 238 | yearSuffix: #{datepicker_options[:year_suffix].to_json} |
239 | - }) | 239 | + }).datepicker('setDate', new Date('#{value}')) |
240 | </script> | 240 | </script> |
241 | ".html_safe | 241 | ".html_safe |
242 | result | 242 | result |
app/helpers/layout_helper.rb
@@ -86,7 +86,7 @@ module LayoutHelper | @@ -86,7 +86,7 @@ module LayoutHelper | ||
86 | end | 86 | end |
87 | 87 | ||
88 | def theme_stylesheet_path | 88 | def theme_stylesheet_path |
89 | - "/assets#{theme_path}/style.css" | 89 | + "#{theme_path}/style.css".gsub(%r{^/}, '') |
90 | end | 90 | end |
91 | 91 | ||
92 | def layout_template | 92 | def layout_template |
app/models/article.rb
@@ -9,7 +9,7 @@ class Article < ActiveRecord::Base | @@ -9,7 +9,7 @@ class Article < ActiveRecord::Base | ||
9 | :highlighted, :notify_comments, :display_hits, :slug, | 9 | :highlighted, :notify_comments, :display_hits, :slug, |
10 | :external_feed_builder, :display_versions, :external_link, | 10 | :external_feed_builder, :display_versions, :external_link, |
11 | :image_builder, :show_to_followers, | 11 | :image_builder, :show_to_followers, |
12 | - :author | 12 | + :author, :display_preview |
13 | 13 | ||
14 | acts_as_having_image | 14 | acts_as_having_image |
15 | 15 | ||
@@ -637,6 +637,20 @@ class Article < ActiveRecord::Base | @@ -637,6 +637,20 @@ class Article < ActiveRecord::Base | ||
637 | can_display_hits? && display_hits | 637 | can_display_hits? && display_hits |
638 | end | 638 | end |
639 | 639 | ||
640 | + def display_media_panel? | ||
641 | + can_display_media_panel? && environment.enabled?('media_panel') | ||
642 | + end | ||
643 | + | ||
644 | + def can_display_media_panel? | ||
645 | + false | ||
646 | + end | ||
647 | + | ||
648 | + settings_items :display_preview, :type => :boolean, :default => false | ||
649 | + | ||
650 | + def display_preview? | ||
651 | + false | ||
652 | + end | ||
653 | + | ||
640 | def image? | 654 | def image? |
641 | false | 655 | false |
642 | end | 656 | end |
@@ -745,9 +759,10 @@ class Article < ActiveRecord::Base | @@ -745,9 +759,10 @@ class Article < ActiveRecord::Base | ||
745 | end | 759 | end |
746 | 760 | ||
747 | def body_images_paths | 761 | def body_images_paths |
748 | - require 'uri' | ||
749 | Nokogiri::HTML.fragment(self.body.to_s).css('img[src]').collect do |i| | 762 | Nokogiri::HTML.fragment(self.body.to_s).css('img[src]').collect do |i| |
750 | - (self.profile && self.profile.environment) ? URI.join(self.profile.environment.top_url, URI.escape(i['src'])).to_s : i['src'] | 763 | + src = i['src'] |
764 | + src = URI.escape src if self.new_record? # xss_terminate runs on save | ||
765 | + (self.profile && self.profile.environment) ? URI.join(self.profile.environment.top_url, src).to_s : src | ||
751 | end | 766 | end |
752 | end | 767 | end |
753 | 768 |
app/models/category.rb
@@ -81,7 +81,7 @@ class Category < ActiveRecord::Base | @@ -81,7 +81,7 @@ class Category < ActiveRecord::Base | ||
81 | end | 81 | end |
82 | 82 | ||
83 | def upcoming_events(limit = 10) | 83 | def upcoming_events(limit = 10) |
84 | - self.events.where('start_date >= ?', Date.today).reorder('start_date').paginate(:page => 1, :per_page => limit) | 84 | + self.events.where('start_date >= ?', DateTime.now.beginning_of_day).order('start_date').paginate(page: 1, per_page: limit) |
85 | end | 85 | end |
86 | 86 | ||
87 | def display_in_menu? | 87 | def display_in_menu? |
app/models/enterprise_homepage.rb
app/models/environment.rb
@@ -13,7 +13,7 @@ class Environment < ActiveRecord::Base | @@ -13,7 +13,7 @@ class Environment < ActiveRecord::Base | ||
13 | :reports_lower_bound, :noreply_email, | 13 | :reports_lower_bound, :noreply_email, |
14 | :signup_welcome_screen_body, :members_whitelist_enabled, | 14 | :signup_welcome_screen_body, :members_whitelist_enabled, |
15 | :members_whitelist, :highlighted_news_amount, | 15 | :members_whitelist, :highlighted_news_amount, |
16 | - :portal_news_amount, :date_format | 16 | + :portal_news_amount, :date_format, :signup_intro |
17 | 17 | ||
18 | has_many :users | 18 | has_many :users |
19 | 19 |
app/models/event.rb
@@ -23,7 +23,7 @@ class Event < Article | @@ -23,7 +23,7 @@ class Event < Article | ||
23 | 23 | ||
24 | def initialize(*args) | 24 | def initialize(*args) |
25 | super(*args) | 25 | super(*args) |
26 | - self.start_date ||= Date.today | 26 | + self.start_date ||= DateTime.now |
27 | end | 27 | end |
28 | 28 | ||
29 | validates_presence_of :title, :start_date | 29 | validates_presence_of :title, :start_date |
@@ -35,8 +35,9 @@ class Event < Article | @@ -35,8 +35,9 @@ class Event < Article | ||
35 | end | 35 | end |
36 | 36 | ||
37 | scope :by_day, -> (date) { | 37 | scope :by_day, -> (date) { |
38 | + where('start_date >= :start_date AND start_date <= :end_date AND end_date IS NULL OR (start_date <= :end_date AND end_date >= :start_date)', | ||
39 | + {:start_date => date.beginning_of_day, :end_date => date.end_of_day}). | ||
38 | order('start_date ASC') | 40 | order('start_date ASC') |
39 | - .where('start_date = :date AND end_date IS NULL OR (start_date <= :date AND end_date >= :date)', {:date => date}) | ||
40 | } | 41 | } |
41 | 42 | ||
42 | scope :next_events_from_month, -> (date) { | 43 | scope :next_events_from_month, -> (date) { |
@@ -75,7 +76,7 @@ class Event < Article | @@ -75,7 +76,7 @@ class Event < Article | ||
75 | 76 | ||
76 | def self.date_range(year, month) | 77 | def self.date_range(year, month) |
77 | if year.nil? || month.nil? | 78 | if year.nil? || month.nil? |
78 | - today = Date.today | 79 | + today = DateTime.now |
79 | year = today.year | 80 | year = today.year |
80 | month = today.month | 81 | month = today.month |
81 | else | 82 | else |
@@ -83,7 +84,7 @@ class Event < Article | @@ -83,7 +84,7 @@ class Event < Article | ||
83 | month = month.to_i | 84 | month = month.to_i |
84 | end | 85 | end |
85 | 86 | ||
86 | - first_day = Date.new(year, month, 1) | 87 | + first_day = DateTime.new(year, month, 1) |
87 | last_day = first_day + 1.month - 1.day | 88 | last_day = first_day + 1.month - 1.day |
88 | 89 | ||
89 | first_day..last_day | 90 | first_day..last_day |
@@ -109,7 +110,7 @@ class Event < Article | @@ -109,7 +110,7 @@ class Event < Article | ||
109 | end | 110 | end |
110 | 111 | ||
111 | def duration | 112 | def duration |
112 | - ((self.end_date || self.start_date) - self.start_date).to_i | 113 | + (((self.end_date || self.start_date) - self.start_date).to_i/60/60/24) |
113 | end | 114 | end |
114 | 115 | ||
115 | alias_method :article_lead, :lead | 116 | alias_method :article_lead, :lead |
@@ -129,6 +130,10 @@ class Event < Article | @@ -129,6 +130,10 @@ class Event < Article | ||
129 | true | 130 | true |
130 | end | 131 | end |
131 | 132 | ||
133 | + def can_display_media_panel? | ||
134 | + true | ||
135 | + end | ||
136 | + | ||
132 | include Noosfero::TranslatableContent | 137 | include Noosfero::TranslatableContent |
133 | include MaybeAddHttp | 138 | include MaybeAddHttp |
134 | 139 |
app/models/profile.rb
@@ -579,6 +579,14 @@ class Profile < ActiveRecord::Base | @@ -579,6 +579,14 @@ class Profile < ActiveRecord::Base | ||
579 | options.merge(Noosfero.url_options) | 579 | options.merge(Noosfero.url_options) |
580 | end | 580 | end |
581 | 581 | ||
582 | + def top_url(scheme = 'http') | ||
583 | + url = scheme + '://' | ||
584 | + url << url_options[:host] | ||
585 | + url << ':' << url_options[:port].to_s if url_options.key?(:port) | ||
586 | + url << Noosfero.root('') | ||
587 | + url | ||
588 | + end | ||
589 | + | ||
582 | private :generate_url, :url_options | 590 | private :generate_url, :url_options |
583 | 591 | ||
584 | def default_hostname | 592 | def default_hostname |
app/models/text_article.rb
@@ -33,12 +33,16 @@ class TextArticle < Article | @@ -33,12 +33,16 @@ class TextArticle < Article | ||
33 | end | 33 | end |
34 | 34 | ||
35 | def change_element_path(el, attribute) | 35 | def change_element_path(el, attribute) |
36 | - fullpath = /(https?):\/\/(#{environment.default_hostname})(:\d+)?(\/.*)/.match(el[attribute]) | 36 | + fullpath = /(https?):\/\/(#{profile.default_hostname})(:\d+)?(\/.*)/.match(el[attribute]) |
37 | if fullpath | 37 | if fullpath |
38 | domain = fullpath[2] | 38 | domain = fullpath[2] |
39 | path = fullpath[4] | 39 | path = fullpath[4] |
40 | - el[attribute] = path if domain == environment.default_hostname | 40 | + el[attribute] = path if domain == profile.default_hostname |
41 | end | 41 | end |
42 | end | 42 | end |
43 | 43 | ||
44 | + def display_preview? | ||
45 | + parent && parent.kind_of?(Blog) && parent.display_preview | ||
46 | + end | ||
47 | + | ||
44 | end | 48 | end |
app/models/textile_article.rb
@@ -24,6 +24,10 @@ class TextileArticle < TextArticle | @@ -24,6 +24,10 @@ class TextileArticle < TextArticle | ||
24 | true | 24 | true |
25 | end | 25 | end |
26 | 26 | ||
27 | + def can_display_media_panel? | ||
28 | + true | ||
29 | + end | ||
30 | + | ||
27 | protected | 31 | protected |
28 | 32 | ||
29 | def convert_to_html(textile) | 33 | def convert_to_html(textile) |
app/models/tiny_mce_article.rb
app/models/user.rb
@@ -121,11 +121,17 @@ class User < ActiveRecord::Base | @@ -121,11 +121,17 @@ class User < ActiveRecord::Base | ||
121 | 121 | ||
122 | validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('{fn} must be checked in order to signup.').fix_i18n | 122 | validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('{fn} must be checked in order to signup.').fix_i18n |
123 | 123 | ||
124 | + scope :has_login?, lambda { |login,email,environment_id| | ||
125 | + where('login = ? OR email = ?', login, email). | ||
126 | + where(environment_id: environment_id) | ||
127 | + } | ||
128 | + | ||
124 | # Authenticates a user by their login name or email and unencrypted password. Returns the user or nil. | 129 | # Authenticates a user by their login name or email and unencrypted password. Returns the user or nil. |
125 | def self.authenticate(login, password, environment = nil) | 130 | def self.authenticate(login, password, environment = nil) |
126 | environment ||= Environment.default | 131 | environment ||= Environment.default |
127 | - u = self.where('(login = ? OR email = ?) AND environment_id = ? AND activated_at IS NOT NULL', | ||
128 | - login, login, environment.id).first # need to get the salt | 132 | + |
133 | + u = self.has_login?(login, login, environment.id) | ||
134 | + u = u.first if u.is_a?(ActiveRecord::Relation) | ||
129 | u && u.authenticated?(password) ? u : nil | 135 | u && u.authenticated?(password) ? u : nil |
130 | end | 136 | end |
131 | 137 | ||
@@ -237,7 +243,23 @@ class User < ActiveRecord::Base | @@ -237,7 +243,23 @@ class User < ActiveRecord::Base | ||
237 | password.crypt(salt) | 243 | password.crypt(salt) |
238 | end | 244 | end |
239 | 245 | ||
246 | + class UserNotActivated < StandardError | ||
247 | + attr_reader :user | ||
248 | + | ||
249 | + def initialize(message, user = nil) | ||
250 | + @user = user | ||
251 | + | ||
252 | + super(message) | ||
253 | + end | ||
254 | + end | ||
255 | + | ||
240 | def authenticated?(password) | 256 | def authenticated?(password) |
257 | + | ||
258 | + unless self.activated? | ||
259 | + message = _('The user "%{login}" is not activated! Please check your email to activate your user') % {login: self.login} | ||
260 | + raise UserNotActivated.new(message, self) | ||
261 | + end | ||
262 | + | ||
241 | result = (crypted_password == encrypt(password)) | 263 | result = (crypted_password == encrypt(password)) |
242 | if (encryption_method != User.system_encryption_method) && result | 264 | if (encryption_method != User.system_encryption_method) && result |
243 | self.password_type = User.system_encryption_method.to_s | 265 | self.password_type = User.system_encryption_method.to_s |
@@ -276,9 +298,15 @@ class User < ActiveRecord::Base | @@ -276,9 +298,15 @@ class User < ActiveRecord::Base | ||
276 | # current password. | 298 | # current password. |
277 | # * Saves the record unless it is a new one. | 299 | # * Saves the record unless it is a new one. |
278 | def change_password!(current, new, confirmation) | 300 | def change_password!(current, new, confirmation) |
279 | - unless self.authenticated?(current) | ||
280 | - self.errors.add(:current_password, _('does not match.')) | ||
281 | - raise IncorrectPassword | 301 | + |
302 | + begin | ||
303 | + unless self.authenticated?(current) | ||
304 | + self.errors.add(:current_password, _('does not match.')) | ||
305 | + raise IncorrectPassword | ||
306 | + end | ||
307 | + rescue UserNotActivated => e | ||
308 | + self.errors.add(:current_password, e.message) | ||
309 | + raise UserNotActivated | ||
282 | end | 310 | end |
283 | self.force_change_password!(new, confirmation) | 311 | self.force_change_password!(new, confirmation) |
284 | end | 312 | end |
app/views/blocks/profile_info_actions/_join_leave_community.html.erb
@@ -11,7 +11,7 @@ | @@ -11,7 +11,7 @@ | ||
11 | class: 'join-community', | 11 | class: 'join-community', |
12 | style: 'position: relative; display: none;' %> | 12 | style: 'position: relative; display: none;' %> |
13 | <% else %> | 13 | <% else %> |
14 | - <%= button :add, _('Join this community'), profile.join_url %> | 14 | + <%= button :add, _('Join this community'), profile.join_url, class: 'join-community' %> |
15 | <% end %> | 15 | <% end %> |
16 | <% end %> | 16 | <% end %> |
17 | <% else %> | 17 | <% else %> |
app/views/cms/_event.html.erb
@@ -8,9 +8,8 @@ | @@ -8,9 +8,8 @@ | ||
8 | <%= render :partial => 'general_fields' %> | 8 | <%= render :partial => 'general_fields' %> |
9 | <%= render :partial => 'translatable' %> | 9 | <%= render :partial => 'translatable' %> |
10 | 10 | ||
11 | -<%= labelled_form_field(_('Start date'), pick_date(:article, :start_date)) %> | 11 | +<%= date_range_field('article[start_date]', 'article[end_date]', @article.start_date, @article.end_date, _('%Y-%m-%d %H:%M'), {:time => true}, {:id => 'article_start_date'} ) %> |
12 | 12 | ||
13 | -<%= labelled_form_field(_('End date'), pick_date(:article, :end_date)) %> | ||
14 | 13 | ||
15 | <%= labelled_form_field(_('Event website:'), text_field(:article, :link)) %> | 14 | <%= labelled_form_field(_('Event website:'), text_field(:article, :link)) %> |
16 | 15 |
app/views/cms/edit.html.erb
1 | <%= error_messages_for 'article' %> | 1 | <%= error_messages_for 'article' %> |
2 | 2 | ||
3 | -<% show_media_panel = environment.enabled?('media_panel') && [TinyMceArticle, TextileArticle, Event, EnterpriseHomepage].any?{|klass| @article.kind_of?(klass)} %> | ||
4 | - | ||
5 | -<div class='<%= (show_media_panel ? 'with_media_panel' : 'no_media_panel') %>'> | 3 | +<div class='<%= (@article.display_media_panel? ? 'with_media_panel' : 'no_media_panel') %>'> |
6 | <%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %> | 4 | <%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %> |
7 | 5 | ||
8 | <%= hidden_field_tag("type", @type) if @type %> | 6 | <%= hidden_field_tag("type", @type) if @type %> |
@@ -68,7 +66,7 @@ | @@ -68,7 +66,7 @@ | ||
68 | <% end %> | 66 | <% end %> |
69 | </div> | 67 | </div> |
70 | 68 | ||
71 | -<% if show_media_panel %> | 69 | +<% if @article.display_media_panel? %> |
72 | <%= render :partial => 'text_editor_sidebar' %> | 70 | <%= render :partial => 'text_editor_sidebar' %> |
73 | <% end %> | 71 | <% end %> |
74 | 72 |
app/views/content_viewer/_article_title.html.erb
@@ -7,7 +7,7 @@ | @@ -7,7 +7,7 @@ | ||
7 | <% end %> | 7 | <% end %> |
8 | </h1> | 8 | </h1> |
9 | <%= render :partial => "publishing_info" %> | 9 | <%= render :partial => "publishing_info" %> |
10 | - <% unless @page.abstract.blank? %> | 10 | + <% if @page.display_preview? %> |
11 | <div class="preview"> | 11 | <div class="preview"> |
12 | <%= @page.lead %> | 12 | <%= @page.lead %> |
13 | </div> | 13 | </div> |
app/views/content_viewer/_publishing_info.html.erb
1 | <span class="publishing-info"> | 1 | <span class="publishing-info"> |
2 | <span class="date"> | 2 | <span class="date"> |
3 | - <%= show_date(@page.published_at) %> | 3 | + <%= show_time(@page.published_at) %> |
4 | </span> | 4 | </span> |
5 | <span class="author"> | 5 | <span class="author"> |
6 | <%= _(", by %s") % (@page.author ? link_to(@page.author_name, @page.author_url) : @page.author_name) %> | 6 | <%= _(", by %s") % (@page.author ? link_to(@page.author_name, @page.author_url) : @page.author_name) %> |
app/views/content_viewer/_uploaded_file.html.erb
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | <%= link_to '', | 2 | <%= link_to '', |
3 | uploaded_file.view_url, | 3 | uploaded_file.view_url, |
4 | :class => 'image', | 4 | :class => 'image', |
5 | - :style => 'background-image: url(%s)'% uploaded_file.public_filename(:thumb) | 5 | + :style => 'background-image: url(%s)'% [Noosfero.root, uploaded_file.public_filename(:thumb)].join |
6 | %> | 6 | %> |
7 | <span><%=h uploaded_file.title %></span> | 7 | <span><%=h uploaded_file.title %></span> |
8 | <% else %> | 8 | <% else %> |
db/migrate/20150722042714_change_article_date_to_datetime.rb
0 → 100644
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | +class ChangeArticleDateToDatetime < ActiveRecord::Migration | ||
2 | + | ||
3 | + def up | ||
4 | + change_table :articles do |t| | ||
5 | + t.change :start_date, :datetime | ||
6 | + t.change :end_date, :datetime | ||
7 | + end | ||
8 | + | ||
9 | + change_table :article_versions do |t| | ||
10 | + t.change :start_date, :datetime | ||
11 | + t.change :end_date, :datetime | ||
12 | + end | ||
13 | + end | ||
14 | + | ||
15 | + def down | ||
16 | + change_table :articles do |t| | ||
17 | + t.change :start_date, :date | ||
18 | + t.change :end_date, :date | ||
19 | + end | ||
20 | + | ||
21 | + change_table :article_versions do |t| | ||
22 | + t.change :start_date, :date | ||
23 | + t.change :end_date, :date | ||
24 | + end | ||
25 | + end | ||
26 | + | ||
27 | +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 => 20150712130827) do | 14 | +ActiveRecord::Schema.define(:version => 20150722042714) 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" |
@@ -75,8 +75,8 @@ ActiveRecord::Schema.define(:version => 20150712130827) do | @@ -75,8 +75,8 @@ ActiveRecord::Schema.define(:version => 20150712130827) do | ||
75 | t.integer "comments_count" | 75 | t.integer "comments_count" |
76 | t.boolean "advertise", :default => true | 76 | t.boolean "advertise", :default => true |
77 | t.boolean "published", :default => true | 77 | t.boolean "published", :default => true |
78 | - t.date "start_date" | ||
79 | - t.date "end_date" | 78 | + t.datetime "start_date" |
79 | + t.datetime "end_date" | ||
80 | t.integer "children_count", :default => 0 | 80 | t.integer "children_count", :default => 0 |
81 | t.boolean "accept_comments", :default => true | 81 | t.boolean "accept_comments", :default => true |
82 | t.integer "reference_article_id" | 82 | t.integer "reference_article_id" |
@@ -127,8 +127,8 @@ ActiveRecord::Schema.define(:version => 20150712130827) do | @@ -127,8 +127,8 @@ ActiveRecord::Schema.define(:version => 20150712130827) do | ||
127 | t.integer "comments_count", :default => 0 | 127 | t.integer "comments_count", :default => 0 |
128 | t.boolean "advertise", :default => true | 128 | t.boolean "advertise", :default => true |
129 | t.boolean "published", :default => true | 129 | t.boolean "published", :default => true |
130 | - t.date "start_date" | ||
131 | - t.date "end_date" | 130 | + t.datetime "start_date" |
131 | + t.datetime "end_date" | ||
132 | t.integer "children_count", :default => 0 | 132 | t.integer "children_count", :default => 0 |
133 | t.boolean "accept_comments", :default => true | 133 | t.boolean "accept_comments", :default => true |
134 | t.integer "reference_article_id" | 134 | t.integer "reference_article_id" |
features/events.feature
@@ -223,7 +223,7 @@ Feature: events | @@ -223,7 +223,7 @@ Feature: events | ||
223 | | owner | name | start_date | end_date | | 223 | | owner | name | start_date | end_date | |
224 | | josesilva | WikiSym 2009 | 2009-10-25 | 2009-10-27 | | 224 | | josesilva | WikiSym 2009 | 2009-10-25 | 2009-10-27 | |
225 | When I am on /profile/josesilva/events/2009/10/26 | 225 | When I am on /profile/josesilva/events/2009/10/26 |
226 | - Then I should see "October 25, 2009 to October 27, 2009" | 226 | + Then I should see "October 25, 2009 0:00 to October 27, 2009 0:00" |
227 | 227 | ||
228 | Scenario: show place of the event | 228 | Scenario: show place of the event |
229 | Given I am on /profile/josesilva/events/2009/10 | 229 | Given I am on /profile/josesilva/events/2009/10 |
lib/tasks/release.rake
@@ -239,12 +239,12 @@ EOF | @@ -239,12 +239,12 @@ EOF | ||
239 | end | 239 | end |
240 | end | 240 | end |
241 | 241 | ||
242 | - Rake::Task['noosfero:upload'].invoke | ||
243 | if confirm('Upload the packages') | 242 | if confirm('Upload the packages') |
244 | puts "==> Uploading debian packages..." | 243 | puts "==> Uploading debian packages..." |
245 | Rake::Task['noosfero:upload_packages'].invoke(target) | 244 | Rake::Task['noosfero:upload_packages'].invoke(target) |
246 | else | 245 | else |
247 | - puts "I: please upload the package manually!" | 246 | + puts "I: please upload the package manually later by running" |
247 | + puts "I: $ rake noosfero:upload_packages" | ||
248 | end | 248 | end |
249 | 249 | ||
250 | rm_f "tmp/pending-release" | 250 | rm_f "tmp/pending-release" |
plugins/analytics/controllers/myprofile/analytics_plugin/stats_controller.rb
0 → 100644
@@ -0,0 +1,21 @@ | @@ -0,0 +1,21 @@ | ||
1 | +class AnalyticsPlugin::StatsController < MyProfileController | ||
2 | + | ||
3 | + no_design_blocks | ||
4 | + | ||
5 | + before_filter :skip_page_view | ||
6 | + | ||
7 | + def index | ||
8 | + end | ||
9 | + | ||
10 | + protected | ||
11 | + | ||
12 | + def default_url_options | ||
13 | + # avoid rails' use_relative_controller! | ||
14 | + {use_route: '/'} | ||
15 | + end | ||
16 | + | ||
17 | + def skip_page_view | ||
18 | + @analytics_skip_page_view = true | ||
19 | + end | ||
20 | + | ||
21 | +end |
plugins/analytics/lib/analytics_plugin.rb
plugins/analytics/lib/analytics_plugin/base.rb
@@ -38,4 +38,12 @@ class AnalyticsPlugin::Base < Noosfero::Plugin | @@ -38,4 +38,12 @@ class AnalyticsPlugin::Base < Noosfero::Plugin | ||
38 | }] | 38 | }] |
39 | end | 39 | end |
40 | 40 | ||
41 | + def control_panel_buttons | ||
42 | + { | ||
43 | + title: I18n.t('analytics_plugin.lib.plugin.panel_button'), | ||
44 | + icon: 'analytics-access', | ||
45 | + url: {controller: 'analytics_plugin/stats', action: :index} | ||
46 | + } | ||
47 | + end | ||
48 | + | ||
41 | end | 49 | end |
plugins/analytics/lib/ext/profile.rb
@@ -13,7 +13,7 @@ end | @@ -13,7 +13,7 @@ end | ||
13 | class Profile | 13 | class Profile |
14 | 14 | ||
15 | def analytics_settings attrs = {} | 15 | def analytics_settings attrs = {} |
16 | - @analytics_settings ||= Noosfero::Plugin::Settings.new self, AnalyticsPlugin, attrs | 16 | + @analytics_settings ||= Noosfero::Plugin::Settings.new self, ::AnalyticsPlugin, attrs |
17 | attrs.each{ |a, v| @analytics_settings.send "#{a}=", v } | 17 | attrs.each{ |a, v| @analytics_settings.send "#{a}=", v } |
18 | @analytics_settings | 18 | @analytics_settings |
19 | end | 19 | end |
plugins/analytics/locales/en.yml
@@ -5,6 +5,13 @@ en: &en | @@ -5,6 +5,13 @@ en: &en | ||
5 | plugin: | 5 | plugin: |
6 | name: 'Access tracking' | 6 | name: 'Access tracking' |
7 | description: 'Register the access of selected profiles' | 7 | description: 'Register the access of selected profiles' |
8 | + panel_button: 'Access tracking' | ||
9 | + | ||
10 | + views: | ||
11 | + stats: | ||
12 | + user: 'User' | ||
13 | + initial_time: 'Time' | ||
14 | + pages: 'Pages' | ||
8 | 15 | ||
9 | en-US: | 16 | en-US: |
10 | <<: *en | 17 | <<: *en |
plugins/analytics/locales/pt.yml
@@ -5,6 +5,13 @@ pt: &pt | @@ -5,6 +5,13 @@ pt: &pt | ||
5 | plugin: | 5 | plugin: |
6 | name: 'Rastreio de accesso' | 6 | name: 'Rastreio de accesso' |
7 | description: 'Registra o acesso de perfis selecionados' | 7 | description: 'Registra o acesso de perfis selecionados' |
8 | + panel_button: 'Rastreio de accesso' | ||
9 | + | ||
10 | + views: | ||
11 | + stats: | ||
12 | + user: 'Usuário' | ||
13 | + initial_time: 'Horário' | ||
14 | + pages: 'Páginas' | ||
8 | 15 | ||
9 | pt-BR: | 16 | pt-BR: |
10 | <<: *pt | 17 | <<: *pt |
plugins/analytics/models/analytics_plugin/page_view.rb
@@ -25,10 +25,24 @@ class AnalyticsPlugin::PageView < ActiveRecord::Base | @@ -25,10 +25,24 @@ class AnalyticsPlugin::PageView < ActiveRecord::Base | ||
25 | before_validation :fill_referer_page_view, on: :create | 25 | before_validation :fill_referer_page_view, on: :create |
26 | before_validation :fill_visit, on: :create | 26 | before_validation :fill_visit, on: :create |
27 | 27 | ||
28 | + scope :latest, -> { order 'request_started_at DESC' } | ||
29 | + | ||
28 | def request_duration | 30 | def request_duration |
29 | self.request_finished_at - self.request_started_at | 31 | self.request_finished_at - self.request_started_at |
30 | end | 32 | end |
31 | 33 | ||
34 | + def initial_time | ||
35 | + self.page_loaded_at || self.request_finished_at | ||
36 | + end | ||
37 | + | ||
38 | + def user_last_time_seen | ||
39 | + self.initial_time + self.time_on_page | ||
40 | + end | ||
41 | + | ||
42 | + def user_on_page? | ||
43 | + Time.now < self.user_last_time_seen + AnalyticsPlugin::TimeOnPageUpdateInterval | ||
44 | + end | ||
45 | + | ||
32 | def page_load! | 46 | def page_load! |
33 | self.page_loaded_at = Time.now | 47 | self.page_loaded_at = Time.now |
34 | self.update_column :page_loaded_at, self.page_loaded_at | 48 | self.update_column :page_loaded_at, self.page_loaded_at |
@@ -36,10 +50,9 @@ class AnalyticsPlugin::PageView < ActiveRecord::Base | @@ -36,10 +50,9 @@ class AnalyticsPlugin::PageView < ActiveRecord::Base | ||
36 | 50 | ||
37 | def increase_time_on_page! | 51 | def increase_time_on_page! |
38 | now = Time.now | 52 | now = Time.now |
39 | - initial_time = self.page_loaded_at || self.request_finished_at | ||
40 | - return unless now > initial_time | 53 | + return unless now > self.initial_time |
41 | 54 | ||
42 | - self.time_on_page = now - initial_time | 55 | + self.time_on_page = now - self.initial_time |
43 | self.update_column :time_on_page, self.time_on_page | 56 | self.update_column :time_on_page, self.time_on_page |
44 | end | 57 | end |
45 | 58 | ||
@@ -59,7 +72,7 @@ class AnalyticsPlugin::PageView < ActiveRecord::Base | @@ -59,7 +72,7 @@ class AnalyticsPlugin::PageView < ActiveRecord::Base | ||
59 | end | 72 | end |
60 | 73 | ||
61 | def fill_visit | 74 | def fill_visit |
62 | - self.visit = self.referer_page_view.visit if self.referer_page_view | 75 | + self.visit = self.referer_page_view.visit if self.referer_page_view and self.referer_page_view.user_on_page? |
63 | self.visit ||= AnalyticsPlugin::Visit.new profile: profile | 76 | self.visit ||= AnalyticsPlugin::Visit.new profile: profile |
64 | end | 77 | end |
65 | 78 |
plugins/analytics/models/analytics_plugin/visit.rb
@@ -3,9 +3,17 @@ class AnalyticsPlugin::Visit < ActiveRecord::Base | @@ -3,9 +3,17 @@ class AnalyticsPlugin::Visit < ActiveRecord::Base | ||
3 | attr_accessible *self.column_names | 3 | attr_accessible *self.column_names |
4 | attr_accessible :profile | 4 | attr_accessible :profile |
5 | 5 | ||
6 | - default_scope -> { includes :page_views } | ||
7 | - | ||
8 | belongs_to :profile | 6 | belongs_to :profile |
9 | has_many :page_views, class_name: 'AnalyticsPlugin::PageView', dependent: :destroy | 7 | has_many :page_views, class_name: 'AnalyticsPlugin::PageView', dependent: :destroy |
10 | 8 | ||
9 | + default_scope -> { joins(:page_views).includes :page_views } | ||
10 | + | ||
11 | + scope :latest, -> { order 'analytics_plugin_page_views.request_started_at DESC' } | ||
12 | + | ||
13 | + def first_page_view | ||
14 | + self.page_views.first | ||
15 | + end | ||
16 | + | ||
17 | + delegate :user, :initial_time, to: :first_page_view | ||
18 | + | ||
11 | end | 19 | end |
plugins/analytics/test/functional/content_viewer_controller_test.rb
@@ -31,6 +31,8 @@ class ContentViewerControllerTest < ActionController::TestCase | @@ -31,6 +31,8 @@ class ContentViewerControllerTest < ActionController::TestCase | ||
31 | 31 | ||
32 | first_page_view = @community.page_views.order(:id).first | 32 | first_page_view = @community.page_views.order(:id).first |
33 | assert_equal @request.referer, first_page_view.referer_url | 33 | assert_equal @request.referer, first_page_view.referer_url |
34 | + assert_equal @user, first_page_view.user | ||
35 | + assert first_page_view.request_duration > 0 and first_page_view.request_duration < 1 | ||
34 | 36 | ||
35 | @request.env['HTTP_REFERER'] = first_url | 37 | @request.env['HTTP_REFERER'] = first_url |
36 | get :view_page, profile: @community.identifier, page: @community.articles.last.path.split('/') | 38 | get :view_page, profile: @community.identifier, page: @community.articles.last.path.split('/') |
@@ -40,9 +42,13 @@ class ContentViewerControllerTest < ActionController::TestCase | @@ -40,9 +42,13 @@ class ContentViewerControllerTest < ActionController::TestCase | ||
40 | second_page_view = @community.page_views.order(:id).last | 42 | second_page_view = @community.page_views.order(:id).last |
41 | assert_equal first_page_view, second_page_view.referer_page_view | 43 | assert_equal first_page_view, second_page_view.referer_page_view |
42 | 44 | ||
43 | - assert_equal @user, second_page_view.user | ||
44 | - | ||
45 | - assert second_page_view.request_duration > 0 and second_page_view.request_duration < 1 | 45 | + # another visit, the referer is set but should be ignored because |
46 | + # the user didn't report to be on the page until now | ||
47 | + @request.env['HTTP_REFERER'] = first_url | ||
48 | + future = Time.now + 2*AnalyticsPlugin::TimeOnPageUpdateInterval | ||
49 | + Time.stubs(:now).returns(future) | ||
50 | + get :view_page, profile: @community.identifier, page: @community.articles.last.path.split('/') | ||
51 | + assert_equal 2, @community.visits.count | ||
46 | end | 52 | end |
47 | 53 | ||
48 | end | 54 | end |
plugins/analytics/views/analytics_plugin/_body_ending.html.slim
1 | javascript: | 1 | javascript: |
2 | analytics.timeOnPage.baseUrl = #{url_for(controller: 'analytics_plugin/time_on_page').to_json} | 2 | analytics.timeOnPage.baseUrl = #{url_for(controller: 'analytics_plugin/time_on_page').to_json} |
3 | - analytics.timeOnPage.updateInterval = #{AnalyticsPlugin::TimeOnPageUpdateInterval.to_json} | 3 | + analytics.timeOnPage.updateInterval = #{AnalyticsPlugin::TimeOnPageUpdateIntervalMs.to_json} |
4 | analytics.requestId = #{request.env['action_dispatch.request_id'].to_json} | 4 | analytics.requestId = #{request.env['action_dispatch.request_id'].to_json} |
5 | analytics.init() | 5 | analytics.init() |
6 | 6 |
plugins/analytics/views/analytics_plugin/stats/_table.html.slim
0 → 100644
@@ -0,0 +1,38 @@ | @@ -0,0 +1,38 @@ | ||
1 | + | ||
2 | +table#analytics-stats.table data-toggle='table' data-striped='true' data-sortable='true' data-icons-prefix='fa' | ||
3 | + thead | ||
4 | + - unless profile.analytics_anonymous? | ||
5 | + th= t'analytics_plugin.views.stats.user' | ||
6 | + th= t'analytics_plugin.views.stats.initial_time' | ||
7 | + th= t'analytics_plugin.views.stats.pages' | ||
8 | + | ||
9 | + tbody | ||
10 | + - profile.visits.each do |visit| | ||
11 | + tr | ||
12 | + td= link_to visit.user.name, visit.user.url | ||
13 | + td | ||
14 | + div data-toggle="tooltip" data-title='#{l visit.initial_time}' | ||
15 | + = time_ago_in_words(visit.initial_time) | ||
16 | + |  | ||
17 | + = _'ago' | ||
18 | + td | ||
19 | + - visit.page_views.each do |page_view| | ||
20 | + = link_to page_view.url, page_view.url | ||
21 | + | | ||
22 | + = "(#{distance_of_time_in_words page_view.time_on_page})" | ||
23 | + | -> | ||
24 | + | ||
25 | +javascript: | ||
26 | + $('#analytics-stats').bootstrapTable({ | ||
27 | + striped: true, | ||
28 | + columns: [ | ||
29 | + {sortable: true}, | ||
30 | + {sortable: true}, | ||
31 | + {sortable: true}, | ||
32 | + ], | ||
33 | + }) | ||
34 | + | ||
35 | + $(document).ready(function() { | ||
36 | + $('[data-toggle="tooltip"]').tooltip() | ||
37 | + }) | ||
38 | + |
plugins/analytics/views/analytics_plugin/stats/index.html.slim
0 → 100644
@@ -0,0 +1,5 @@ | @@ -0,0 +1,5 @@ | ||
1 | +- content_for :head | ||
2 | + = javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table-all.min.js' | ||
3 | + = stylesheet_link_tag 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table.css' | ||
4 | + | ||
5 | += render 'table' |
plugins/community_track/lib/community_track_plugin/step.rb
@@ -29,8 +29,8 @@ class CommunityTrackPlugin::Step < Folder | @@ -29,8 +29,8 @@ class CommunityTrackPlugin::Step < Folder | ||
29 | 29 | ||
30 | def initialize(*args) | 30 | def initialize(*args) |
31 | super(*args) | 31 | super(*args) |
32 | - self.start_date ||= Date.today | ||
33 | - self.end_date ||= Date.today + 1.day | 32 | + self.start_date ||= DateTime.now |
33 | + self.end_date ||= DateTime.now + 1.day | ||
34 | end | 34 | end |
35 | 35 | ||
36 | def set_hidden_position | 36 | def set_hidden_position |
@@ -72,20 +72,20 @@ class CommunityTrackPlugin::Step < Folder | @@ -72,20 +72,20 @@ class CommunityTrackPlugin::Step < Folder | ||
72 | end | 72 | end |
73 | 73 | ||
74 | def active? | 74 | def active? |
75 | - (start_date..end_date).include?(Date.today) | 75 | + (start_date..end_date).cover?(DateTime.now) |
76 | end | 76 | end |
77 | 77 | ||
78 | def finished? | 78 | def finished? |
79 | - Date.today > end_date | 79 | + DateTime.now > end_date |
80 | end | 80 | end |
81 | 81 | ||
82 | def waiting? | 82 | def waiting? |
83 | - Date.today < start_date | 83 | + DateTime.now < start_date |
84 | end | 84 | end |
85 | 85 | ||
86 | def schedule_activation | 86 | def schedule_activation |
87 | return if !changes['start_date'] && !changes['end_date'] | 87 | return if !changes['start_date'] && !changes['end_date'] |
88 | - if Date.today <= end_date || accept_comments | 88 | + if DateTime.now <= end_date || accept_comments |
89 | schedule_date = !accept_comments ? start_date : end_date + 1.day | 89 | schedule_date = !accept_comments ? start_date : end_date + 1.day |
90 | CommunityTrackPlugin::ActivationJob.find(id).destroy_all | 90 | CommunityTrackPlugin::ActivationJob.find(id).destroy_all |
91 | Delayed::Job.enqueue(CommunityTrackPlugin::ActivationJob.new(self.id), :run_at => schedule_date) | 91 | Delayed::Job.enqueue(CommunityTrackPlugin::ActivationJob.new(self.id), :run_at => schedule_date) |
plugins/community_track/test/functional/community_track_plugin_content_viewer_controller_test.rb
@@ -5,7 +5,7 @@ class ContentViewerControllerTest < ActionController::TestCase | @@ -5,7 +5,7 @@ class ContentViewerControllerTest < ActionController::TestCase | ||
5 | def setup | 5 | def setup |
6 | @profile = Community.create!(:name => 'Sample community', :identifier => 'sample-community') | 6 | @profile = Community.create!(:name => 'Sample community', :identifier => 'sample-community') |
7 | @track = create_track('track', @profile) | 7 | @track = create_track('track', @profile) |
8 | - @step = CommunityTrackPlugin::Step.create!(:name => 'step1', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => Date.today, :start_date => Date.today, :tool_type => TinyMceArticle.name) | 8 | + @step = CommunityTrackPlugin::Step.create!(:name => 'step1', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => DateTime.now.end_of_day, :start_date => DateTime.now.beginning_of_day, :tool_type => TinyMceArticle.name) |
9 | 9 | ||
10 | user = create_user('testinguser') | 10 | user = create_user('testinguser') |
11 | login_as(user.login) | 11 | login_as(user.login) |
plugins/community_track/test/unit/community_track_plugin/step_test.rb
@@ -9,7 +9,7 @@ class StepTest < ActiveSupport::TestCase | @@ -9,7 +9,7 @@ class StepTest < ActiveSupport::TestCase | ||
9 | @track.add_category(@category) | 9 | @track.add_category(@category) |
10 | @track.save! | 10 | @track.save! |
11 | 11 | ||
12 | - @step = CommunityTrackPlugin::Step.new(:name => 'Step', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => Date.today, :start_date => Date.today) | 12 | + @step = CommunityTrackPlugin::Step.new(:name => 'Step', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => DateTime.now.end_of_day, :start_date => DateTime.now.beginning_of_day - 1.day) |
13 | Delayed::Job.destroy_all | 13 | Delayed::Job.destroy_all |
14 | end | 14 | end |
15 | 15 | ||
@@ -22,39 +22,39 @@ class StepTest < ActiveSupport::TestCase | @@ -22,39 +22,39 @@ class StepTest < ActiveSupport::TestCase | ||
22 | end | 22 | end |
23 | 23 | ||
24 | should 'set accept_comments to false on create' do | 24 | should 'set accept_comments to false on create' do |
25 | - today = Date.today | 25 | + today = DateTime.now |
26 | step = CommunityTrackPlugin::Step.create(:name => 'Step', :body => 'body', :profile => @profile, :parent => @track, :start_date => today, :end_date => today, :published => true) | 26 | step = CommunityTrackPlugin::Step.create(:name => 'Step', :body => 'body', :profile => @profile, :parent => @track, :start_date => today, :end_date => today, :published => true) |
27 | refute step.accept_comments | 27 | refute step.accept_comments |
28 | end | 28 | end |
29 | 29 | ||
30 | should 'do not allow step creation with a parent that is not a track' do | 30 | should 'do not allow step creation with a parent that is not a track' do |
31 | - today = Date.today | 31 | + today = DateTime.now |
32 | blog = fast_create(Blog) | 32 | blog = fast_create(Blog) |
33 | step = CommunityTrackPlugin::Step.new(:name => 'Step', :body => 'body', :profile => @profile, :parent => blog, :start_date => today, :end_date => today, :published => true) | 33 | step = CommunityTrackPlugin::Step.new(:name => 'Step', :body => 'body', :profile => @profile, :parent => blog, :start_date => today, :end_date => today, :published => true) |
34 | refute step.save | 34 | refute step.save |
35 | end | 35 | end |
36 | 36 | ||
37 | should 'do not allow step creation without a parent' do | 37 | should 'do not allow step creation without a parent' do |
38 | - today = Date.today | 38 | + today = DateTime.now |
39 | step = CommunityTrackPlugin::Step.new(:name => 'Step', :body => 'body', :profile => @profile, :parent => nil, :start_date => today, :end_date => today, :published => true) | 39 | step = CommunityTrackPlugin::Step.new(:name => 'Step', :body => 'body', :profile => @profile, :parent => nil, :start_date => today, :end_date => today, :published => true) |
40 | refute step.save | 40 | refute step.save |
41 | end | 41 | end |
42 | 42 | ||
43 | should 'create step if end date is equal to start date' do | 43 | should 'create step if end date is equal to start date' do |
44 | - @step.start_date = Date.today | ||
45 | - @step.end_date = Date.today | 44 | + @step.start_date = DateTime.now |
45 | + @step.end_date = DateTime.now | ||
46 | assert @step.save | 46 | assert @step.save |
47 | end | 47 | end |
48 | 48 | ||
49 | should 'create step if end date is after start date' do | 49 | should 'create step if end date is after start date' do |
50 | - @step.start_date = Date.today | ||
51 | - @step.end_date = Date.today + 1.day | 50 | + @step.start_date = DateTime.now |
51 | + @step.end_date = DateTime.now + 1.day | ||
52 | assert @step.save | 52 | assert @step.save |
53 | end | 53 | end |
54 | 54 | ||
55 | should 'do not create step if end date is before start date' do | 55 | should 'do not create step if end date is before start date' do |
56 | - @step.start_date = Date.today | ||
57 | - @step.end_date = Date.today - 1.day | 56 | + @step.start_date = DateTime.now |
57 | + @step.end_date = DateTime.now - 1.day | ||
58 | refute @step.save | 58 | refute @step.save |
59 | end | 59 | end |
60 | 60 | ||
@@ -71,20 +71,20 @@ class StepTest < ActiveSupport::TestCase | @@ -71,20 +71,20 @@ class StepTest < ActiveSupport::TestCase | ||
71 | end | 71 | end |
72 | 72 | ||
73 | should 'be active if today is between start and end dates' do | 73 | should 'be active if today is between start and end dates' do |
74 | - @step.start_date = Date.today | ||
75 | - @step.end_date = Date.today + 1.day | 74 | + @step.start_date = DateTime.now |
75 | + @step.end_date = DateTime.now + 1.day | ||
76 | assert @step.active? | 76 | assert @step.active? |
77 | end | 77 | end |
78 | 78 | ||
79 | should 'be finished if today is after the end date' do | 79 | should 'be finished if today is after the end date' do |
80 | - @step.start_date = Date.today - 2.day | ||
81 | - @step.end_date = Date.today - 1.day | 80 | + @step.start_date = DateTime.now - 2.day |
81 | + @step.end_date = DateTime.now - 1.day | ||
82 | assert @step.finished? | 82 | assert @step.finished? |
83 | end | 83 | end |
84 | 84 | ||
85 | should 'be waiting if today is before the end date' do | 85 | should 'be waiting if today is before the end date' do |
86 | - @step.start_date = Date.today + 1.day | ||
87 | - @step.end_date = Date.today + 2.day | 86 | + @step.start_date = DateTime.now + 1.day |
87 | + @step.end_date = DateTime.now + 2.day | ||
88 | assert @step.waiting? | 88 | assert @step.waiting? |
89 | end | 89 | end |
90 | 90 | ||
@@ -95,17 +95,17 @@ class StepTest < ActiveSupport::TestCase | @@ -95,17 +95,17 @@ class StepTest < ActiveSupport::TestCase | ||
95 | end | 95 | end |
96 | 96 | ||
97 | should 'create delayed job' do | 97 | should 'create delayed job' do |
98 | - @step.start_date = Date.today | ||
99 | - @step.end_date = Date.today | 98 | + @step.start_date = DateTime.now.beginning_of_day |
99 | + @step.end_date = DateTime.now.end_of_day | ||
100 | @step.accept_comments = false | 100 | @step.accept_comments = false |
101 | @step.schedule_activation | 101 | @step.schedule_activation |
102 | assert_equal 1, Delayed::Job.count | 102 | assert_equal 1, Delayed::Job.count |
103 | - assert_equal @step.start_date, Delayed::Job.first.run_at.to_date | 103 | + assert_equal @step.start_date, Delayed::Job.first.run_at |
104 | end | 104 | end |
105 | 105 | ||
106 | should 'do not duplicate delayed job' do | 106 | should 'do not duplicate delayed job' do |
107 | - @step.start_date = Date.today | ||
108 | - @step.end_date = Date.today | 107 | + @step.start_date = DateTime.now |
108 | + @step.end_date = DateTime.now | ||
109 | @step.schedule_activation | 109 | @step.schedule_activation |
110 | assert_equal 1, Delayed::Job.count | 110 | assert_equal 1, Delayed::Job.count |
111 | @step.schedule_activation | 111 | @step.schedule_activation |
@@ -113,30 +113,30 @@ class StepTest < ActiveSupport::TestCase | @@ -113,30 +113,30 @@ class StepTest < ActiveSupport::TestCase | ||
113 | end | 113 | end |
114 | 114 | ||
115 | should 'create delayed job when a step is saved' do | 115 | should 'create delayed job when a step is saved' do |
116 | - @step.start_date = Date.today | ||
117 | - @step.end_date = Date.today | 116 | + @step.start_date = DateTime.now.beginning_of_day |
117 | + @step.end_date = DateTime.now.end_of_day | ||
118 | @step.save! | 118 | @step.save! |
119 | - assert_equal @step.start_date, Delayed::Job.first.run_at.to_date | 119 | + assert_equal @step.start_date, Delayed::Job.first.run_at |
120 | end | 120 | end |
121 | 121 | ||
122 | should 'create delayed job even if start date has passed' do | 122 | should 'create delayed job even if start date has passed' do |
123 | - @step.start_date = Date.today - 2.days | ||
124 | - @step.end_date = Date.today | 123 | + @step.start_date = DateTime.now - 2.days |
124 | + @step.end_date = DateTime.now.end_of_day | ||
125 | @step.accept_comments = false | 125 | @step.accept_comments = false |
126 | @step.schedule_activation | 126 | @step.schedule_activation |
127 | - assert_equal @step.start_date, Delayed::Job.first.run_at.to_date | 127 | + assert_equal @step.start_date, Delayed::Job.first.run_at |
128 | end | 128 | end |
129 | 129 | ||
130 | should 'create delayed job if end date has passed' do | 130 | should 'create delayed job if end date has passed' do |
131 | - @step.start_date = Date.today - 5.days | ||
132 | - @step.end_date = Date.today - 2.days | 131 | + @step.start_date = DateTime.now - 5.days |
132 | + @step.end_date = DateTime.now - 2.days | ||
133 | @step.schedule_activation | 133 | @step.schedule_activation |
134 | - assert_equal @step.end_date + 1.day, Delayed::Job.first.run_at.to_date | 134 | + assert_equal @step.end_date + 1.day, Delayed::Job.first.run_at |
135 | end | 135 | end |
136 | 136 | ||
137 | should 'do not schedule delayed job if save but do not modify date fields' do | 137 | should 'do not schedule delayed job if save but do not modify date fields' do |
138 | - @step.start_date = Date.today | ||
139 | - @step.end_date = Date.today | 138 | + @step.start_date = DateTime.now |
139 | + @step.end_date = DateTime.now.end_of_day | ||
140 | @step.save! | 140 | @step.save! |
141 | assert_equal 1, Delayed::Job.count | 141 | assert_equal 1, Delayed::Job.count |
142 | Delayed::Job.destroy_all | 142 | Delayed::Job.destroy_all |
@@ -149,13 +149,13 @@ class StepTest < ActiveSupport::TestCase | @@ -149,13 +149,13 @@ class StepTest < ActiveSupport::TestCase | ||
149 | refute @step.position | 149 | refute @step.position |
150 | @step.save! | 150 | @step.save! |
151 | assert_equal 1, @step.position | 151 | assert_equal 1, @step.position |
152 | - step2 = CommunityTrackPlugin::Step.new(:name => 'Step2', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => Date.today, :start_date => Date.today) | 152 | + step2 = CommunityTrackPlugin::Step.new(:name => 'Step2', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => DateTime.now.end_of_day, :start_date => DateTime.now.beginning_of_day) |
153 | step2.save! | 153 | step2.save! |
154 | assert_equal 2, step2.position | 154 | assert_equal 2, step2.position |
155 | end | 155 | end |
156 | 156 | ||
157 | should 'accept comments if step is active' do | 157 | should 'accept comments if step is active' do |
158 | - @step.start_date = Date.today | 158 | + @step.start_date = DateTime.now |
159 | @step.save! | 159 | @step.save! |
160 | refute @step.accept_comments | 160 | refute @step.accept_comments |
161 | @step.toggle_activation | 161 | @step.toggle_activation |
@@ -164,8 +164,8 @@ class StepTest < ActiveSupport::TestCase | @@ -164,8 +164,8 @@ class StepTest < ActiveSupport::TestCase | ||
164 | end | 164 | end |
165 | 165 | ||
166 | should 'do not accept comments if step is not active' do | 166 | should 'do not accept comments if step is not active' do |
167 | - @step.start_date = Date.today + 2.days | ||
168 | - @step.end_date = Date.today + 3.days | 167 | + @step.start_date = DateTime.now + 2.days |
168 | + @step.end_date = DateTime.now + 3.days | ||
169 | @step.save! | 169 | @step.save! |
170 | refute @step.published | 170 | refute @step.published |
171 | @step.toggle_activation | 171 | @step.toggle_activation |
@@ -174,14 +174,14 @@ class StepTest < ActiveSupport::TestCase | @@ -174,14 +174,14 @@ class StepTest < ActiveSupport::TestCase | ||
174 | end | 174 | end |
175 | 175 | ||
176 | should 'do not accept comments if step is not active anymore' do | 176 | should 'do not accept comments if step is not active anymore' do |
177 | - @step.start_date = Date.today | 177 | + @step.end_date = DateTime.now.end_of_day |
178 | @step.save! | 178 | @step.save! |
179 | @step.toggle_activation | 179 | @step.toggle_activation |
180 | @step.reload | 180 | @step.reload |
181 | assert @step.accept_comments | 181 | assert @step.accept_comments |
182 | 182 | ||
183 | - @step.start_date = Date.today - 2.days | ||
184 | - @step.end_date = Date.today - 1.day | 183 | + @step.start_date = DateTime.now - 2.days |
184 | + @step.end_date = DateTime.now - 1.day | ||
185 | @step.save! | 185 | @step.save! |
186 | @step.toggle_activation | 186 | @step.toggle_activation |
187 | @step.reload | 187 | @step.reload |
@@ -203,7 +203,7 @@ class StepTest < ActiveSupport::TestCase | @@ -203,7 +203,7 @@ class StepTest < ActiveSupport::TestCase | ||
203 | end | 203 | end |
204 | 204 | ||
205 | should 'change position to botton if a hidden step becomes visible' do | 205 | should 'change position to botton if a hidden step becomes visible' do |
206 | - step1 = CommunityTrackPlugin::Step.new(:name => 'Step1', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => Date.today, :start_date => Date.today) | 206 | + step1 = CommunityTrackPlugin::Step.new(:name => 'Step1', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => DateTime.now.end_of_day, :start_date => DateTime.now.beginning_of_day) |
207 | step1.save! | 207 | step1.save! |
208 | @step.hidden = true | 208 | @step.hidden = true |
209 | @step.save! | 209 | @step.save! |
@@ -215,7 +215,7 @@ class StepTest < ActiveSupport::TestCase | @@ -215,7 +215,7 @@ class StepTest < ActiveSupport::TestCase | ||
215 | 215 | ||
216 | should 'decrement lower items positions if a step becomes hidden' do | 216 | should 'decrement lower items positions if a step becomes hidden' do |
217 | @step.save! | 217 | @step.save! |
218 | - step1 = CommunityTrackPlugin::Step.new(:name => 'Step1', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => Date.today, :start_date => Date.today) | 218 | + step1 = CommunityTrackPlugin::Step.new(:name => 'Step1', :body => 'body', :profile => @profile, :parent => @track, :published => false, :end_date => DateTime.now.end_of_day, :start_date => DateTime.now.beginning_of_day) |
219 | step1.save! | 219 | step1.save! |
220 | assert_equal 2, step1.position | 220 | assert_equal 2, step1.position |
221 | @step.hidden = true | 221 | @step.hidden = true |
@@ -225,7 +225,7 @@ class StepTest < ActiveSupport::TestCase | @@ -225,7 +225,7 @@ class StepTest < ActiveSupport::TestCase | ||
225 | end | 225 | end |
226 | 226 | ||
227 | should 'do not publish a hidden step' do | 227 | should 'do not publish a hidden step' do |
228 | - @step.start_date = Date.today | 228 | + @step.start_date = DateTime.now |
229 | @step.hidden = true | 229 | @step.hidden = true |
230 | @step.save! | 230 | @step.save! |
231 | refute @step.published | 231 | refute @step.published |
@@ -266,7 +266,7 @@ class StepTest < ActiveSupport::TestCase | @@ -266,7 +266,7 @@ class StepTest < ActiveSupport::TestCase | ||
266 | end | 266 | end |
267 | 267 | ||
268 | should 'enable comments on children when step is activated' do | 268 | should 'enable comments on children when step is activated' do |
269 | - @step.start_date = Date.today | 269 | + @step.start_date = DateTime.now |
270 | @step.save! | 270 | @step.save! |
271 | refute @step.accept_comments | 271 | refute @step.accept_comments |
272 | article = fast_create(Article, :parent_id => @step.id, :profile_id => @step.profile.id, :accept_comments => false) | 272 | article = fast_create(Article, :parent_id => @step.id, :profile_id => @step.profile.id, :accept_comments => false) |
@@ -276,8 +276,7 @@ class StepTest < ActiveSupport::TestCase | @@ -276,8 +276,7 @@ class StepTest < ActiveSupport::TestCase | ||
276 | end | 276 | end |
277 | 277 | ||
278 | should 'enable comments on children when step is active' do | 278 | should 'enable comments on children when step is active' do |
279 | - @step.start_date = Date.today | ||
280 | - @step.start_date = Date.today | 279 | + @step.start_date = DateTime.now |
281 | @step.save! | 280 | @step.save! |
282 | refute @step.accept_comments | 281 | refute @step.accept_comments |
283 | @step.toggle_activation | 282 | @step.toggle_activation |
plugins/event/lib/event_plugin/event_block.rb
@@ -30,13 +30,13 @@ class EventPlugin::EventBlock < Block | @@ -30,13 +30,13 @@ class EventPlugin::EventBlock < Block | ||
30 | events = user.nil? ? events.is_public : events.display_filter(user,nil) | 30 | events = user.nil? ? events.is_public : events.display_filter(user,nil) |
31 | 31 | ||
32 | if future_only | 32 | if future_only |
33 | - events = events.where('start_date >= ?', Date.today) | 33 | + events = events.where('start_date >= ?', DateTime.now.beginning_of_day) |
34 | end | 34 | end |
35 | 35 | ||
36 | if date_distance_limit > 0 | 36 | if date_distance_limit > 0 |
37 | events = events.by_range([ | 37 | events = events.by_range([ |
38 | - Date.today - date_distance_limit, | ||
39 | - Date.today + date_distance_limit | 38 | + DateTime.now.beginning_of_day - date_distance_limit, |
39 | + DateTime.now.beginning_of_day + date_distance_limit | ||
40 | ]) | 40 | ]) |
41 | end | 41 | end |
42 | 42 |
plugins/event/test/functional/event_block_test.rb
@@ -7,7 +7,7 @@ class HomeControllerTest < ActionController::TestCase | @@ -7,7 +7,7 @@ class HomeControllerTest < ActionController::TestCase | ||
7 | @env.enable_plugin('EventPlugin') | 7 | @env.enable_plugin('EventPlugin') |
8 | 8 | ||
9 | @p1 = fast_create(Person, :environment_id => @env.id) | 9 | @p1 = fast_create(Person, :environment_id => @env.id) |
10 | - @e1a = fast_create(Event, :name=>'Event p1 A', :profile_id=>@p1.id) | 10 | + @e1a = Event.create!(:name=>'Event p1 A', :profile =>@p1) |
11 | 11 | ||
12 | box = Box.create!(:owner => @env) | 12 | box = Box.create!(:owner => @env) |
13 | @block = EventPlugin::EventBlock.create!(:box => box) | 13 | @block = EventPlugin::EventBlock.create!(:box => box) |
@@ -19,6 +19,7 @@ class HomeControllerTest < ActionController::TestCase | @@ -19,6 +19,7 @@ class HomeControllerTest < ActionController::TestCase | ||
19 | 19 | ||
20 | should 'see events microdata sturcture' do | 20 | should 'see events microdata sturcture' do |
21 | get :index | 21 | get :index |
22 | +#raise response.body.inspect | ||
22 | assert_select '.event-plugin_event-block ul.events' | 23 | assert_select '.event-plugin_event-block ul.events' |
23 | assert_select ev | 24 | assert_select ev |
24 | assert_select ev + 'a[itemprop="url"]' | 25 | assert_select ev + 'a[itemprop="url"]' |
@@ -33,15 +34,15 @@ class HomeControllerTest < ActionController::TestCase | @@ -33,15 +34,15 @@ class HomeControllerTest < ActionController::TestCase | ||
33 | 34 | ||
34 | should 'see event duration' do | 35 | should 'see event duration' do |
35 | @e1a.slug = 'event1a' | 36 | @e1a.slug = 'event1a' |
36 | - @e1a.start_date = Date.today | ||
37 | - @e1a.end_date = Date.today + 1.day | 37 | + @e1a.start_date = DateTime.now |
38 | + @e1a.end_date = DateTime.now + 1.day | ||
38 | @e1a.save! | 39 | @e1a.save! |
39 | get :index | 40 | get :index |
40 | assert_select ev + 'time.duration[itemprop="endDate"]', /1 day/ | 41 | assert_select ev + 'time.duration[itemprop="endDate"]', /1 day/ |
41 | 42 | ||
42 | @e1a.slug = 'event1a' | 43 | @e1a.slug = 'event1a' |
43 | - @e1a.start_date = Date.today | ||
44 | - @e1a.end_date = Date.today + 2.day | 44 | + @e1a.start_date = DateTime.now |
45 | + @e1a.end_date = DateTime.now + 2.day | ||
45 | @e1a.save! | 46 | @e1a.save! |
46 | get :index | 47 | get :index |
47 | assert_select ev + 'time.duration[itemprop="endDate"]', /2 days/ | 48 | assert_select ev + 'time.duration[itemprop="endDate"]', /2 days/ |
@@ -52,8 +53,8 @@ class HomeControllerTest < ActionController::TestCase | @@ -52,8 +53,8 @@ class HomeControllerTest < ActionController::TestCase | ||
52 | assert_select ev + 'time.duration[itemprop="endDate"]', false | 53 | assert_select ev + 'time.duration[itemprop="endDate"]', false |
53 | 54 | ||
54 | @e1a.slug = 'event1a' | 55 | @e1a.slug = 'event1a' |
55 | - @e1a.start_date = Date.today | ||
56 | - @e1a.end_date = Date.today | 56 | + @e1a.start_date = DateTime.now |
57 | + @e1a.end_date = DateTime.now | ||
57 | @e1a.save! | 58 | @e1a.save! |
58 | get :index | 59 | get :index |
59 | assert_select ev + 'time.duration[itemprop="endDate"]', false | 60 | assert_select ev + 'time.duration[itemprop="endDate"]', false |
plugins/event/views/blocks/event.html.erb
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | 2 | ||
3 | <ul class="events"> | 3 | <ul class="events"> |
4 | <% block.events(user).map do |event| %> | 4 | <% block.events(user).map do |event| %> |
5 | - <% days_left = ( event.start_date - Date.today ).round %> | 5 | + <% days_left = ( (event.start_date - DateTime.now)/60/60/24 ).round %> |
6 | <li itemscope="itemscope" itemtype="http://data-vocabulary.org/Event" class="event"> | 6 | <li itemscope="itemscope" itemtype="http://data-vocabulary.org/Event" class="event"> |
7 | <%= render( | 7 | <%= render( |
8 | :file => 'event_plugin/event_block_item', | 8 | :file => 'event_plugin/event_block_item', |
@@ -0,0 +1,47 @@ | @@ -0,0 +1,47 @@ | ||
1 | +page_tab: | ||
2 | + use_test_app: false | ||
3 | +timeline: | ||
4 | + use_test_app: true | ||
5 | + | ||
6 | +test_users: | ||
7 | + - identifier1 | ||
8 | + - identifier1 | ||
9 | + | ||
10 | +app: | ||
11 | + id: xxx | ||
12 | + secret: xxx | ||
13 | + domain: domainconfigured.net | ||
14 | + | ||
15 | + open_graph: | ||
16 | + namespace: app_name | ||
17 | + objects: | ||
18 | + blog_post: article | ||
19 | + community: community | ||
20 | + enterprise: sse_initiative | ||
21 | + favorite_enterprise: sse_initiative | ||
22 | + forum: discussion | ||
23 | + event: event | ||
24 | + friend: friend | ||
25 | + gallery_image: picture | ||
26 | + person: user | ||
27 | + product: sse_product | ||
28 | + uploaded_file: document | ||
29 | + actions: | ||
30 | + add: add | ||
31 | + comment: comment | ||
32 | + create: create | ||
33 | + favorite: favorite | ||
34 | + like: like | ||
35 | + make_friendship: make_friendship | ||
36 | + upload: upload | ||
37 | + update: update | ||
38 | + start: start | ||
39 | + announce_creation: announce_creation | ||
40 | + announce_new: announce_new | ||
41 | + announce_update: announce_update | ||
42 | + announce_news: announce_news | ||
43 | + | ||
44 | +test_app: | ||
45 | + test_id: xxx | ||
46 | + test_secret: xxx | ||
47 | + |
plugins/fb_app/controllers/myprofile/fb_app_plugin_myprofile_controller.rb
0 → 100644
@@ -0,0 +1,59 @@ | @@ -0,0 +1,59 @@ | ||
1 | +class FbAppPluginMyprofileController < OpenGraphPlugin::MyprofileController | ||
2 | + | ||
3 | + no_design_blocks | ||
4 | + | ||
5 | + before_filter :load_provider | ||
6 | + before_filter :load_auth | ||
7 | + | ||
8 | + def index | ||
9 | + if params[:tabs_added] | ||
10 | + @page_tabs = FbAppPlugin::PageTab.create_from_tabs_added params[:tabs_added], params[:page_tab] | ||
11 | + @page_tab = @page_tabs.first | ||
12 | + redirect_to @page_tab.facebook_url | ||
13 | + end | ||
14 | + end | ||
15 | + | ||
16 | + def show_login | ||
17 | + @status = params[:auth].delete :status | ||
18 | + @logged_auth = FbAppPlugin::Auth.new params[:auth] | ||
19 | + @logged_auth.fetch_user | ||
20 | + if @auth.connected? | ||
21 | + render partial: 'identity', locals: {auth: @logged_auth} | ||
22 | + else | ||
23 | + render nothing: true | ||
24 | + end | ||
25 | + end | ||
26 | + | ||
27 | + def save_auth | ||
28 | + @status = params[:auth].delete :status rescue FbAppPlugin::Auth::Status::Unknown | ||
29 | + if @status == FbAppPlugin::Auth::Status::Connected | ||
30 | + @auth.attributes = params[:auth] | ||
31 | + @auth.save! if @auth.changed? | ||
32 | + else | ||
33 | + @auth.destroy if @auth and @auth.persisted? | ||
34 | + @auth = new_auth | ||
35 | + end | ||
36 | + | ||
37 | + render partial: 'settings' | ||
38 | + end | ||
39 | + | ||
40 | + protected | ||
41 | + | ||
42 | + def load_provider | ||
43 | + @provider = FbAppPlugin.oauth_provider_for environment | ||
44 | + end | ||
45 | + | ||
46 | + def load_auth | ||
47 | + @auth = FbAppPlugin::Auth.where(profile_id: profile.id, provider_id: @provider.id).first | ||
48 | + @auth ||= new_auth | ||
49 | + end | ||
50 | + | ||
51 | + def new_auth | ||
52 | + FbAppPlugin::Auth.new profile: profile, provider: @provider | ||
53 | + end | ||
54 | + | ||
55 | + def context | ||
56 | + :fb_app | ||
57 | + end | ||
58 | + | ||
59 | +end |
plugins/fb_app/controllers/public/fb_app_plugin_controller.rb
0 → 100644
@@ -0,0 +1,24 @@ | @@ -0,0 +1,24 @@ | ||
1 | +class FbAppPluginController < PublicController | ||
2 | + | ||
3 | + no_design_blocks | ||
4 | + | ||
5 | + def index | ||
6 | + end | ||
7 | + | ||
8 | + def myprofile_config | ||
9 | + if logged_in? | ||
10 | + redirect_to controller: :fb_app_plugin_myprofile, profile: user.identifier | ||
11 | + else | ||
12 | + redirect_to controller: :account, action: :login, return_to: url_for(controller: :fb_app_plugin, action: :myprofile_config) | ||
13 | + end | ||
14 | + end | ||
15 | + | ||
16 | + protected | ||
17 | + | ||
18 | + # prevent session reset because X-CSRF not being passed by FB | ||
19 | + # see also https://gist.github.com/toretore/911886 | ||
20 | + def handle_unverified_request | ||
21 | + end | ||
22 | + | ||
23 | +end | ||
24 | + |
plugins/fb_app/controllers/public/fb_app_plugin_page_tab_controller.rb
0 → 100644
@@ -0,0 +1,173 @@ | @@ -0,0 +1,173 @@ | ||
1 | +class FbAppPluginPageTabController < FbAppPluginController | ||
2 | + | ||
3 | + no_design_blocks | ||
4 | + | ||
5 | + before_filter :change_theme | ||
6 | + before_filter :disable_cache | ||
7 | + | ||
8 | + include CatalogHelper | ||
9 | + | ||
10 | + helper ManageProductsHelper | ||
11 | + helper FbAppPlugin::DisplayHelper | ||
12 | + | ||
13 | + def index | ||
14 | + return unless load_page_tabs | ||
15 | + | ||
16 | + if params[:tabs_added] | ||
17 | + @page_tabs = FbAppPlugin::PageTab.create_from_tabs_added params[:tabs_added] | ||
18 | + @page_tab = @page_tabs.first | ||
19 | + redirect_to @page_tab.facebook_url | ||
20 | + elsif @signed_request or @page_id | ||
21 | + if @page_tab.present? | ||
22 | + if product_id = params[:product_id] | ||
23 | + @product = environment.products.find product_id | ||
24 | + @profile = @product.profile | ||
25 | + @inputs = @product.inputs | ||
26 | + @allowed_user = false | ||
27 | + load_catalog | ||
28 | + | ||
29 | + render action: 'product' | ||
30 | + elsif @page_tab.config_type.in? [:profile, :own_profile] | ||
31 | + @profile = @page_tab.value | ||
32 | + | ||
33 | + load_catalog | ||
34 | + render action: 'catalog' unless performed? | ||
35 | + else | ||
36 | + # fake profile for catalog controller | ||
37 | + @profile = environment.enterprise_default_template | ||
38 | + @profile.shopping_cart_settings.enabled = true | ||
39 | + | ||
40 | + base_query = @page_tab.value | ||
41 | + params[:base_query] = base_query | ||
42 | + params[:scope] = 'all' | ||
43 | + | ||
44 | + load_catalog | ||
45 | + render action: 'catalog' unless performed? | ||
46 | + end | ||
47 | + else | ||
48 | + render action: 'first_load' | ||
49 | + end | ||
50 | + else | ||
51 | + # render template | ||
52 | + render action: 'index' | ||
53 | + end | ||
54 | + end | ||
55 | + | ||
56 | + def search_autocomplete | ||
57 | + load_page_tabs | ||
58 | + load_search_autocomplete | ||
59 | + respond_to do |format| | ||
60 | + format.json{ render 'catalog/search_autocomplete' } | ||
61 | + end | ||
62 | + end | ||
63 | + | ||
64 | + def admin | ||
65 | + return redirect_to '/plugin/fb_app/myprofile_config' if params[:page_id].blank? and params[:signed_request].blank? | ||
66 | + return unless load_page_tabs | ||
67 | + | ||
68 | + if request.put? and @page_id.present? | ||
69 | + create_page_tabs if @page_tab.nil? | ||
70 | + | ||
71 | + @page_tab.update_attributes! params[:page_tab] | ||
72 | + | ||
73 | + respond_to do |format| | ||
74 | + format.js{ render action: 'admin' } | ||
75 | + end | ||
76 | + end | ||
77 | + end | ||
78 | + | ||
79 | + def destroy | ||
80 | + @page_tab = FbAppPlugin::PageTab.find params[:id] | ||
81 | + return render_access_denied unless user.present? and (user.is_admin?(environment) or user.is_admin? @page_tab.profile) | ||
82 | + @page_tab.destroy | ||
83 | + render nothing: true | ||
84 | + end | ||
85 | + | ||
86 | + def uninstall | ||
87 | + render text: params.to_yaml | ||
88 | + end | ||
89 | + | ||
90 | + def enterprise_search | ||
91 | + scope = environment.enterprises.enabled.public | ||
92 | + @query = params[:query] | ||
93 | + @profiles = scope.limit(10).order('name ASC'). | ||
94 | + where(['name ILIKE ? OR name ILIKE ? OR identifier LIKE ?', "#{@query}%", "% #{@query}%", "#{@query}%"]) | ||
95 | + render partial: 'open_graph_plugin/myprofile/profile_search', locals: {profiles: @profiles} | ||
96 | + end | ||
97 | + | ||
98 | + # unfortunetely, this needs to be public | ||
99 | + def profile | ||
100 | + @profile | ||
101 | + end | ||
102 | + | ||
103 | + protected | ||
104 | + | ||
105 | + def default_url_options | ||
106 | + {profile: @profile.identifier} if @profile | ||
107 | + end | ||
108 | + | ||
109 | + def load_page_tabs | ||
110 | + @signed_requests = read_param params[:signed_request] | ||
111 | + if @signed_requests.present? | ||
112 | + @datas = [] | ||
113 | + @page_ids = @signed_requests.map do |signed_request| | ||
114 | + @data = FbAppPlugin::Auth.parse_signed_request signed_request | ||
115 | + @datas << @data | ||
116 | + page_id = @data[:page][:id] rescue nil | ||
117 | + if page_id.blank? | ||
118 | + render_not_found | ||
119 | + return false | ||
120 | + end | ||
121 | + page_id | ||
122 | + end | ||
123 | + else | ||
124 | + @page_ids = read_param params[:page_id] | ||
125 | + end | ||
126 | + | ||
127 | + @page_tabs = FbAppPlugin::PageTab.where page_id: @page_ids | ||
128 | + | ||
129 | + @signed_request = @signed_requests.first | ||
130 | + @page_id = @page_ids.first | ||
131 | + @page_tab = @page_tabs.first | ||
132 | + @new_request = @page_tab.blank? | ||
133 | + | ||
134 | + true | ||
135 | + end | ||
136 | + | ||
137 | + def create_page_tabs | ||
138 | + @page_tabs = FbAppPlugin::PageTab.create_from_page_ids @page_ids | ||
139 | + @page_tab ||= @page_tabs.first | ||
140 | + end | ||
141 | + | ||
142 | + def change_theme | ||
143 | + # move to config | ||
144 | + unless theme_responsive? | ||
145 | + @current_theme = 'ees' | ||
146 | + @theme_responsive = true | ||
147 | + end | ||
148 | + @without_pure_chat = true | ||
149 | + end | ||
150 | + def get_layout | ||
151 | + return nil if request.format == :js or request.xhr? | ||
152 | + | ||
153 | + return 'application-responsive' | ||
154 | + end | ||
155 | + | ||
156 | + def disable_cache | ||
157 | + @disable_cache_theme_navigation = true | ||
158 | + end | ||
159 | + | ||
160 | + def load_catalog options = {} | ||
161 | + @use_show_more = true | ||
162 | + catalog_load_index options | ||
163 | + end | ||
164 | + | ||
165 | + def read_param param | ||
166 | + if param.is_a? Hash | ||
167 | + param.values | ||
168 | + else | ||
169 | + Array(param).select{ |p| p.present? } | ||
170 | + end | ||
171 | + end | ||
172 | + | ||
173 | +end |
plugins/fb_app/db/migrate/20140319135819_create_fb_app_page_tab_config.rb
0 → 100644
@@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
1 | +class CreateFbAppPageTabConfig < ActiveRecord::Migration | ||
2 | + | ||
3 | + def change | ||
4 | + create_table :fb_app_plugin_page_tab_configs do |t| | ||
5 | + t.string :page_id | ||
6 | + t.text :config, default: {}.to_yaml | ||
7 | + t.integer :profile_id | ||
8 | + | ||
9 | + t.timestamps | ||
10 | + end | ||
11 | + add_index :fb_app_plugin_page_tab_configs, [:profile_id] | ||
12 | + add_index :fb_app_plugin_page_tab_configs, [:page_id] | ||
13 | + add_index :fb_app_plugin_page_tab_configs, [:page_id, :profile_id] | ||
14 | + end | ||
15 | + | ||
16 | +end |
@@ -0,0 +1,28 @@ | @@ -0,0 +1,28 @@ | ||
1 | +require_dependency 'profile' | ||
2 | +# hate to wrte this, but without Noosfero::Plugin::Settings is loaded instead | ||
3 | +require 'fb_app_plugin/settings' | ||
4 | + | ||
5 | +# attr_accessible must be defined on subclasses | ||
6 | +Profile.descendants.each do |subclass| | ||
7 | + subclass.class_eval do | ||
8 | + attr_accessible :fb_app_settings | ||
9 | + end | ||
10 | +end | ||
11 | + | ||
12 | +class Profile | ||
13 | + | ||
14 | + def fb_app_settings attrs = {} | ||
15 | + @fb_app_settings ||= FbAppPlugin::Settings.new self, attrs | ||
16 | + attrs.each{ |a, v| @fb_app_settings.send "#{a}=", v } | ||
17 | + @fb_app_settings | ||
18 | + end | ||
19 | + alias_method :fb_app_settings=, :fb_app_settings | ||
20 | + | ||
21 | + has_many :fb_app_page_tabs, class_name: 'FbAppPlugin::PageTab' | ||
22 | + | ||
23 | + def fb_app_auth | ||
24 | + provider = FbAppPlugin.oauth_provider_for self.environment | ||
25 | + self.oauth_auths.where(provider_id: provider.id).first | ||
26 | + end | ||
27 | + | ||
28 | +end |
@@ -0,0 +1,87 @@ | @@ -0,0 +1,87 @@ | ||
1 | +module FbAppPlugin | ||
2 | + | ||
3 | + extend Noosfero::Plugin::ParentMethods | ||
4 | + | ||
5 | + def self.plugin_name | ||
6 | + I18n.t 'fb_app_plugin.lib.plugin.name' | ||
7 | + end | ||
8 | + | ||
9 | + def self.plugin_description | ||
10 | + I18n.t 'fb_app_plugin.lib.plugin.description' | ||
11 | + end | ||
12 | + | ||
13 | + def self.config | ||
14 | + @config ||= HashWithIndifferentAccess.new(YAML.load File.read("#{File.dirname __FILE__}/../config.yml")) rescue {} | ||
15 | + end | ||
16 | + | ||
17 | + def self.test_users | ||
18 | + @test_users ||= self.config[:test_users] | ||
19 | + end | ||
20 | + def self.test_user? user | ||
21 | + user and (self.test_users.blank? or self.test_users.include? user.identifier) | ||
22 | + end | ||
23 | + | ||
24 | + def self.debug? actor=nil | ||
25 | + self.test_user? actor | ||
26 | + end | ||
27 | + | ||
28 | + def self.scope user | ||
29 | + if self.test_user? user then 'publish_actions' else '' end | ||
30 | + end | ||
31 | + | ||
32 | + def self.oauth_provider_for environment | ||
33 | + return unless self.config.present? | ||
34 | + | ||
35 | + @oauth_providers ||= {} | ||
36 | + @oauth_providers[environment] ||= begin | ||
37 | + app_id = self.timeline_app_credentials[:id].to_s | ||
38 | + app_secret = self.timeline_app_credentials[:secret].to_s | ||
39 | + | ||
40 | + client = environment.oauth_providers.where(client_id: app_id).first | ||
41 | + # attributes that may be changed by the user | ||
42 | + client ||= OauthClientPlugin::Provider.new strategy: 'facebook', | ||
43 | + name: 'FB App', site: 'https://facebook.com' | ||
44 | + | ||
45 | + # attributes that should not change | ||
46 | + client.attributes = { | ||
47 | + client_id: app_id, client_secret: app_secret, | ||
48 | + environment_id: environment.id, | ||
49 | + } | ||
50 | + client.save! if client.changed? | ||
51 | + | ||
52 | + client | ||
53 | + end | ||
54 | + end | ||
55 | + | ||
56 | + def self.open_graph_config | ||
57 | + return unless self.config.present? | ||
58 | + | ||
59 | + @open_graph_config ||= begin | ||
60 | + key = if self.config[:timeline][:use_test_app] then :test_app else :app end | ||
61 | + self.config[key][:open_graph] | ||
62 | + end | ||
63 | + end | ||
64 | + | ||
65 | + def self.credentials app = :app | ||
66 | + return unless self.config.present? | ||
67 | + {id: self.config[app][:id], secret: self.config[app][:secret]} | ||
68 | + end | ||
69 | + | ||
70 | + def self.timeline_app_credentials | ||
71 | + return unless self.config.present? | ||
72 | + @timeline_app_credentials ||= begin | ||
73 | + key = if self.config[:timeline][:use_test_app] then :test_app else :app end | ||
74 | + self.credentials key | ||
75 | + end | ||
76 | + end | ||
77 | + | ||
78 | + def self.page_tab_app_credentials | ||
79 | + return unless self.config.present? | ||
80 | + @page_tab_app_credentials ||= begin | ||
81 | + key = if self.config[:page_tab][:use_test_app] then :test_app else :app end | ||
82 | + self.credentials key | ||
83 | + end | ||
84 | + end | ||
85 | + | ||
86 | +end | ||
87 | + |
@@ -0,0 +1,35 @@ | @@ -0,0 +1,35 @@ | ||
1 | +class FbAppPlugin::Base < Noosfero::Plugin | ||
2 | + | ||
3 | + def stylesheet? | ||
4 | + true | ||
5 | + end | ||
6 | + | ||
7 | + def js_files | ||
8 | + ['fb_app.js'].map{ |j| "javascripts/#{j}" } | ||
9 | + end | ||
10 | + | ||
11 | + def head_ending | ||
12 | + return unless FbAppPlugin.config.present? | ||
13 | + lambda do | ||
14 | + tag 'meta', property: 'fb:app_id', content: FbAppPlugin.config[:app][:id] | ||
15 | + end | ||
16 | + end | ||
17 | + | ||
18 | + def control_panel_buttons | ||
19 | + return unless FbAppPlugin.config.present? | ||
20 | + { title: FbAppPlugin.plugin_name, icon: 'fb-app', url: {host: FbAppPlugin.config[:app][:domain], profile: profile.identifier, controller: :fb_app_plugin_myprofile} } | ||
21 | + end | ||
22 | + | ||
23 | +end | ||
24 | + | ||
25 | +ActiveSupport.on_load :open_graph_plugin do | ||
26 | + OpenGraphPlugin::Stories.register_publisher FbAppPlugin::Publisher.default | ||
27 | +end | ||
28 | +ActiveSupport.on_load :metadata_plugin do | ||
29 | + MetadataPlugin::Controllers.class_eval do | ||
30 | + def fb_app_plugin_page_tab | ||
31 | + :@product | ||
32 | + end | ||
33 | + end | ||
34 | +end | ||
35 | + |
@@ -0,0 +1,51 @@ | @@ -0,0 +1,51 @@ | ||
1 | +module FbAppPlugin::DisplayHelper | ||
2 | + | ||
3 | + extend CatalogHelper | ||
4 | + | ||
5 | + def fb_url_options options | ||
6 | + options.merge! page_id: @page_ids, signed_request: @signed_requests, id: nil | ||
7 | + end | ||
8 | + | ||
9 | + def url_for options = {} | ||
10 | + return super unless options.is_a? Hash | ||
11 | + if options[:controller] == :catalog | ||
12 | + options[:controller] = :fb_app_plugin_page_tab | ||
13 | + options = fb_url_options options | ||
14 | + end | ||
15 | + super | ||
16 | + end | ||
17 | + | ||
18 | + protected | ||
19 | + | ||
20 | + def product_url_options product, options = {} | ||
21 | + options = options.merge! product.url | ||
22 | + options = options.merge! controller: :fb_app_plugin_page_tab, product_id: product.id, action: :index | ||
23 | + options = fb_url_options options | ||
24 | + unless Rails.env.development? | ||
25 | + domain = FbAppPlugin.config[:app][:domain] | ||
26 | + options[:host] = domain if domain.present? | ||
27 | + options[:protocol] = '//' | ||
28 | + end | ||
29 | + options | ||
30 | + end | ||
31 | + def product_path product, options = {} | ||
32 | + url = url_for product_url_options(product, options = {}) | ||
33 | + url | ||
34 | + end | ||
35 | + | ||
36 | + def link_to_product product, opts = {} | ||
37 | + url_opts = opts.delete(:url_options) || {} | ||
38 | + url_opts = product_url_options product, url_opts | ||
39 | + url = params.merge url_opts | ||
40 | + link_to content_tag('span', product.name), url, | ||
41 | + opts.merge(target: '') | ||
42 | + end | ||
43 | + | ||
44 | + def link_to name = nil, options = nil, html_options = nil, &block | ||
45 | + html_options ||= {} | ||
46 | + options[:protocol] = '//' if options.is_a? Hash | ||
47 | + html_options[:target] ||= '_parent' | ||
48 | + super | ||
49 | + end | ||
50 | + | ||
51 | +end |
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +# add target attribute to links | ||
2 | +class FbAppPlugin::LinkRenderer < WillPaginate::ActionView::LinkRenderer | ||
3 | + | ||
4 | + def prepare collection, options, template | ||
5 | + super | ||
6 | + end | ||
7 | + | ||
8 | + protected | ||
9 | + | ||
10 | + def default_url_params | ||
11 | + {target: ''} | ||
12 | + end | ||
13 | + | ||
14 | +end |
@@ -0,0 +1,17 @@ | @@ -0,0 +1,17 @@ | ||
1 | +# Publishing examples on console | ||
2 | +# pub=FbAppPlugin::Publisher.default; u=Profile['brauliobo']; a=Article.find 307591 | ||
3 | +# pub.publish_story a, u, :announce_news_from_a_sse_initiative | ||
4 | +# | ||
5 | +# pub=FbAppPlugin::Publisher.default; u=Profile['brauliobo']; f=FavoriteEnterprisePerson.last | ||
6 | +# pub.publish_story f, u, :favorite_a_sse_initiative | ||
7 | +# | ||
8 | +class FbAppPlugin::Publisher < OpenGraphPlugin::Publisher | ||
9 | + | ||
10 | + def publish_story object_data, actor, story | ||
11 | + OpenGraphPlugin.context = FbAppPlugin::Activity.context | ||
12 | + a = FbAppPlugin::Activity.new object_data: object_data, actor: actor, story: story | ||
13 | + a.dispatch_publications | ||
14 | + a.save | ||
15 | + end | ||
16 | + | ||
17 | +end |
@@ -0,0 +1,83 @@ | @@ -0,0 +1,83 @@ | ||
1 | + | ||
2 | +"en-US": &en-US | ||
3 | + | ||
4 | + fb_app_plugin: | ||
5 | + lib: | ||
6 | + plugin: | ||
7 | + name: 'Facebook integration' | ||
8 | + description: 'Use the app for Facebook!' | ||
9 | + models: | ||
10 | + page_tab: | ||
11 | + types: | ||
12 | + own_profile: 'Catalog from this enterprise' | ||
13 | + profile: 'Catalog from a single SSE enterprise' | ||
14 | + other_profile: 'Catalog from other SSE enterprise' | ||
15 | + profiles: 'Catalog from more than one SSE enterprise' | ||
16 | + query: 'Catalog of products chosen by filter or free search' | ||
17 | + views: | ||
18 | + myprofile: | ||
19 | + checking_auth: 'Checking authorization' | ||
20 | + different_login: 'But you are logged in on facebook as:' | ||
21 | + current_login: 'You are logged in on facebook as:' | ||
22 | + connect: 'I want to install the app for Facebook' | ||
23 | + logged_connect: 'Connect this Facebook account with my user %{profile}' | ||
24 | + disconnect: 'Disconnect' | ||
25 | + connect_to_another: 'Conect to another Facebook account' | ||
26 | + reconnect: 'Reconnect' | ||
27 | + | ||
28 | + timeline: | ||
29 | + heading: 'Publishing in your facebook' | ||
30 | + add: 'Save timeline post settings' | ||
31 | + explanation_title: 'Soon!' | ||
32 | + explanation_text: 'In a short time, your actions will become new posts in your Facebook timeline!' | ||
33 | + organization_redirect: '%{redirect_link} to post updates %{type}, conect your personal profile to Facebook.' | ||
34 | + organization_from_enterprise: 'from this enterprise' | ||
35 | + organization_from_community: 'from this community' | ||
36 | + redirect_link: 'Click here' | ||
37 | + | ||
38 | + catalogs: | ||
39 | + heading: 'Solidarity Economy Catalog' | ||
40 | + new: 'Add new catalog' | ||
41 | + catalog_title_label: 'Title' | ||
42 | + catalog_subtitle_label: 'Subtitle' | ||
43 | + catalog_type_chooser_label: 'Type' | ||
44 | + profile_chooser_label: 'Enterprise' | ||
45 | + profiles_chooser_label: 'Enterprises' | ||
46 | + query_label: "Criteria for the catalog's products" | ||
47 | + query_help: "Write the words separated by space, and click at the button below to save." | ||
48 | + edit_button: "Edit catalog '%{catalog_title}'" | ||
49 | + remove_button: "Remove catalog '%{catalog_title}'" | ||
50 | + cancel_button: 'Cancel' | ||
51 | + confirm_removal: "<p>Warning: To really remove a catalog, you must go to the facebook page where it is, click at '<u>add or remove tabs</u>' and then remove the tab.</p><p>After that, you can come here and remove the catalog from our settings.</p><p>Are you sure you want to remove this catalog?</p>" | ||
52 | + confirm_removal_button: 'Yes, I want to delete this catalog' | ||
53 | + confirm_disconnect: "<p>Warning: If you disconnect your account to this Facebook profile, all your catalogs will also be removed</p><p>And then, to really remove your catalogs (if they were created), you must go to the facebook page where it is, click at '<u>add or remove tabs</u>' and then remove the tab.</p><p>After that, you can come here and disconnect.</p><p>Are you sure you want to disconnect your account to this Facebook profile?</p>" | ||
54 | + confirm_disconnect_button: 'Yes, I want to disconnect my account from this Facebook profile' | ||
55 | + | ||
56 | + catalog: | ||
57 | + see_page: 'See page on Facebook' | ||
58 | + | ||
59 | + error: | ||
60 | + empty_title: 'Please add a title to your catalog.' | ||
61 | + empty_settings: 'Please choose SSE enterprises or search terms or filters for your catalog.' | ||
62 | + | ||
63 | + page_tab: | ||
64 | + edit_catalog: 'Edit catalog' | ||
65 | + add: 'Add catalog to one of my pages on Facebook' | ||
66 | + save: 'Save' | ||
67 | + added_notice: "Congratulations: You've just published a new SSE catalog in Facebook!" | ||
68 | + profile: | ||
69 | + placeholder: "select an enterprise" | ||
70 | + profiles: | ||
71 | + placeholder: "select the enterprises" | ||
72 | + query: | ||
73 | + placeholder: "select the category or type the search terms" | ||
74 | + back_to_catalog: 'Back to catalog' | ||
75 | + footer1: "" | ||
76 | + footer2: "" | ||
77 | + | ||
78 | + | ||
79 | +'en_US': | ||
80 | + <<: *en-US | ||
81 | +'en': | ||
82 | + <<: *en-US | ||
83 | + |
@@ -0,0 +1,82 @@ | @@ -0,0 +1,82 @@ | ||
1 | + | ||
2 | +"pt-BR": &pt-BR | ||
3 | + | ||
4 | + fb_app_plugin: | ||
5 | + lib: | ||
6 | + plugin: | ||
7 | + name: 'App Facebook' | ||
8 | + description: 'Divulgue suas ações no Facebook!' | ||
9 | + models: | ||
10 | + page_tab: | ||
11 | + types: | ||
12 | + own_profile: 'Vitrine deste empreendimento' | ||
13 | + profile: 'Vitrine de um único empreendimento' | ||
14 | + other_profile: 'Vitrine de outro empreendimento' | ||
15 | + profiles: 'Vitrine de mais de um empreendimento' | ||
16 | + query: 'Vitrine de produtos ou serviços escolhidos por busca livre' | ||
17 | + views: | ||
18 | + myprofile: | ||
19 | + checking_auth: 'Verificando autorização de acesso' | ||
20 | + different_login: 'Mas você está logado no facebook como:' | ||
21 | + current_login: 'Você está logado no facebook como:' | ||
22 | + connect: 'Quero instalar o App Facebook' | ||
23 | + logged_connect: 'Quero conectar esta conta do Facebook com meu usuário %{profile}' | ||
24 | + disconnect: 'Desconectar' | ||
25 | + connect_to_another: 'Conectar a outra conta do Facebook' | ||
26 | + reconnect: 'Reconectar' | ||
27 | + | ||
28 | + timeline: | ||
29 | + heading: 'Postar ações no facebook automaticamente:' | ||
30 | + add: 'Salvar configuração de postagens na timeline' | ||
31 | + explanation_title: 'Aguarde!' | ||
32 | + explanation_text: 'Em breve, este aplicativo poderá postar automaticamente no seu face as ações que você fizer! Por exemplo: quando você postar novos conteúdos no seu blog, enviar uma imagem, etc.' | ||
33 | + organization_redirect: '%{redirect_link} para postar atualizações %{type}, conecte o seu perfil pessoal ao facebook.' | ||
34 | + organization_from_enterprise: 'deste empreendimento' | ||
35 | + organization_from_community: 'desta comunidade' | ||
36 | + redirect_link: 'Clique aqui' | ||
37 | + | ||
38 | + catalogs: | ||
39 | + heading: 'Vitrine' | ||
40 | + new: 'Criar nova vitrine' | ||
41 | + catalog_title_label: 'Título' | ||
42 | + catalog_subtitle_label: 'Subtítulo' | ||
43 | + catalog_type_chooser_label: 'Tipo' | ||
44 | + profile_chooser_label: 'Empreendimento' | ||
45 | + profiles_chooser_label: 'Empreendimentos' | ||
46 | + query_label: "Critérios para os produtos/serviços da vitrine" | ||
47 | + query_help: "Escreva as palavras separadas por espaço, e clique no botão abaixo para salvar." | ||
48 | + edit_button: "Editar vitrine '%{catalog_title}'" | ||
49 | + remove_button: "Remover vitrine '%{catalog_title}'" | ||
50 | + cancel_button: 'Cancelar' | ||
51 | + confirm_removal: "<p><b>Atenção:</b> Para realmente apagar uma vitrine, você deve primeiro ir para a página do Facebook onde está a sua vitrine e seguir este roteiro:</p><ul><li>Clique em '<i>Mais</i>' na barra da página, depois em '<i>Gerenciar guias</i>', e então em '<i>Adicionar ou remover guias</i>'.</li><li>. Clique no X para remover.</li></ul><p>Depois disso, você pode vir aqui e pedir para remover a vitrine.</p><p>Se você já removeu lá no Facebook, tem certeza que quer remover esta vitrine?</p>" | ||
52 | + confirm_removal_button: 'Sim, quero remover esta vitrine' | ||
53 | + confirm_disconnect: "<p><b>Atenção:</b> Se você desconectar, vai apagar todas as vitrines. Portanto, para realmente apagar suas vitrines, você deve primeiro ir para a página do Facebook onde está a sua vitrine e seguir este roteiro:</p><ul><li>Clique em '<i>Mais</i>' na barra da página, depois em '<i>Gerenciar guias</i>', e então em '<i>Adicionar ou remover guias</i>'.</li><li>Clique no X para remover.</li></ul><p>Depois disso, você pode vir aqui e desconectar.</p><p>Tem certeza que quer desconectar?</p>" | ||
54 | + confirm_disconnect_button: 'Sim, quero desconectar' | ||
55 | + | ||
56 | + catalog: | ||
57 | + see_page: 'Ver página no facebook' | ||
58 | + | ||
59 | + error: | ||
60 | + empty_title: 'Por favor, coloque um título para a sua vitrine.' | ||
61 | + empty_settings: 'Por favor, selecione os empreendimentos solidários ou os termos de busca para sua vitrine.' | ||
62 | + | ||
63 | + page_tab: | ||
64 | + edit_catalog: 'Editar vitrine' | ||
65 | + add: 'Criar vitrine em uma página sua no Facebook' | ||
66 | + save: 'Salvar' | ||
67 | + added_notice: 'Parabéns: você acaba de publicar uma nova vitrine da Economia Solidária no Facebook!' | ||
68 | + profile: | ||
69 | + placeholder: "selecione um empreendimento" | ||
70 | + profiles: | ||
71 | + placeholder: "selecione os empreendimentos" | ||
72 | + query: | ||
73 | + placeholder: "escolha os produtos e serviços por palavras de busca" | ||
74 | + back_to_catalog: 'Voltar à vitrine' | ||
75 | + footer1: "" | ||
76 | + footer2: "" | ||
77 | + | ||
78 | +'pt_BR': | ||
79 | + <<: *pt-BR | ||
80 | +'pt': | ||
81 | + <<: *pt-BR | ||
82 | + |
@@ -0,0 +1,54 @@ | @@ -0,0 +1,54 @@ | ||
1 | +class FbAppPlugin::Activity < OpenGraphPlugin::Activity | ||
2 | + | ||
3 | + self.context = :fb_app | ||
4 | + self.actions = FbAppPlugin.open_graph_config[:actions] | ||
5 | + self.objects = FbAppPlugin.open_graph_config[:objects] | ||
6 | + | ||
7 | + # this avoid to many saves for frequent fail cases | ||
8 | + attr_accessor :should_save | ||
9 | + validates_presence_of :should_save | ||
10 | + | ||
11 | + def self.scrape object_data_url | ||
12 | + params = {id: object_data_url, scrape: true, method: 'post'} | ||
13 | + url = "http://graph.facebook.com?#{params.to_query}" | ||
14 | + Net::HTTP.get URI.parse(url) | ||
15 | + end | ||
16 | + def scrape | ||
17 | + self.class.scrape self.object_data_url | ||
18 | + end | ||
19 | + | ||
20 | + def publish! actor = self.actor | ||
21 | + print_debug "fb_app: action #{self.action}, object_type #{self.object_type}" if debug? actor | ||
22 | + | ||
23 | + auth = actor.fb_app_auth | ||
24 | + return if auth.blank? or auth.expired? | ||
25 | + print_debug "fb_app: Auth found and is valid" if debug? actor | ||
26 | + | ||
27 | + # always update the object to expire facebook cache | ||
28 | + Thread.new{ self.scrape } | ||
29 | + | ||
30 | + return if self.defs[:on] == :update and self.recent_publish? actor, self.object_type, self.object_data_url | ||
31 | + print_debug "fb_app: no recent publication found, making new" if debug? actor | ||
32 | + | ||
33 | + self.should_save = true | ||
34 | + | ||
35 | + namespace = FbAppPlugin.open_graph_config[:namespace] | ||
36 | + # to_str is needed to ensure String, see https://github.com/nov/fb_graph2/issues/88 | ||
37 | + params = {self.object_type => self.object_data_url.to_str} | ||
38 | + params['fb:explicitly_shared'] = 'true' unless self.defs[:tracker] | ||
39 | + print_debug "fb_app: publishing with params #{params.inspect}" if debug? actor | ||
40 | + | ||
41 | + me = FbGraph2::User.me auth.access_token | ||
42 | + me.og_action! "#{namespace}:#{action}", params | ||
43 | + | ||
44 | + self.published_at = Time.now | ||
45 | + print_debug "fb_app: published with success" if debug? actor | ||
46 | + end | ||
47 | + | ||
48 | + protected | ||
49 | + | ||
50 | + def debug? actor=nil | ||
51 | + super or FbAppPlugin.debug? actor | ||
52 | + end | ||
53 | + | ||
54 | +end |
@@ -0,0 +1,89 @@ | @@ -0,0 +1,89 @@ | ||
1 | +class FbAppPlugin::Auth < OauthClientPlugin::Auth | ||
2 | + | ||
3 | + module Status | ||
4 | + Connected = 'connected' | ||
5 | + NotAuthorized = 'not_authorized' | ||
6 | + Unknown = 'unknown' | ||
7 | + end | ||
8 | + | ||
9 | + settings_items :signed_request | ||
10 | + settings_items :fb_user | ||
11 | + | ||
12 | + attr_accessible :provider_user_id, :signed_request | ||
13 | + | ||
14 | + before_create :update_user | ||
15 | + before_create :exchange_token | ||
16 | + after_create :schedule_exchange_token | ||
17 | + after_destroy :destroy_page_tabs | ||
18 | + before_validation :set_enabled | ||
19 | + | ||
20 | + validates_presence_of :provider_user_id | ||
21 | + validates_uniqueness_of :provider_user_id, scope: :profile_id | ||
22 | + | ||
23 | + def self.parse_signed_request signed_request, credentials = FbAppPlugin.page_tab_app_credentials | ||
24 | + secret = credentials[:secret] rescue '' | ||
25 | + request = Facebook::SignedRequest.new signed_request, secret: secret | ||
26 | + request.data | ||
27 | + end | ||
28 | + | ||
29 | + def status | ||
30 | + if self.access_token.present? and self.not_expired? then Status::Connected else Status::NotAuthorized end | ||
31 | + end | ||
32 | + def not_authorized? | ||
33 | + self.status == Status::NotAuthorized | ||
34 | + end | ||
35 | + def connected? | ||
36 | + self.status == Status::Connected | ||
37 | + end | ||
38 | + | ||
39 | + def exchange_token | ||
40 | + app_id = FbAppPlugin.timeline_app_credentials[:id] | ||
41 | + app_secret = FbAppPlugin.timeline_app_credentials[:secret] | ||
42 | + fb_auth = FbGraph2::Auth.new app_id, app_secret | ||
43 | + fb_auth.fb_exchange_token = self.access_token | ||
44 | + | ||
45 | + access_token = fb_auth.access_token! | ||
46 | + self.access_token = access_token.access_token | ||
47 | + self.expires_in = access_token.expires_in | ||
48 | + # refresh user and its stored access token | ||
49 | + self.fetch_user | ||
50 | + end | ||
51 | + | ||
52 | + def exchange_token! | ||
53 | + self.exchange_token | ||
54 | + self.save! | ||
55 | + end | ||
56 | + | ||
57 | + def signed_request_data | ||
58 | + self.class.parse_signed_request self.signed_request | ||
59 | + end | ||
60 | + | ||
61 | + def fetch_user | ||
62 | + fb_user = FbGraph2::User.me self.access_token | ||
63 | + self.fb_user = fb_user.fetch | ||
64 | + end | ||
65 | + def update_user | ||
66 | + self.fb_user = self.fetch_user | ||
67 | + end | ||
68 | + | ||
69 | + protected | ||
70 | + | ||
71 | + def destroy_page_tabs | ||
72 | + self.profile.fb_app_page_tabs.destroy_all | ||
73 | + end | ||
74 | + | ||
75 | + def exchange_token_and_reschedule! | ||
76 | + self.exchange_token! | ||
77 | + self.schedule_exchange_token | ||
78 | + end | ||
79 | + | ||
80 | + def schedule_exchange_token | ||
81 | + self.delay(run_at: self.expires_at - 2.weeks).exchange_token_and_reschedule! | ||
82 | + end | ||
83 | + | ||
84 | + def set_enabled | ||
85 | + self.enabled = self.not_expired? | ||
86 | + end | ||
87 | + | ||
88 | +end | ||
89 | + |
@@ -0,0 +1,111 @@ | @@ -0,0 +1,111 @@ | ||
1 | +class FbAppPlugin::PageTab < ActiveRecord::Base | ||
2 | + | ||
3 | + # FIXME: rename table to match model | ||
4 | + self.table_name = :fb_app_plugin_page_tab_configs | ||
5 | + | ||
6 | + attr_accessible :owner_profile, :profile_id, :page_id, | ||
7 | + :config_type, :profile_ids, :query, | ||
8 | + :title, :subtitle | ||
9 | + | ||
10 | + belongs_to :owner_profile, foreign_key: :profile_id, class_name: 'Profile' | ||
11 | + | ||
12 | + acts_as_having_settings field: :config | ||
13 | + | ||
14 | + ConfigTypes = [:profile, :profiles, :query] | ||
15 | + EnterpriseConfigTypes = [:own_profile] + ConfigTypes | ||
16 | + | ||
17 | + validates_presence_of :page_id | ||
18 | + validates_uniqueness_of :page_id | ||
19 | + validates_inclusion_of :config_type, in: ConfigTypes + EnterpriseConfigTypes | ||
20 | + | ||
21 | + def self.page_ids_from_tabs_added tabs_added | ||
22 | + tabs_added.map{ |id, value| id } | ||
23 | + end | ||
24 | + | ||
25 | + def self.create_from_page_ids page_ids, attrs = {} | ||
26 | + attrs.delete :page_id | ||
27 | + page_ids.map do |page_id| | ||
28 | + page_tab = FbAppPlugin::PageTab.where(page_id: page_id).first | ||
29 | + page_tab ||= FbAppPlugin::PageTab.new page_id: page_id | ||
30 | + page_tab.update_attributes! attrs | ||
31 | + page_tab | ||
32 | + end | ||
33 | + end | ||
34 | + def self.create_from_tabs_added tabs_added, attrs = {} | ||
35 | + page_ids = self.page_ids_from_tabs_added tabs_added | ||
36 | + self.create_from_page_ids page_ids, attrs | ||
37 | + end | ||
38 | + | ||
39 | + def self.facebook_url page_id | ||
40 | + "https://facebook.com/#{page_id}?sk=app_#{FbAppPlugin.page_tab_app_credentials[:id]}" | ||
41 | + end | ||
42 | + | ||
43 | + def facebook_url | ||
44 | + self.class.facebook_url self.page_id | ||
45 | + end | ||
46 | + | ||
47 | + def types | ||
48 | + if self.owner_profile.present? and self.owner_profile.enterprise? then EnterpriseConfigTypes else ConfigTypes end | ||
49 | + end | ||
50 | + | ||
51 | + def config_type | ||
52 | + self.config[:type] || (self.owner_profile ? :own_profile : :profile) | ||
53 | + end | ||
54 | + def config_type= value | ||
55 | + self.config[:type] = value.to_sym | ||
56 | + end | ||
57 | + | ||
58 | + def value | ||
59 | + case self.config_type | ||
60 | + when :profiles | ||
61 | + self.profiles.map(&:identifier).join(' OR ') | ||
62 | + else | ||
63 | + self.send self.config_type | ||
64 | + end | ||
65 | + end | ||
66 | + def blank? | ||
67 | + self.value.blank? rescue true | ||
68 | + end | ||
69 | + | ||
70 | + def own_profile | ||
71 | + self.owner_profile | ||
72 | + end | ||
73 | + def profiles | ||
74 | + Profile.where(id: self.config[:profile_ids]) | ||
75 | + end | ||
76 | + def profile | ||
77 | + self.profiles.first | ||
78 | + end | ||
79 | + def profile_ids | ||
80 | + self.profiles.map(&:id) | ||
81 | + end | ||
82 | + def query | ||
83 | + self.config[:query] | ||
84 | + end | ||
85 | + | ||
86 | + def title | ||
87 | + self.config[:title] | ||
88 | + end | ||
89 | + def title= value | ||
90 | + self.config[:title] = value | ||
91 | + end | ||
92 | + | ||
93 | + def subtitle | ||
94 | + self.config[:subtitle] | ||
95 | + end | ||
96 | + def subtitle= value | ||
97 | + self.config[:subtitle] = value | ||
98 | + end | ||
99 | + | ||
100 | + def profile_ids= ids | ||
101 | + ids = ids.to_s.split(',') | ||
102 | + self.config[:type] = if ids.size == 1 then :profile else :profiles end | ||
103 | + self.config[:profile_ids] = ids | ||
104 | + end | ||
105 | + | ||
106 | + def query= value | ||
107 | + self.config[:type] = :query | ||
108 | + self.config[:query] = value | ||
109 | + end | ||
110 | + | ||
111 | +end |
plugins/fb_app/plugins/fb_app/lib/ext/action_tracker_model.rb
0 → 100644
722 Bytes
3.31 KB
18.3 KB
plugins/fb_app/public/javascripts/bootstrap-tokenfield.js
0 → 100644
@@ -0,0 +1,1032 @@ | @@ -0,0 +1,1032 @@ | ||
1 | +/*! | ||
2 | + * bootstrap-tokenfield | ||
3 | + * https://github.com/sliptree/bootstrap-tokenfield | ||
4 | + * Copyright 2013-2014 Sliptree and other contributors; Licensed MIT | ||
5 | + */ | ||
6 | + | ||
7 | +(function (factory) { | ||
8 | + if (typeof define === 'function' && define.amd) { | ||
9 | + // AMD. Register as an anonymous module. | ||
10 | + define(['jquery'], factory); | ||
11 | + } else if (typeof exports === 'object') { | ||
12 | + // For CommonJS and CommonJS-like environments where a window with jQuery | ||
13 | + // is present, execute the factory with the jQuery instance from the window object | ||
14 | + // For environments that do not inherently posses a window with a document | ||
15 | + // (such as Node.js), expose a Tokenfield-making factory as module.exports | ||
16 | + // This accentuates the need for the creation of a real window or passing in a jQuery instance | ||
17 | + // e.g. require("bootstrap-tokenfield")(window); or require("bootstrap-tokenfield")($); | ||
18 | + module.exports = global.window && global.window.$ ? | ||
19 | + factory( global.window.$ ) : | ||
20 | + function( input ) { | ||
21 | + if ( !input.$ && !input.fn ) { | ||
22 | + throw new Error( "Tokenfield requires a window object with jQuery or a jQuery instance" ); | ||
23 | + } | ||
24 | + return factory( input.$ || input ); | ||
25 | + }; | ||
26 | + } else { | ||
27 | + // Browser globals | ||
28 | + factory(jQuery, window); | ||
29 | + } | ||
30 | +}(function ($, window) { | ||
31 | + | ||
32 | + "use strict"; // jshint ;_; | ||
33 | + | ||
34 | + /* TOKENFIELD PUBLIC CLASS DEFINITION | ||
35 | + * ============================== */ | ||
36 | + | ||
37 | + var Tokenfield = function (element, options) { | ||
38 | + var _self = this | ||
39 | + | ||
40 | + this.$element = $(element) | ||
41 | + this.textDirection = this.$element.css('direction'); | ||
42 | + | ||
43 | + // Extend options | ||
44 | + this.options = $.extend(true, {}, $.fn.tokenfield.defaults, { tokens: this.$element.val() }, this.$element.data(), options) | ||
45 | + | ||
46 | + // Setup delimiters and trigger keys | ||
47 | + this._delimiters = (typeof this.options.delimiter === 'string') ? [this.options.delimiter] : this.options.delimiter | ||
48 | + this._triggerKeys = $.map(this._delimiters, function (delimiter) { | ||
49 | + return delimiter.charCodeAt(0); | ||
50 | + }); | ||
51 | + this._firstDelimiter = this._delimiters[0]; | ||
52 | + | ||
53 | + // Check for whitespace, dash and special characters | ||
54 | + var whitespace = $.inArray(' ', this._delimiters) | ||
55 | + , dash = $.inArray('-', this._delimiters) | ||
56 | + | ||
57 | + if (whitespace >= 0) | ||
58 | + this._delimiters[whitespace] = '\\s' | ||
59 | + | ||
60 | + if (dash >= 0) { | ||
61 | + delete this._delimiters[dash] | ||
62 | + this._delimiters.unshift('-') | ||
63 | + } | ||
64 | + | ||
65 | + var specialCharacters = ['\\', '$', '[', '{', '^', '.', '|', '?', '*', '+', '(', ')'] | ||
66 | + $.each(this._delimiters, function (index, character) { | ||
67 | + var pos = $.inArray(character, specialCharacters) | ||
68 | + if (pos >= 0) _self._delimiters[index] = '\\' + character; | ||
69 | + }); | ||
70 | + | ||
71 | + // Store original input width | ||
72 | + var elRules = (window && typeof window.getMatchedCSSRules === 'function') ? window.getMatchedCSSRules( element ) : null | ||
73 | + , elStyleWidth = element.style.width | ||
74 | + , elCSSWidth | ||
75 | + , elWidth = this.$element.width() | ||
76 | + | ||
77 | + if (elRules) { | ||
78 | + $.each( elRules, function (i, rule) { | ||
79 | + if (rule.style.width) { | ||
80 | + elCSSWidth = rule.style.width; | ||
81 | + } | ||
82 | + }); | ||
83 | + } | ||
84 | + | ||
85 | + // Move original input out of the way | ||
86 | + var hidingPosition = $('body').css('direction') === 'rtl' ? 'right' : 'left', | ||
87 | + originalStyles = { position: this.$element.css('position') }; | ||
88 | + originalStyles[hidingPosition] = this.$element.css(hidingPosition); | ||
89 | + | ||
90 | + this.$element | ||
91 | + .data('original-styles', originalStyles) | ||
92 | + .data('original-tabindex', this.$element.prop('tabindex')) | ||
93 | + .css('position', 'absolute') | ||
94 | + .css(hidingPosition, '-10000px') | ||
95 | + .prop('tabindex', -1) | ||
96 | + | ||
97 | + // Create a wrapper | ||
98 | + this.$wrapper = $('<div class="tokenfield form-control" />') | ||
99 | + if (this.$element.hasClass('input-lg')) this.$wrapper.addClass('input-lg') | ||
100 | + if (this.$element.hasClass('input-sm')) this.$wrapper.addClass('input-sm') | ||
101 | + if (this.textDirection === 'rtl') this.$wrapper.addClass('rtl') | ||
102 | + | ||
103 | + // Create a new input | ||
104 | + var id = this.$element.prop('id') || new Date().getTime() + '' + Math.floor((1 + Math.random()) * 100) | ||
105 | + this.$input = $('<input type="'+this.options.inputType+'" class="token-input" autocomplete="off" />') | ||
106 | + .appendTo( this.$wrapper ) | ||
107 | + .prop( 'placeholder', this.$element.prop('placeholder') ) | ||
108 | + .prop( 'id', id + '-tokenfield' ) | ||
109 | + .prop( 'tabindex', this.$element.data('original-tabindex') ) | ||
110 | + | ||
111 | + // Re-route original input label to new input | ||
112 | + var $label = $( 'label[for="' + this.$element.prop('id') + '"]' ) | ||
113 | + if ( $label.length ) { | ||
114 | + $label.prop( 'for', this.$input.prop('id') ) | ||
115 | + } | ||
116 | + | ||
117 | + // Set up a copy helper to handle copy & paste | ||
118 | + this.$copyHelper = $('<input type="text" />').css('position', 'absolute').css(hidingPosition, '-10000px').prop('tabindex', -1).prependTo( this.$wrapper ) | ||
119 | + | ||
120 | + // Set wrapper width | ||
121 | + if (elStyleWidth) { | ||
122 | + this.$wrapper.css('width', elStyleWidth); | ||
123 | + } | ||
124 | + else if (elCSSWidth) { | ||
125 | + this.$wrapper.css('width', elCSSWidth); | ||
126 | + } | ||
127 | + // If input is inside inline-form with no width set, set fixed width | ||
128 | + else if (this.$element.parents('.form-inline').length) { | ||
129 | + this.$wrapper.width( elWidth ) | ||
130 | + } | ||
131 | + | ||
132 | + // Set tokenfield disabled, if original or fieldset input is disabled | ||
133 | + if (this.$element.prop('disabled') || this.$element.parents('fieldset[disabled]').length) { | ||
134 | + this.disable(); | ||
135 | + } | ||
136 | + | ||
137 | + // Set tokenfield readonly, if original input is readonly | ||
138 | + if (this.$element.prop('readonly')) { | ||
139 | + this.readonly(); | ||
140 | + } | ||
141 | + | ||
142 | + // Set up mirror for input auto-sizing | ||
143 | + this.$mirror = $('<span style="position:absolute; top:-999px; left:0; white-space:pre;"/>'); | ||
144 | + this.$input.css('min-width', this.options.minWidth + 'px') | ||
145 | + $.each([ | ||
146 | + 'fontFamily', | ||
147 | + 'fontSize', | ||
148 | + 'fontWeight', | ||
149 | + 'fontStyle', | ||
150 | + 'letterSpacing', | ||
151 | + 'textTransform', | ||
152 | + 'wordSpacing', | ||
153 | + 'textIndent' | ||
154 | + ], function (i, val) { | ||
155 | + _self.$mirror[0].style[val] = _self.$input.css(val); | ||
156 | + }); | ||
157 | + this.$mirror.appendTo( 'body' ) | ||
158 | + | ||
159 | + // Insert tokenfield to HTML | ||
160 | + this.$wrapper.insertBefore( this.$element ) | ||
161 | + this.$element.prependTo( this.$wrapper ) | ||
162 | + | ||
163 | + // Calculate inner input width | ||
164 | + this.update() | ||
165 | + | ||
166 | + // Create initial tokens, if any | ||
167 | + this.setTokens(this.options.tokens, false, ! this.$element.val() && this.options.tokens ) | ||
168 | + | ||
169 | + // Start listening to events | ||
170 | + this.listen() | ||
171 | + | ||
172 | + // Initialize autocomplete, if necessary | ||
173 | + if ( ! $.isEmptyObject( this.options.autocomplete ) ) { | ||
174 | + var side = this.textDirection === 'rtl' ? 'right' : 'left' | ||
175 | + , autocompleteOptions = $.extend({ | ||
176 | + minLength: this.options.showAutocompleteOnFocus ? 0 : null, | ||
177 | + position: { my: side + " top", at: side + " bottom", of: this.$wrapper } | ||
178 | + }, this.options.autocomplete ) | ||
179 | + | ||
180 | + this.$input.autocomplete( autocompleteOptions ) | ||
181 | + } | ||
182 | + | ||
183 | + // Initialize typeahead, if necessary | ||
184 | + if ( ! $.isEmptyObject( this.options.typeahead ) ) { | ||
185 | + | ||
186 | + var typeaheadOptions = this.options.typeahead | ||
187 | + , defaults = { | ||
188 | + minLength: this.options.showAutocompleteOnFocus ? 0 : null | ||
189 | + } | ||
190 | + , args = $.isArray( typeaheadOptions ) ? typeaheadOptions : [typeaheadOptions, typeaheadOptions] | ||
191 | + | ||
192 | + args[0] = $.extend( {}, defaults, args[0] ) | ||
193 | + | ||
194 | + this.$input.typeahead.apply( this.$input, args ) | ||
195 | + this.typeahead = true | ||
196 | + } | ||
197 | + } | ||
198 | + | ||
199 | + Tokenfield.prototype = { | ||
200 | + | ||
201 | + constructor: Tokenfield | ||
202 | + | ||
203 | + , createToken: function (attrs, triggerChange) { | ||
204 | + var _self = this | ||
205 | + | ||
206 | + if (typeof attrs === 'string') { | ||
207 | + attrs = { value: attrs, label: attrs } | ||
208 | + } else { | ||
209 | + // Copy objects to prevent contamination of data sources. | ||
210 | + attrs = $.extend( {}, attrs ) | ||
211 | + } | ||
212 | + | ||
213 | + if (typeof triggerChange === 'undefined') { | ||
214 | + triggerChange = true | ||
215 | + } | ||
216 | + | ||
217 | + // Normalize label and value | ||
218 | + attrs.value = $.trim(attrs.value.toString()); | ||
219 | + attrs.label = attrs.label && attrs.label.length ? $.trim(attrs.label) : attrs.value | ||
220 | + | ||
221 | + // Bail out if has no value or label, or label is too short | ||
222 | + if (!attrs.value.length || !attrs.label.length || attrs.label.length <= this.options.minLength) return | ||
223 | + | ||
224 | + // Bail out if maximum number of tokens is reached | ||
225 | + if (this.options.limit && this.getTokens().length >= this.options.limit) return | ||
226 | + | ||
227 | + // Allow changing token data before creating it | ||
228 | + var createEvent = $.Event('tokenfield:createtoken', { attrs: attrs }) | ||
229 | + this.$element.trigger(createEvent) | ||
230 | + | ||
231 | + // Bail out if there if attributes are empty or event was defaultPrevented | ||
232 | + if (!createEvent.attrs || createEvent.isDefaultPrevented()) return | ||
233 | + | ||
234 | + var $token = $('<div class="token" />') | ||
235 | + .append('<span class="token-label" />') | ||
236 | + .append('<a href="#" class="close" tabindex="-1">×</a>') | ||
237 | + .data('attrs', attrs) | ||
238 | + | ||
239 | + // Insert token into HTML | ||
240 | + if (this.$input.hasClass('tt-input')) { | ||
241 | + // If the input has typeahead enabled, insert token before it's parent | ||
242 | + this.$input.parent().before( $token ) | ||
243 | + } else { | ||
244 | + this.$input.before( $token ) | ||
245 | + } | ||
246 | + | ||
247 | + // Temporarily set input width to minimum | ||
248 | + this.$input.css('width', this.options.minWidth + 'px') | ||
249 | + | ||
250 | + var $tokenLabel = $token.find('.token-label') | ||
251 | + , $closeButton = $token.find('.close') | ||
252 | + | ||
253 | + // Determine maximum possible token label width | ||
254 | + if (!this.maxTokenWidth) { | ||
255 | + this.maxTokenWidth = | ||
256 | + this.$wrapper.width() - $closeButton.outerWidth() - | ||
257 | + parseInt($closeButton.css('margin-left'), 10) - | ||
258 | + parseInt($closeButton.css('margin-right'), 10) - | ||
259 | + parseInt($token.css('border-left-width'), 10) - | ||
260 | + parseInt($token.css('border-right-width'), 10) - | ||
261 | + parseInt($token.css('padding-left'), 10) - | ||
262 | + parseInt($token.css('padding-right'), 10) | ||
263 | + parseInt($tokenLabel.css('border-left-width'), 10) - | ||
264 | + parseInt($tokenLabel.css('border-right-width'), 10) - | ||
265 | + parseInt($tokenLabel.css('padding-left'), 10) - | ||
266 | + parseInt($tokenLabel.css('padding-right'), 10) | ||
267 | + parseInt($tokenLabel.css('margin-left'), 10) - | ||
268 | + parseInt($tokenLabel.css('margin-right'), 10) | ||
269 | + } | ||
270 | + | ||
271 | + //$tokenLabel.css('max-width', this.maxTokenWidth) | ||
272 | + if (this.options.html) | ||
273 | + $tokenLabel.html(attrs.label) | ||
274 | + else | ||
275 | + $tokenLabel.text(attrs.label) | ||
276 | + | ||
277 | + // Listen to events on token | ||
278 | + $token | ||
279 | + .on('mousedown', function (e) { | ||
280 | + if (_self._disabled || _self._readonly) return false | ||
281 | + _self.preventDeactivation = true | ||
282 | + }) | ||
283 | + .on('click', function (e) { | ||
284 | + if (_self._disabled || _self._readonly) return false | ||
285 | + _self.preventDeactivation = false | ||
286 | + | ||
287 | + if (e.ctrlKey || e.metaKey) { | ||
288 | + e.preventDefault() | ||
289 | + return _self.toggle( $token ) | ||
290 | + } | ||
291 | + | ||
292 | + _self.activate( $token, e.shiftKey, e.shiftKey ) | ||
293 | + }) | ||
294 | + .on('dblclick', function (e) { | ||
295 | + if (_self._disabled || _self._readonly || !_self.options.allowEditing ) return false | ||
296 | + _self.edit( $token ) | ||
297 | + }) | ||
298 | + | ||
299 | + $closeButton | ||
300 | + .on('click', $.proxy(this.remove, this)) | ||
301 | + | ||
302 | + // Trigger createdtoken event on the original field | ||
303 | + // indicating that the token is now in the DOM | ||
304 | + this.$element.trigger($.Event('tokenfield:createdtoken', { | ||
305 | + attrs: attrs, | ||
306 | + relatedTarget: $token.get(0) | ||
307 | + })) | ||
308 | + | ||
309 | + // Trigger change event on the original field | ||
310 | + if (triggerChange) { | ||
311 | + this.$element.val( this.getTokensList() ).trigger( $.Event('change', { initiator: 'tokenfield' }) ) | ||
312 | + } | ||
313 | + | ||
314 | + // Update tokenfield dimensions | ||
315 | + this.update() | ||
316 | + | ||
317 | + // Return original element | ||
318 | + return this.$element.get(0) | ||
319 | + } | ||
320 | + | ||
321 | + , setTokens: function (tokens, add, triggerChange) { | ||
322 | + if (!tokens) return | ||
323 | + | ||
324 | + if (!add) this.$wrapper.find('.token').remove() | ||
325 | + | ||
326 | + if (typeof triggerChange === 'undefined') { | ||
327 | + triggerChange = true | ||
328 | + } | ||
329 | + | ||
330 | + if (typeof tokens === 'string') { | ||
331 | + if (this._delimiters.length) { | ||
332 | + // Split based on delimiters | ||
333 | + tokens = tokens.split( new RegExp( '[' + this._delimiters.join('') + ']' ) ) | ||
334 | + } else { | ||
335 | + tokens = [tokens]; | ||
336 | + } | ||
337 | + } | ||
338 | + | ||
339 | + var _self = this | ||
340 | + $.each(tokens, function (i, attrs) { | ||
341 | + _self.createToken(attrs, triggerChange) | ||
342 | + }) | ||
343 | + | ||
344 | + return this.$element.get(0) | ||
345 | + } | ||
346 | + | ||
347 | + , getTokenData: function($token) { | ||
348 | + var data = $token.map(function() { | ||
349 | + var $token = $(this); | ||
350 | + return $token.data('attrs') | ||
351 | + }).get(); | ||
352 | + | ||
353 | + if (data.length == 1) { | ||
354 | + data = data[0]; | ||
355 | + } | ||
356 | + | ||
357 | + return data; | ||
358 | + } | ||
359 | + | ||
360 | + , getTokens: function(active) { | ||
361 | + var self = this | ||
362 | + , tokens = [] | ||
363 | + , activeClass = active ? '.active' : '' // get active tokens only | ||
364 | + this.$wrapper.find( '.token' + activeClass ).each( function() { | ||
365 | + tokens.push( self.getTokenData( $(this) ) ) | ||
366 | + }) | ||
367 | + return tokens | ||
368 | + } | ||
369 | + | ||
370 | + , getTokensList: function(delimiter, beautify, active) { | ||
371 | + delimiter = delimiter || this._firstDelimiter | ||
372 | + beautify = ( typeof beautify !== 'undefined' && beautify !== null ) ? beautify : this.options.beautify | ||
373 | + | ||
374 | + var separator = delimiter + ( beautify && delimiter !== ' ' ? ' ' : '') | ||
375 | + return $.map( this.getTokens(active), function (token) { | ||
376 | + return token.value | ||
377 | + }).join(separator) | ||
378 | + } | ||
379 | + | ||
380 | + , getInput: function() { | ||
381 | + return this.$input.val() | ||
382 | + } | ||
383 | + | ||
384 | + , listen: function () { | ||
385 | + var _self = this | ||
386 | + | ||
387 | + this.$element | ||
388 | + .on('change', $.proxy(this.change, this)) | ||
389 | + | ||
390 | + this.$wrapper | ||
391 | + .on('mousedown',$.proxy(this.focusInput, this)) | ||
392 | + | ||
393 | + this.$input | ||
394 | + .on('focus', $.proxy(this.focus, this)) | ||
395 | + .on('blur', $.proxy(this.blur, this)) | ||
396 | + .on('paste', $.proxy(this.paste, this)) | ||
397 | + .on('keydown', $.proxy(this.keydown, this)) | ||
398 | + .on('keypress', $.proxy(this.keypress, this)) | ||
399 | + .on('keyup', $.proxy(this.keyup, this)) | ||
400 | + | ||
401 | + this.$copyHelper | ||
402 | + .on('focus', $.proxy(this.focus, this)) | ||
403 | + .on('blur', $.proxy(this.blur, this)) | ||
404 | + .on('keydown', $.proxy(this.keydown, this)) | ||
405 | + .on('keyup', $.proxy(this.keyup, this)) | ||
406 | + | ||
407 | + // Secondary listeners for input width calculation | ||
408 | + this.$input | ||
409 | + .on('keypress', $.proxy(this.update, this)) | ||
410 | + .on('keyup', $.proxy(this.update, this)) | ||
411 | + | ||
412 | + this.$input | ||
413 | + .on('autocompletecreate', function() { | ||
414 | + // Set minimum autocomplete menu width | ||
415 | + var $_menuElement = $(this).data('ui-autocomplete').menu.element | ||
416 | + | ||
417 | + var minWidth = _self.$wrapper.outerWidth() - | ||
418 | + parseInt( $_menuElement.css('border-left-width'), 10 ) - | ||
419 | + parseInt( $_menuElement.css('border-right-width'), 10 ) | ||
420 | + | ||
421 | + $_menuElement.css( 'min-width', minWidth + 'px' ) | ||
422 | + }) | ||
423 | + .on('autocompleteselect', function (e, ui) { | ||
424 | + if (_self.createToken( ui.item )) { | ||
425 | + _self.$input.val('') | ||
426 | + if (_self.$input.data( 'edit' )) { | ||
427 | + _self.unedit(true) | ||
428 | + } | ||
429 | + } | ||
430 | + return false | ||
431 | + }) | ||
432 | + .on('typeahead:selected typeahead:autocompleted', function (e, datum, dataset) { | ||
433 | + // Create token | ||
434 | + if (_self.createToken( datum )) { | ||
435 | + _self.$input.typeahead('val', '') | ||
436 | + if (_self.$input.data( 'edit' )) { | ||
437 | + _self.unedit(true) | ||
438 | + } | ||
439 | + } | ||
440 | + }) | ||
441 | + | ||
442 | + // Listen to window resize | ||
443 | + $(window).on('resize', $.proxy(this.update, this )) | ||
444 | + | ||
445 | + } | ||
446 | + | ||
447 | + , keydown: function (e) { | ||
448 | + | ||
449 | + if (!this.focused) return | ||
450 | + | ||
451 | + var _self = this | ||
452 | + | ||
453 | + switch(e.keyCode) { | ||
454 | + case 8: // backspace | ||
455 | + if (!this.$input.is(document.activeElement)) break | ||
456 | + this.lastInputValue = this.$input.val() | ||
457 | + break | ||
458 | + | ||
459 | + case 37: // left arrow | ||
460 | + leftRight( this.textDirection === 'rtl' ? 'next': 'prev' ) | ||
461 | + break | ||
462 | + | ||
463 | + case 38: // up arrow | ||
464 | + upDown('prev') | ||
465 | + break | ||
466 | + | ||
467 | + case 39: // right arrow | ||
468 | + leftRight( this.textDirection === 'rtl' ? 'prev': 'next' ) | ||
469 | + break | ||
470 | + | ||
471 | + case 40: // down arrow | ||
472 | + upDown('next') | ||
473 | + break | ||
474 | + | ||
475 | + case 65: // a (to handle ctrl + a) | ||
476 | + if (this.$input.val().length > 0 || !(e.ctrlKey || e.metaKey)) break | ||
477 | + this.activateAll() | ||
478 | + e.preventDefault() | ||
479 | + break | ||
480 | + | ||
481 | + case 9: // tab | ||
482 | + case 13: // enter | ||
483 | + | ||
484 | + // We will handle creating tokens from autocomplete in autocomplete events | ||
485 | + if (this.$input.data('ui-autocomplete') && this.$input.data('ui-autocomplete').menu.element.find("li:has(a.ui-state-focus), li.ui-state-focus").length) break | ||
486 | + | ||
487 | + // We will handle creating tokens from typeahead in typeahead events | ||
488 | + if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-cursor').length ) break | ||
489 | + if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-hint').val() && this.$wrapper.find('.tt-hint').val().length) break | ||
490 | + | ||
491 | + // Create token | ||
492 | + if (this.$input.is(document.activeElement) && this.$input.val().length || this.$input.data('edit')) { | ||
493 | + return this.createTokensFromInput(e, this.$input.data('edit')); | ||
494 | + } | ||
495 | + | ||
496 | + // Edit token | ||
497 | + if (e.keyCode === 13) { | ||
498 | + if (!this.$copyHelper.is(document.activeElement) || this.$wrapper.find('.token.active').length !== 1) break | ||
499 | + if (!_self.options.allowEditing) break | ||
500 | + this.edit( this.$wrapper.find('.token.active') ) | ||
501 | + } | ||
502 | + } | ||
503 | + | ||
504 | + function leftRight(direction) { | ||
505 | + if (_self.$input.is(document.activeElement)) { | ||
506 | + if (_self.$input.val().length > 0) return | ||
507 | + | ||
508 | + direction += 'All' | ||
509 | + var $token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction]('.token:first') : _self.$input[direction]('.token:first') | ||
510 | + if (!$token.length) return | ||
511 | + | ||
512 | + _self.preventInputFocus = true | ||
513 | + _self.preventDeactivation = true | ||
514 | + | ||
515 | + _self.activate( $token ) | ||
516 | + e.preventDefault() | ||
517 | + | ||
518 | + } else { | ||
519 | + _self[direction]( e.shiftKey ) | ||
520 | + e.preventDefault() | ||
521 | + } | ||
522 | + } | ||
523 | + | ||
524 | + function upDown(direction) { | ||
525 | + if (!e.shiftKey) return | ||
526 | + | ||
527 | + if (_self.$input.is(document.activeElement)) { | ||
528 | + if (_self.$input.val().length > 0) return | ||
529 | + | ||
530 | + var $token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction + 'All']('.token:first') : _self.$input[direction + 'All']('.token:first') | ||
531 | + if (!$token.length) return | ||
532 | + | ||
533 | + _self.activate( $token ) | ||
534 | + } | ||
535 | + | ||
536 | + var opposite = direction === 'prev' ? 'next' : 'prev' | ||
537 | + , position = direction === 'prev' ? 'first' : 'last' | ||
538 | + | ||
539 | + _self.$firstActiveToken[opposite + 'All']('.token').each(function() { | ||
540 | + _self.deactivate( $(this) ) | ||
541 | + }) | ||
542 | + | ||
543 | + _self.activate( _self.$wrapper.find('.token:' + position), true, true ) | ||
544 | + e.preventDefault() | ||
545 | + } | ||
546 | + | ||
547 | + this.lastKeyDown = e.keyCode | ||
548 | + } | ||
549 | + | ||
550 | + , keypress: function(e) { | ||
551 | + | ||
552 | + // Comma | ||
553 | + if ($.inArray( e.which, this._triggerKeys) !== -1 && this.$input.is(document.activeElement)) { | ||
554 | + if (this.$input.val()) { | ||
555 | + this.createTokensFromInput(e) | ||
556 | + } | ||
557 | + return false; | ||
558 | + } | ||
559 | + } | ||
560 | + | ||
561 | + , keyup: function (e) { | ||
562 | + this.preventInputFocus = false | ||
563 | + | ||
564 | + if (!this.focused) return | ||
565 | + | ||
566 | + switch(e.keyCode) { | ||
567 | + case 8: // backspace | ||
568 | + if (this.$input.is(document.activeElement)) { | ||
569 | + if (this.$input.val().length || this.lastInputValue.length && this.lastKeyDown === 8) break | ||
570 | + | ||
571 | + this.preventDeactivation = true | ||
572 | + var $prevToken = this.$input.hasClass('tt-input') ? this.$input.parent().prevAll('.token:first') : this.$input.prevAll('.token:first') | ||
573 | + | ||
574 | + if (!$prevToken.length) break | ||
575 | + | ||
576 | + this.activate( $prevToken ) | ||
577 | + } else { | ||
578 | + this.remove(e) | ||
579 | + } | ||
580 | + break | ||
581 | + | ||
582 | + case 46: // delete | ||
583 | + this.remove(e, 'next') | ||
584 | + break | ||
585 | + } | ||
586 | + this.lastKeyUp = e.keyCode | ||
587 | + } | ||
588 | + | ||
589 | + , focus: function (e) { | ||
590 | + this.focused = true | ||
591 | + this.$wrapper.addClass('focus') | ||
592 | + | ||
593 | + if (this.$input.is(document.activeElement)) { | ||
594 | + this.$wrapper.find('.active').removeClass('active') | ||
595 | + this.$firstActiveToken = null | ||
596 | + | ||
597 | + if (this.options.showAutocompleteOnFocus) { | ||
598 | + this.search() | ||
599 | + } | ||
600 | + } | ||
601 | + } | ||
602 | + | ||
603 | + , blur: function (e) { | ||
604 | + | ||
605 | + this.focused = false | ||
606 | + this.$wrapper.removeClass('focus') | ||
607 | + | ||
608 | + if (!this.preventDeactivation && !this.$element.is(document.activeElement)) { | ||
609 | + this.$wrapper.find('.active').removeClass('active') | ||
610 | + this.$firstActiveToken = null | ||
611 | + } | ||
612 | + | ||
613 | + if (!this.preventCreateTokens && (this.$input.data('edit') && !this.$input.is(document.activeElement) || this.options.createTokensOnBlur )) { | ||
614 | + this.createTokensFromInput(e) | ||
615 | + } | ||
616 | + | ||
617 | + this.preventDeactivation = false | ||
618 | + this.preventCreateTokens = false | ||
619 | + } | ||
620 | + | ||
621 | + , paste: function (e) { | ||
622 | + var _self = this | ||
623 | + | ||
624 | + // Add tokens to existing ones | ||
625 | + if (_self.options.allowPasting) { | ||
626 | + setTimeout(function () { | ||
627 | + _self.createTokensFromInput(e) | ||
628 | + }, 1) | ||
629 | + } | ||
630 | + } | ||
631 | + | ||
632 | + , change: function (e) { | ||
633 | + if ( e.initiator === 'tokenfield' ) return // Prevent loops | ||
634 | + | ||
635 | + this.setTokens( this.$element.val() ) | ||
636 | + } | ||
637 | + | ||
638 | + , createTokensFromInput: function (e, focus) { | ||
639 | + if (this.$input.val().length < this.options.minLength) | ||
640 | + return // No input, simply return | ||
641 | + | ||
642 | + var tokensBefore = this.getTokensList() | ||
643 | + this.setTokens( this.$input.val(), true ) | ||
644 | + | ||
645 | + if (tokensBefore == this.getTokensList() && this.$input.val().length) | ||
646 | + return false // No tokens were added, do nothing (prevent form submit) | ||
647 | + | ||
648 | + if (this.$input.hasClass('tt-input')) { | ||
649 | + // Typeahead acts weird when simply setting input value to empty, | ||
650 | + // so we set the query to empty instead | ||
651 | + this.$input.typeahead('val', '') | ||
652 | + } else { | ||
653 | + this.$input.val('') | ||
654 | + } | ||
655 | + | ||
656 | + if (this.$input.data( 'edit' )) { | ||
657 | + this.unedit(focus) | ||
658 | + } | ||
659 | + | ||
660 | + return false // Prevent form being submitted | ||
661 | + } | ||
662 | + | ||
663 | + , next: function (add) { | ||
664 | + if (add) { | ||
665 | + var $firstActiveToken = this.$wrapper.find('.active:first') | ||
666 | + , deactivate = $firstActiveToken && this.$firstActiveToken ? $firstActiveToken.index() < this.$firstActiveToken.index() : false | ||
667 | + | ||
668 | + if (deactivate) return this.deactivate( $firstActiveToken ) | ||
669 | + } | ||
670 | + | ||
671 | + var $lastActiveToken = this.$wrapper.find('.active:last') | ||
672 | + , $nextToken = $lastActiveToken.nextAll('.token:first') | ||
673 | + | ||
674 | + if (!$nextToken.length) { | ||
675 | + this.$input.focus() | ||
676 | + return | ||
677 | + } | ||
678 | + | ||
679 | + this.activate($nextToken, add) | ||
680 | + } | ||
681 | + | ||
682 | + , prev: function (add) { | ||
683 | + | ||
684 | + if (add) { | ||
685 | + var $lastActiveToken = this.$wrapper.find('.active:last') | ||
686 | + , deactivate = $lastActiveToken && this.$firstActiveToken ? $lastActiveToken.index() > this.$firstActiveToken.index() : false | ||
687 | + | ||
688 | + if (deactivate) return this.deactivate( $lastActiveToken ) | ||
689 | + } | ||
690 | + | ||
691 | + var $firstActiveToken = this.$wrapper.find('.active:first') | ||
692 | + , $prevToken = $firstActiveToken.prevAll('.token:first') | ||
693 | + | ||
694 | + if (!$prevToken.length) { | ||
695 | + $prevToken = this.$wrapper.find('.token:first') | ||
696 | + } | ||
697 | + | ||
698 | + if (!$prevToken.length && !add) { | ||
699 | + this.$input.focus() | ||
700 | + return | ||
701 | + } | ||
702 | + | ||
703 | + this.activate( $prevToken, add ) | ||
704 | + } | ||
705 | + | ||
706 | + , activate: function ($token, add, multi, remember) { | ||
707 | + | ||
708 | + if (!$token) return | ||
709 | + | ||
710 | + if (typeof remember === 'undefined') var remember = true | ||
711 | + | ||
712 | + if (multi) var add = true | ||
713 | + | ||
714 | + this.$copyHelper.focus() | ||
715 | + | ||
716 | + if (!add) { | ||
717 | + this.$wrapper.find('.active').removeClass('active') | ||
718 | + if (remember) { | ||
719 | + this.$firstActiveToken = $token | ||
720 | + } else { | ||
721 | + delete this.$firstActiveToken | ||
722 | + } | ||
723 | + } | ||
724 | + | ||
725 | + if (multi && this.$firstActiveToken) { | ||
726 | + // Determine first active token and the current tokens indicies | ||
727 | + // Account for the 1 hidden textarea by subtracting 1 from both | ||
728 | + var i = this.$firstActiveToken.index() - 2 | ||
729 | + , a = $token.index() - 2 | ||
730 | + , _self = this | ||
731 | + | ||
732 | + this.$wrapper.find('.token').slice( Math.min(i, a) + 1, Math.max(i, a) ).each( function() { | ||
733 | + _self.activate( $(this), true ) | ||
734 | + }) | ||
735 | + } | ||
736 | + | ||
737 | + $token.addClass('active') | ||
738 | + this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() | ||
739 | + } | ||
740 | + | ||
741 | + , activateAll: function() { | ||
742 | + var _self = this | ||
743 | + | ||
744 | + this.$wrapper.find('.token').each( function (i) { | ||
745 | + _self.activate($(this), i !== 0, false, false) | ||
746 | + }) | ||
747 | + } | ||
748 | + | ||
749 | + , deactivate: function($token) { | ||
750 | + if (!$token) return | ||
751 | + | ||
752 | + $token.removeClass('active') | ||
753 | + this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() | ||
754 | + } | ||
755 | + | ||
756 | + , toggle: function($token) { | ||
757 | + if (!$token) return | ||
758 | + | ||
759 | + $token.toggleClass('active') | ||
760 | + this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() | ||
761 | + } | ||
762 | + | ||
763 | + , edit: function ($token) { | ||
764 | + if (!$token) return | ||
765 | + | ||
766 | + var attrs = $token.data('attrs') | ||
767 | + | ||
768 | + // Allow changing input value before editing | ||
769 | + var options = { attrs: attrs, relatedTarget: $token.get(0) } | ||
770 | + var editEvent = $.Event('tokenfield:edittoken', options) | ||
771 | + this.$element.trigger( editEvent ) | ||
772 | + | ||
773 | + // Edit event can be cancelled if default is prevented | ||
774 | + if (editEvent.isDefaultPrevented()) return | ||
775 | + | ||
776 | + $token.find('.token-label').text(attrs.value) | ||
777 | + var tokenWidth = $token.outerWidth() | ||
778 | + | ||
779 | + var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input | ||
780 | + | ||
781 | + $token.replaceWith( $_input ) | ||
782 | + | ||
783 | + this.preventCreateTokens = true | ||
784 | + | ||
785 | + this.$input.val( attrs.value ) | ||
786 | + .select() | ||
787 | + .data( 'edit', true ) | ||
788 | + .width( tokenWidth ) | ||
789 | + | ||
790 | + this.update(); | ||
791 | + | ||
792 | + // Indicate that token is now being edited, and is replaced with an input field in the DOM | ||
793 | + this.$element.trigger($.Event('tokenfield:editedtoken', options )) | ||
794 | + } | ||
795 | + | ||
796 | + , unedit: function (focus) { | ||
797 | + var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input | ||
798 | + $_input.appendTo( this.$wrapper ) | ||
799 | + | ||
800 | + this.$input.data('edit', false) | ||
801 | + this.$mirror.text('') | ||
802 | + | ||
803 | + this.update() | ||
804 | + | ||
805 | + // Because moving the input element around in DOM | ||
806 | + // will cause it to lose focus, we provide an option | ||
807 | + // to re-focus the input after appending it to the wrapper | ||
808 | + if (focus) { | ||
809 | + var _self = this | ||
810 | + setTimeout(function () { | ||
811 | + _self.$input.focus() | ||
812 | + }, 1) | ||
813 | + } | ||
814 | + } | ||
815 | + | ||
816 | + , remove: function (e, direction) { | ||
817 | + if (this.$input.is(document.activeElement) || this._disabled || this._readonly) return | ||
818 | + | ||
819 | + var $token = (e.type === 'click') ? $(e.target).closest('.token') : this.$wrapper.find('.token.active') | ||
820 | + | ||
821 | + if (e.type !== 'click') { | ||
822 | + if (!direction) var direction = 'prev' | ||
823 | + this[direction]() | ||
824 | + | ||
825 | + // Was it the first token? | ||
826 | + if (direction === 'prev') var firstToken = $token.first().prevAll('.token:first').length === 0 | ||
827 | + } | ||
828 | + | ||
829 | + // Prepare events and their options | ||
830 | + var options = { attrs: this.getTokenData( $token ), relatedTarget: $token.get(0) } | ||
831 | + , removeEvent = $.Event('tokenfield:removetoken', options) | ||
832 | + | ||
833 | + this.$element.trigger(removeEvent); | ||
834 | + | ||
835 | + // Remove event can be intercepted and cancelled | ||
836 | + if (removeEvent.isDefaultPrevented()) return | ||
837 | + | ||
838 | + var removedEvent = $.Event('tokenfield:removedtoken', options) | ||
839 | + , changeEvent = $.Event('change', { initiator: 'tokenfield' }) | ||
840 | + | ||
841 | + // Remove token from DOM | ||
842 | + $token.remove() | ||
843 | + | ||
844 | + // Trigger events | ||
845 | + this.$element.val( this.getTokensList() ).trigger( removedEvent ).trigger( changeEvent ) | ||
846 | + | ||
847 | + // Focus, when necessary: | ||
848 | + // When there are no more tokens, or if this was the first token | ||
849 | + // and it was removed with backspace or it was clicked on | ||
850 | + if (!this.$wrapper.find('.token').length || e.type === 'click' || firstToken) this.$input.focus() | ||
851 | + | ||
852 | + // Adjust input width | ||
853 | + this.$input.css('width', this.options.minWidth + 'px') | ||
854 | + this.update() | ||
855 | + | ||
856 | + // Cancel original event handlers | ||
857 | + e.preventDefault() | ||
858 | + e.stopPropagation() | ||
859 | + } | ||
860 | + | ||
861 | + /** | ||
862 | + * Update tokenfield dimensions | ||
863 | + */ | ||
864 | + , update: function (e) { | ||
865 | + var value = this.$input.val() | ||
866 | + , inputPaddingLeft = parseInt(this.$input.css('padding-left'), 10) | ||
867 | + , inputPaddingRight = parseInt(this.$input.css('padding-right'), 10) | ||
868 | + , inputPadding = inputPaddingLeft + inputPaddingRight | ||
869 | + | ||
870 | + if (this.$input.data('edit')) { | ||
871 | + | ||
872 | + if (!value) { | ||
873 | + value = this.$input.prop("placeholder") | ||
874 | + } | ||
875 | + if (value === this.$mirror.text()) return | ||
876 | + | ||
877 | + this.$mirror.text(value) | ||
878 | + | ||
879 | + var mirrorWidth = this.$mirror.width() + 10; | ||
880 | + if ( mirrorWidth > this.$wrapper.width() ) { | ||
881 | + return this.$input.width( this.$wrapper.width() ) | ||
882 | + } | ||
883 | + | ||
884 | + this.$input.width( mirrorWidth ) | ||
885 | + } | ||
886 | + else { | ||
887 | + var w = (this.textDirection === 'rtl') | ||
888 | + ? this.$input.offset().left + this.$input.outerWidth() - this.$wrapper.offset().left - parseInt(this.$wrapper.css('padding-left'), 10) - inputPadding - 1 | ||
889 | + : this.$wrapper.offset().left + this.$wrapper.width() + parseInt(this.$wrapper.css('padding-left'), 10) - this.$input.offset().left - inputPadding; | ||
890 | + // | ||
891 | + // some usecases pre-render widget before attaching to DOM, | ||
892 | + // dimensions returned by jquery will be NaN -> we default to 100% | ||
893 | + // so placeholder won't be cut off. | ||
894 | + isNaN(w) ? this.$input.width('100%') : this.$input.width(w); | ||
895 | + } | ||
896 | + } | ||
897 | + | ||
898 | + , focusInput: function (e) { | ||
899 | + if ( $(e.target).closest('.token').length || $(e.target).closest('.token-input').length || $(e.target).closest('.tt-dropdown-menu').length ) return | ||
900 | + // Focus only after the current call stack has cleared, | ||
901 | + // otherwise has no effect. | ||
902 | + // Reason: mousedown is too early - input will lose focus | ||
903 | + // after mousedown. However, since the input may be moved | ||
904 | + // in DOM, there may be no click or mouseup event triggered. | ||
905 | + var _self = this | ||
906 | + setTimeout(function() { | ||
907 | + _self.$input.focus() | ||
908 | + }, 0) | ||
909 | + } | ||
910 | + | ||
911 | + , search: function () { | ||
912 | + if ( this.$input.data('ui-autocomplete') ) { | ||
913 | + this.$input.autocomplete('search') | ||
914 | + } | ||
915 | + } | ||
916 | + | ||
917 | + , disable: function () { | ||
918 | + this.setProperty('disabled', true); | ||
919 | + } | ||
920 | + | ||
921 | + , enable: function () { | ||
922 | + this.setProperty('disabled', false); | ||
923 | + } | ||
924 | + | ||
925 | + , readonly: function () { | ||
926 | + this.setProperty('readonly', true); | ||
927 | + } | ||
928 | + | ||
929 | + , writeable: function () { | ||
930 | + this.setProperty('readonly', false); | ||
931 | + } | ||
932 | + | ||
933 | + , setProperty: function(property, value) { | ||
934 | + this['_' + property] = value; | ||
935 | + this.$input.prop(property, value); | ||
936 | + this.$element.prop(property, value); | ||
937 | + this.$wrapper[ value ? 'addClass' : 'removeClass' ](property); | ||
938 | + } | ||
939 | + | ||
940 | + , destroy: function() { | ||
941 | + // Set field value | ||
942 | + this.$element.val( this.getTokensList() ); | ||
943 | + // Restore styles and properties | ||
944 | + this.$element.css( this.$element.data('original-styles') ); | ||
945 | + this.$element.prop( 'tabindex', this.$element.data('original-tabindex') ); | ||
946 | + | ||
947 | + // Re-route tokenfield label to original input | ||
948 | + var $label = $( 'label[for="' + this.$input.prop('id') + '"]' ) | ||
949 | + if ( $label.length ) { | ||
950 | + $label.prop( 'for', this.$element.prop('id') ) | ||
951 | + } | ||
952 | + | ||
953 | + // Move original element outside of tokenfield wrapper | ||
954 | + this.$element.insertBefore( this.$wrapper ); | ||
955 | + | ||
956 | + // Remove tokenfield-related data | ||
957 | + this.$element.removeData('original-styles') | ||
958 | + .removeData('original-tabindex') | ||
959 | + .removeData('bs.tokenfield'); | ||
960 | + | ||
961 | + // Remove tokenfield from DOM | ||
962 | + this.$wrapper.remove(); | ||
963 | + this.$mirror.remove(); | ||
964 | + | ||
965 | + var $_element = this.$element; | ||
966 | + | ||
967 | + return $_element; | ||
968 | + } | ||
969 | + | ||
970 | + } | ||
971 | + | ||
972 | + | ||
973 | + /* TOKENFIELD PLUGIN DEFINITION | ||
974 | + * ======================== */ | ||
975 | + | ||
976 | + var old = $.fn.tokenfield | ||
977 | + | ||
978 | + $.fn.tokenfield = function (option, param) { | ||
979 | + var value | ||
980 | + , args = [] | ||
981 | + | ||
982 | + Array.prototype.push.apply( args, arguments ); | ||
983 | + | ||
984 | + var elements = this.each(function () { | ||
985 | + var $this = $(this) | ||
986 | + , data = $this.data('bs.tokenfield') | ||
987 | + , options = typeof option == 'object' && option | ||
988 | + | ||
989 | + if (typeof option === 'string' && data && data[option]) { | ||
990 | + args.shift() | ||
991 | + value = data[option].apply(data, args) | ||
992 | + } else { | ||
993 | + if (!data && typeof option !== 'string' && !param) { | ||
994 | + $this.data('bs.tokenfield', (data = new Tokenfield(this, options))) | ||
995 | + $this.trigger('tokenfield:initialize') | ||
996 | + } | ||
997 | + } | ||
998 | + }) | ||
999 | + | ||
1000 | + return typeof value !== 'undefined' ? value : elements; | ||
1001 | + } | ||
1002 | + | ||
1003 | + $.fn.tokenfield.defaults = { | ||
1004 | + minWidth: 60, | ||
1005 | + minLength: 0, | ||
1006 | + html: true, | ||
1007 | + allowEditing: true, | ||
1008 | + allowPasting: true, | ||
1009 | + limit: 0, | ||
1010 | + autocomplete: {}, | ||
1011 | + typeahead: {}, | ||
1012 | + showAutocompleteOnFocus: false, | ||
1013 | + createTokensOnBlur: false, | ||
1014 | + delimiter: ',', | ||
1015 | + beautify: true, | ||
1016 | + inputType: 'text' | ||
1017 | + } | ||
1018 | + | ||
1019 | + $.fn.tokenfield.Constructor = Tokenfield | ||
1020 | + | ||
1021 | + | ||
1022 | + /* TOKENFIELD NO CONFLICT | ||
1023 | + * ================== */ | ||
1024 | + | ||
1025 | + $.fn.tokenfield.noConflict = function () { | ||
1026 | + $.fn.tokenfield = old | ||
1027 | + return this | ||
1028 | + } | ||
1029 | + | ||
1030 | + return Tokenfield; | ||
1031 | + | ||
1032 | +})); |
@@ -0,0 +1,312 @@ | @@ -0,0 +1,312 @@ | ||
1 | +fb_app = { | ||
2 | + current_url: '', | ||
3 | + | ||
4 | + locales: { | ||
5 | + | ||
6 | + }, | ||
7 | + | ||
8 | + config: { | ||
9 | + url_prefix: '', | ||
10 | + save_auth_url: '', | ||
11 | + show_login_url: '', | ||
12 | + | ||
13 | + init: function() { | ||
14 | + | ||
15 | + }, | ||
16 | + | ||
17 | + }, | ||
18 | + | ||
19 | + timeline: { | ||
20 | + appId: '', | ||
21 | + app_scope: 'publish_actions', | ||
22 | + | ||
23 | + loading: function() { | ||
24 | + jQuery('#fb-app-connect-status').empty().addClass('loading').height(150) | ||
25 | + }, | ||
26 | + | ||
27 | + connect: function() { | ||
28 | + this.loading(); | ||
29 | + fb_app.fb.scope = this.app_scope | ||
30 | + fb_app.fb.connect(function (response) { | ||
31 | + fb_app.auth.receive(response) | ||
32 | + }); | ||
33 | + }, | ||
34 | + | ||
35 | + disconnect: function() { | ||
36 | + // 'not_authorized' is used to disconnect from facebook | ||
37 | + jQuery('#fb-app-modal-wrap #fb-app-modal-intro').html( | ||
38 | + fb_app.locales.confirm_disconnect | ||
39 | + ) | ||
40 | + jQuery('#fb-app-modal-wrap .modal-button-no') | ||
41 | + .html(fb_app.locales.cancel_button) | ||
42 | + .attr('onClick', 'noosfero.modal.close(); return false') | ||
43 | + jQuery('#fb-app-modal-wrap .modal-button-yes') | ||
44 | + .html(fb_app.locales.confirm_disconnect_button) | ||
45 | + .attr('onClick', 'fb_app.timeline.disconnect_confirmed();noosfero.modal.close(); return false') | ||
46 | + noosfero.modal.html(jQuery('#fb-app-modal-wrap').html()) | ||
47 | + }, | ||
48 | + | ||
49 | + disconnect_confirmed: function() { | ||
50 | + this.loading(); | ||
51 | + fb_app.auth.receive({status: 'not_authorized'}) | ||
52 | + }, | ||
53 | + | ||
54 | + connect_to_another: function() { | ||
55 | + this.disconnect(); | ||
56 | + fb_app.fb.connect_to_another(this.connect) | ||
57 | + }, | ||
58 | + }, | ||
59 | + | ||
60 | + page_tab: { | ||
61 | + appId: '', | ||
62 | + nextUrl: '', | ||
63 | + | ||
64 | + init: function() { | ||
65 | + FB.Canvas.scrollTo(0,140); | ||
66 | + // While Braulio doesnt make the catalog-options-bar work in a product page, I'll hide it. User will have to go back to the catalog to see this bar, provisorily... | ||
67 | + jQuery('#product-page').prev('form').hide(); | ||
68 | + }, | ||
69 | + | ||
70 | + config: { | ||
71 | + | ||
72 | + init: function() { | ||
73 | + this.change_type($('select#page_tab_config_type')) | ||
74 | + | ||
75 | + }, | ||
76 | + | ||
77 | + edit: function(button) { | ||
78 | + var page_tab = button.parents('.page-tab') | ||
79 | + page_tab.find('form').toggle(400) | ||
80 | + }, | ||
81 | + | ||
82 | + remove: function(button, url) { | ||
83 | + var page_tab = button.parents('.page-tab') | ||
84 | + var name = page_tab.find('#page_tab_name').val() | ||
85 | + //jQuery('#fb-app-modal-catalog-name').text(name) | ||
86 | + jQuery('#fb-app-modal-wrap #fb-app-modal-intro').html( | ||
87 | + fb_app.locales.confirm_removal | ||
88 | + ) | ||
89 | + jQuery('#fb-app-modal-wrap .modal-button-no') | ||
90 | + .html(fb_app.locales.cancel_button) | ||
91 | + .attr('onClick', 'noosfero.modal.close(); return false') | ||
92 | + jQuery('#fb-app-modal-wrap .modal-button-yes') | ||
93 | + .html(fb_app.locales.confirm_removal_button) | ||
94 | + .attr('onClick', 'fb_app.page_tab.config.remove_confirmed(this);noosfero.modal.close(); return false') | ||
95 | + .attr('target_url',url) | ||
96 | + .attr('target_id','#'+page_tab.attr('id')) | ||
97 | + noosfero.modal.html(jQuery('#fb-app-modal-wrap').html()) | ||
98 | + }, | ||
99 | + | ||
100 | + remove_confirmed: function(el) { | ||
101 | + el = jQuery(el) | ||
102 | + jQuery.post(el.attr('target_url'), function() { | ||
103 | + var page_tab = jQuery(el.attr('target_id')) | ||
104 | + page_tab.remove() | ||
105 | + }) | ||
106 | + }, | ||
107 | + | ||
108 | + close: function(pageId) { | ||
109 | + noosfero.modal.close() | ||
110 | + jQuery('#content').html('').addClass('loading') | ||
111 | + fb_app.fb.redirect_to_tab(pageId, fb_app.page_tab.appId) | ||
112 | + }, | ||
113 | + | ||
114 | + validate: function(form) { | ||
115 | + for (var i=0; tinymce.editors[i]; i++) { | ||
116 | + var editor = tinymce.editors[i] | ||
117 | + var textarea = editor.getElement() | ||
118 | + textarea.value = editor.getContent() | ||
119 | + } | ||
120 | + | ||
121 | + if (form.find('#page_tab_title').val().trim()=='') { | ||
122 | + noosfero.modal.html('<div id="fb-app-error">'+fb_app.locales.error_empty_title+'</div>') | ||
123 | + return false | ||
124 | + } else { | ||
125 | + var selected_type = form.find('#page_tab_config_type').val() | ||
126 | + var sub_option = form.find('.config-type-'+selected_type+' input') | ||
127 | + if (sub_option.length > 0 && sub_option.val().trim()=='') { | ||
128 | + noosfero.modal.html('<div id="fb-app-error">'+fb_app.locales.error_empty_settings+'</div>') | ||
129 | + return false | ||
130 | + } | ||
131 | + } | ||
132 | + return true | ||
133 | + }, | ||
134 | + | ||
135 | + add: function (form) { | ||
136 | + if (!this.validate(form)) | ||
137 | + return false | ||
138 | + // this checks if the user is using FB as a page and offer a switch | ||
139 | + FB.login(function(response) { | ||
140 | + if (response.status != 'connected') return | ||
141 | + var nextUrl = fb_app.page_tab.nextUrl + '?' + form.serialize() | ||
142 | + window.location.href = fb_app.fb.add_tab_url(fb_app.page_tab.appId, nextUrl) | ||
143 | + }) | ||
144 | + return false | ||
145 | + }, | ||
146 | + | ||
147 | + save: function(form) { | ||
148 | + if (!this.validate(form)) | ||
149 | + return false | ||
150 | + jQuery(form).ajaxSubmit({ | ||
151 | + dataType: 'script', | ||
152 | + }) | ||
153 | + return false | ||
154 | + }, | ||
155 | + | ||
156 | + change_type: function(select) { | ||
157 | + select = jQuery(select) | ||
158 | + var page_tab = select.parents('.page-tab') | ||
159 | + var config_selector = '.config-type-'+select.val() | ||
160 | + var config = page_tab.find(config_selector) | ||
161 | + var to_show = config | ||
162 | + var to_hide = page_tab.find('.config-type:not('+config_selector+')') | ||
163 | + | ||
164 | + to_show.show(). | ||
165 | + find('input').prop('disabled', false) | ||
166 | + to_show.find('.tokenfield').removeClass('disabled') | ||
167 | + to_hide.hide(). | ||
168 | + find('input').prop('disabled', true) | ||
169 | + }, | ||
170 | + | ||
171 | + profile: { | ||
172 | + | ||
173 | + onchange: function(input) { | ||
174 | + if (input.val()) | ||
175 | + input.removeAttr('placeholder') | ||
176 | + else | ||
177 | + input.attr('placeholder', input.attr('data-placeholder')) | ||
178 | + }, | ||
179 | + }, | ||
180 | + }, | ||
181 | + | ||
182 | + }, | ||
183 | + | ||
184 | + auth: { | ||
185 | + status: 'not_authorized', | ||
186 | + | ||
187 | + load: function (html) { | ||
188 | + jQuery('#fb-app-settings').html(html) | ||
189 | + }, | ||
190 | + loadLogin: function (html) { | ||
191 | + if (this.status == 'not_authorized') | ||
192 | + jQuery('#fb-app-connect').html(html).removeClass('loading') | ||
193 | + else | ||
194 | + jQuery('#fb-app-login').html(html) | ||
195 | + }, | ||
196 | + | ||
197 | + receive: function(response) { | ||
198 | + fb_app.fb.authResponse = response | ||
199 | + fb_app.auth.save(response) | ||
200 | + jQuery('html,body').animate({ scrollTop: jQuery('#fb-app-settings').offset().top-100 }, 400) | ||
201 | + }, | ||
202 | + | ||
203 | + transformParams: function(response) { | ||
204 | + var authResponse = response.authResponse | ||
205 | + if (!authResponse) | ||
206 | + return {auth: {status: response.status}} | ||
207 | + else | ||
208 | + return { | ||
209 | + auth: { | ||
210 | + status: response.status, | ||
211 | + access_token: authResponse.accessToken, | ||
212 | + expires_in: authResponse.expiresIn, | ||
213 | + signed_request: authResponse.signedRequest, | ||
214 | + provider_user_id: authResponse.userID, | ||
215 | + } | ||
216 | + } | ||
217 | + }, | ||
218 | + | ||
219 | + showLogin: function(response) { | ||
220 | + jQuery.get(fb_app.config.show_login_url, this.transformParams(response), this.loadLogin) | ||
221 | + }, | ||
222 | + | ||
223 | + save: function(response) { | ||
224 | + jQuery.post(fb_app.config.save_auth_url, this.transformParams(response), this.load) | ||
225 | + }, | ||
226 | + }, | ||
227 | + | ||
228 | + | ||
229 | + // interface to facebook's SDK | ||
230 | + fb: { | ||
231 | + appId: '', | ||
232 | + scope: '', | ||
233 | + inited: false, | ||
234 | + initCode: null, | ||
235 | + | ||
236 | + prepareAsyncInit: function(appId, asyncInitCode) { | ||
237 | + this.id = appId | ||
238 | + this.initCode = asyncInitCode | ||
239 | + | ||
240 | + window.fbAsyncInit = function() { | ||
241 | + FB.init({ | ||
242 | + appId: appId, | ||
243 | + cookie: true, | ||
244 | + xfbml: true, | ||
245 | + status: true, | ||
246 | + }) | ||
247 | + | ||
248 | + // automatic iframe's resize | ||
249 | + // FIXME: move to page tab embed code | ||
250 | + fb_app.fb.size_change() | ||
251 | + jQuery(document).on('DOMNodeInserted', fb_app.fb.size_change) | ||
252 | + | ||
253 | + if (asyncInitCode) | ||
254 | + jQuery.globalEval(asyncInitCode) | ||
255 | + | ||
256 | + fb_app.fb.inited = true | ||
257 | + } | ||
258 | + }, | ||
259 | + | ||
260 | + init: function() { | ||
261 | + // the SDK is loaded on views/fb_app_plugin/_load.html.slim and then call window.fbAsyncInit | ||
262 | + }, | ||
263 | + | ||
264 | + size_change: function() { | ||
265 | + FB.Canvas.setSize({height: jQuery('body').height()+100}) | ||
266 | + }, | ||
267 | + | ||
268 | + redirect_to_tab: function(pageId, appId) { | ||
269 | + window.location.href = 'https://facebook.com/' + pageId + '?sk=app_' + appId | ||
270 | + }, | ||
271 | + | ||
272 | + add_tab_url: function (appId, nextUrl) { | ||
273 | + return 'https://www.facebook.com/dialog/pagetab?' + jQuery.param({app_id: appId, next: nextUrl}) | ||
274 | + }, | ||
275 | + | ||
276 | + connect: function(callback) { | ||
277 | + FB.login(function(response) { | ||
278 | + if (callback) callback(response) | ||
279 | + }, {scope: fb_app.fb.scope}) | ||
280 | + }, | ||
281 | + | ||
282 | + connect_to_another: function(callback) { | ||
283 | + this.logout(this.connect(callback)) | ||
284 | + }, | ||
285 | + | ||
286 | + logout: function(callback) { | ||
287 | + // this checks if the user is using FB as a page and offer a switch | ||
288 | + FB.login(function(response) { | ||
289 | + FB.logout(function(response) { | ||
290 | + if (callback) callback(response) | ||
291 | + }) | ||
292 | + }) | ||
293 | + }, | ||
294 | + | ||
295 | + // not to be used | ||
296 | + delete: function(callback) { | ||
297 | + FB.api("/me/permissions", "DELETE", function(response) { | ||
298 | + if (callback) callback(response) | ||
299 | + }) | ||
300 | + }, | ||
301 | + | ||
302 | + checkLoginStatus: function() { | ||
303 | + FB.getLoginStatus(function(response) { | ||
304 | + // don't do nothing, this is just to fetch auth after init | ||
305 | + }) | ||
306 | + }, | ||
307 | + | ||
308 | + }, | ||
309 | + | ||
310 | +} | ||
311 | + | ||
312 | + |
@@ -0,0 +1,214 @@ | @@ -0,0 +1,214 @@ | ||
1 | +/* use with @extend, CSS clear bugfix */ | ||
2 | +.clean { | ||
3 | + clear: both; | ||
4 | +} | ||
5 | +.container-clean { | ||
6 | + overflow: hidden; | ||
7 | + display: inline-block; /* Necessary to trigger "hasLayout" in IE */ | ||
8 | + display: block; /* Sets element back to block */ | ||
9 | +} | ||
10 | + | ||
11 | +/* layout base parameters */ | ||
12 | +$modules: 12; | ||
13 | +$base: 8px; | ||
14 | +$wireframe: 1040px; | ||
15 | + | ||
16 | +/* heights should only use multiples of this */ | ||
17 | +$height: $base; | ||
18 | + | ||
19 | +/* base measurements */ | ||
20 | +$intercolumn: 2*$base; | ||
21 | +$module: $wireframe/$modules - $intercolumn; | ||
22 | + | ||
23 | +/* widths should only use one of these */ | ||
24 | +$module01: 01*$module + 00*$intercolumn; | ||
25 | +$module02: 02*$module + 01*$intercolumn; | ||
26 | +$module03: 03*$module + 02*$intercolumn; | ||
27 | +$module04: 04*$module + 03*$intercolumn; | ||
28 | +$module05: 05*$module + 04*$intercolumn; | ||
29 | +$module06: 06*$module + 05*$intercolumn; | ||
30 | +$module07: 07*$module + 06*$intercolumn; | ||
31 | +$module08: 08*$module + 07*$intercolumn; | ||
32 | +$module09: 09*$module + 08*$intercolumn; | ||
33 | +$module09: 09*$module + 08*$intercolumn; | ||
34 | +$module10: 10*$module + 09*$intercolumn; | ||
35 | +$module11: 11*$module + 10*$intercolumn; | ||
36 | +$module12: 12*$module + 11*$intercolumn; | ||
37 | +$module01p: percentage($module01/$wireframe); | ||
38 | +$module02p: percentage($module02/$wireframe); | ||
39 | +$module03p: percentage($module03/$wireframe); | ||
40 | +$module04p: percentage($module04/$wireframe); | ||
41 | +$module05p: percentage($module05/$wireframe); | ||
42 | +$module06p: percentage($module06/$wireframe); | ||
43 | +$module07p: percentage($module07/$wireframe); | ||
44 | +$module08p: percentage($module08/$wireframe); | ||
45 | +$module09p: percentage($module09/$wireframe); | ||
46 | +$module10p: percentage($module10/$wireframe); | ||
47 | +$module11p: percentage($module11/$wireframe); | ||
48 | +$module12p: percentage($module12/$wireframe); | ||
49 | + | ||
50 | +/* paddings and margins should only use one of these | ||
51 | + Ps. 1: disccount the borders size from padding, as borders uses padding's space. | ||
52 | + Ps. 2: because of W3C's content-box default box sizing, padding sums to width size. If your | ||
53 | + box doesn't have a padding, then sum $intercolumn to the width. | ||
54 | + */ | ||
55 | +$margin: $intercolumn; | ||
56 | +$half-margin: $margin/2; | ||
57 | +$padding: $intercolumn/2; | ||
58 | +$half-padding: $padding/2; | ||
59 | +$marginp: percentage($margin/$wireframe); | ||
60 | +$half-marginp: percentage($half-margin/$wireframe); | ||
61 | +$paddingp: percentage($padding/$wireframe); | ||
62 | +$half-paddingp: percentage($half-padding/$wireframe); | ||
63 | + | ||
64 | +$wireframe-padding: 5*$padding; | ||
65 | + | ||
66 | +/* use for borders */ | ||
67 | +$border: 1px; | ||
68 | +$border-radius: 5px; | ||
69 | + | ||
70 | +/* use for text shadows */ | ||
71 | +$shadow: 2px; | ||
72 | + | ||
73 | +/* Colors */ | ||
74 | + | ||
75 | +$border-action-button: #F4A439; | ||
76 | +$bg-action-button: #FBCA47; | ||
77 | +$bg-selection-button: white; | ||
78 | + | ||
79 | +/* Fonts */ | ||
80 | + | ||
81 | +/* Paragraphs Styles (use with @extend) */ | ||
82 | + | ||
83 | +.pstyle-none { | ||
84 | + font-size: 12px; | ||
85 | +} | ||
86 | +.pstyle-basic { | ||
87 | + font-size: 16px; | ||
88 | +} | ||
89 | +.pstyle-button { | ||
90 | + font-size: 16px; | ||
91 | +} | ||
92 | +.pstyle-button-small { | ||
93 | + font-size: 13px; | ||
94 | +} | ||
95 | +.pstyle-title { | ||
96 | + font-size: 72px; | ||
97 | +} | ||
98 | +.pstyle-h1 { | ||
99 | + font-size: 34px; | ||
100 | +} | ||
101 | +.pstyle-h2 { | ||
102 | + font-size: 26px; | ||
103 | +} | ||
104 | +.pstyle-h3 { | ||
105 | + font-size: 21px; | ||
106 | +} | ||
107 | +.pstyle-h4 { | ||
108 | + font-size: 16px; | ||
109 | +} | ||
110 | +.pstyle-h5 { | ||
111 | + font-size: 13px; | ||
112 | +} | ||
113 | +.pstyle-title-section { | ||
114 | + font-size: 92px; | ||
115 | +} | ||
116 | +.pstyle-field { | ||
117 | + font-size: 13px; | ||
118 | +} | ||
119 | +.pstyle-menu-big-selected { | ||
120 | + font-size: 21px; | ||
121 | +} | ||
122 | +.pstyle-menu-big-unselected { | ||
123 | + font-size: 21px; | ||
124 | +} | ||
125 | +.pstyle-menu-medium-selected { | ||
126 | + font-size: 16px; | ||
127 | +} | ||
128 | +.pstyle-menu-medium-unselected { | ||
129 | + font-size: 16px; | ||
130 | +} | ||
131 | +.pstyle-menu-small-selected { | ||
132 | + font-size: 13px; | ||
133 | +} | ||
134 | +.pstyle-menu-small-unselected { | ||
135 | + font-size: 13px; | ||
136 | +} | ||
137 | +.pstyle-tp4 { | ||
138 | + font-size: 42px; | ||
139 | +} | ||
140 | +.pstyle-tp3 { | ||
141 | + font-size: 34px; | ||
142 | +} | ||
143 | +.pstyle-tp2 { | ||
144 | + font-size: 26px; | ||
145 | +} | ||
146 | +.pstyle-tp1 { | ||
147 | + font-size: 21px; | ||
148 | +} | ||
149 | +.pstyle-tm1 { | ||
150 | + font-size: 13px; | ||
151 | +} | ||
152 | +.pstyle-tm2 { | ||
153 | + font-size: 10px; | ||
154 | +} | ||
155 | +.subtitle { | ||
156 | + @extend .pstyle-tm2; | ||
157 | +} | ||
158 | + | ||
159 | +/* Images */ | ||
160 | + | ||
161 | +$profile-thumb-size: 4*$base; | ||
162 | +$profile-portrait-size: 10*$base; | ||
163 | + | ||
164 | +/* profile-image that can be centered and resized with aspect ratio */ | ||
165 | +.profile-image { | ||
166 | + display: inline-block; | ||
167 | + | ||
168 | + &.thumb { | ||
169 | + width: $profile-thumb-size; | ||
170 | + height: $profile-thumb-size; | ||
171 | + } | ||
172 | + &.portrait { | ||
173 | + width: $profile-portrait-size; | ||
174 | + height: $profile-portrait-size; | ||
175 | + } | ||
176 | + | ||
177 | + /* do not put padding in this as background size will consider it. */ | ||
178 | + .inner { | ||
179 | + display: block; | ||
180 | + width: 100%; | ||
181 | + height: 100%; | ||
182 | + background-repeat: no-repeat; | ||
183 | + background-position: center; | ||
184 | + background-size: 100%; | ||
185 | + background-size: contain; /* css3 enabled */ | ||
186 | + } | ||
187 | +} | ||
188 | + | ||
189 | +/* Buttons */ | ||
190 | + | ||
191 | +.action-button { | ||
192 | + display: inline-block; | ||
193 | + padding: $half-padding $padding; | ||
194 | + height: auto; | ||
195 | + width: auto; | ||
196 | + //&:visited, &:active, &:hover { color: white; } | ||
197 | + background: $bg-action-button; | ||
198 | + border: $border solid $border-action-button; | ||
199 | + cursor: pointer; | ||
200 | + color: black; | ||
201 | + font-weight: bold; | ||
202 | + line-height: 2*$height; | ||
203 | + text-align: center; | ||
204 | + text-decoration: none; | ||
205 | + text-transform: uppercase; | ||
206 | + text-shadow: none; | ||
207 | + border-radius: $border-radius; | ||
208 | +} | ||
209 | + | ||
210 | +.selection-button { | ||
211 | + @extend .action-button; | ||
212 | + background: $bg-selection-button; | ||
213 | +} | ||
214 | + |
plugins/fb_app/public/stylesheets/bootstrap-tokenfield.css
0 → 100644
@@ -0,0 +1,209 @@ | @@ -0,0 +1,209 @@ | ||
1 | +/*! | ||
2 | + * bootstrap-tokenfield | ||
3 | + * https://github.com/sliptree/bootstrap-tokenfield | ||
4 | + * Copyright 2013-2014 Sliptree and other contributors; Licensed MIT | ||
5 | + */ | ||
6 | +@-webkit-keyframes blink { | ||
7 | + 0% { | ||
8 | + border-color: #ededed; | ||
9 | + } | ||
10 | + 100% { | ||
11 | + border-color: #b94a48; | ||
12 | + } | ||
13 | +} | ||
14 | +@-moz-keyframes blink { | ||
15 | + 0% { | ||
16 | + border-color: #ededed; | ||
17 | + } | ||
18 | + 100% { | ||
19 | + border-color: #b94a48; | ||
20 | + } | ||
21 | +} | ||
22 | +@keyframes blink { | ||
23 | + 0% { | ||
24 | + border-color: #ededed; | ||
25 | + } | ||
26 | + 100% { | ||
27 | + border-color: #b94a48; | ||
28 | + } | ||
29 | +} | ||
30 | +.tokenfield { | ||
31 | + height: auto; | ||
32 | + min-height: 34px; | ||
33 | + padding-bottom: 0px; | ||
34 | +} | ||
35 | +.tokenfield.focus { | ||
36 | + border-color: #66afe9; | ||
37 | + outline: 0; | ||
38 | + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); | ||
39 | + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); | ||
40 | +} | ||
41 | +.tokenfield .token { | ||
42 | + -webkit-box-sizing: border-box; | ||
43 | + -moz-box-sizing: border-box; | ||
44 | + box-sizing: border-box; | ||
45 | + -webkit-border-radius: 3px; | ||
46 | + -moz-border-radius: 3px; | ||
47 | + border-radius: 3px; | ||
48 | + display: inline-block; | ||
49 | + border: 1px solid #d9d9d9; | ||
50 | + background-color: #ededed; | ||
51 | + white-space: nowrap; | ||
52 | + margin: -1px 5px 5px 0; | ||
53 | + height: 22px; | ||
54 | + vertical-align: top; | ||
55 | + cursor: default; | ||
56 | +} | ||
57 | +.tokenfield .token:hover { | ||
58 | + border-color: #b9b9b9; | ||
59 | +} | ||
60 | +.tokenfield .token.active { | ||
61 | + border-color: #52a8ec; | ||
62 | + border-color: rgba(82, 168, 236, 0.8); | ||
63 | +} | ||
64 | +.tokenfield .token.duplicate { | ||
65 | + border-color: #ebccd1; | ||
66 | + -webkit-animation-name: blink; | ||
67 | + animation-name: blink; | ||
68 | + -webkit-animation-duration: 0.1s; | ||
69 | + animation-duration: 0.1s; | ||
70 | + -webkit-animation-direction: normal; | ||
71 | + animation-direction: normal; | ||
72 | + -webkit-animation-timing-function: ease; | ||
73 | + animation-timing-function: ease; | ||
74 | + -webkit-animation-iteration-count: infinite; | ||
75 | + animation-iteration-count: infinite; | ||
76 | +} | ||
77 | +.tokenfield .token.invalid { | ||
78 | + background: none; | ||
79 | + border: 1px solid transparent; | ||
80 | + -webkit-border-radius: 0; | ||
81 | + -moz-border-radius: 0; | ||
82 | + border-radius: 0; | ||
83 | + border-bottom: 1px dotted #d9534f; | ||
84 | +} | ||
85 | +.tokenfield .token.invalid.active { | ||
86 | + background: #ededed; | ||
87 | + border: 1px solid #ededed; | ||
88 | + -webkit-border-radius: 3px; | ||
89 | + -moz-border-radius: 3px; | ||
90 | + border-radius: 3px; | ||
91 | +} | ||
92 | +.tokenfield .token .token-label { | ||
93 | + display: inline-block; | ||
94 | + overflow: hidden; | ||
95 | + text-overflow: ellipsis; | ||
96 | + padding-left: 4px; | ||
97 | + vertical-align: top; | ||
98 | +} | ||
99 | +.tokenfield .token .close { | ||
100 | + font-family: Arial; | ||
101 | + display: inline-block; | ||
102 | + line-height: 100%; | ||
103 | + font-size: 1.1em; | ||
104 | + line-height: 1.49em; | ||
105 | + margin-left: 5px; | ||
106 | + float: none; | ||
107 | + height: 100%; | ||
108 | + vertical-align: top; | ||
109 | + padding-right: 4px; | ||
110 | +} | ||
111 | +.tokenfield .token-input { | ||
112 | + background: none; | ||
113 | + width: 60px; | ||
114 | + min-width: 60px; | ||
115 | + border: 0; | ||
116 | + height: 20px; | ||
117 | + padding: 0; | ||
118 | + margin-bottom: 6px; | ||
119 | + -webkit-box-shadow: none; | ||
120 | + box-shadow: none; | ||
121 | +} | ||
122 | +.tokenfield .token-input:focus { | ||
123 | + border-color: transparent; | ||
124 | + outline: 0; | ||
125 | + /* IE6-9 */ | ||
126 | + -webkit-box-shadow: none; | ||
127 | + box-shadow: none; | ||
128 | +} | ||
129 | +.tokenfield.disabled { | ||
130 | + cursor: not-allowed; | ||
131 | + background-color: #eeeeee; | ||
132 | +} | ||
133 | +.tokenfield.disabled .token-input { | ||
134 | + cursor: not-allowed; | ||
135 | +} | ||
136 | +.tokenfield.disabled .token:hover { | ||
137 | + cursor: not-allowed; | ||
138 | + border-color: #d9d9d9; | ||
139 | +} | ||
140 | +.tokenfield.disabled .token:hover .close { | ||
141 | + cursor: not-allowed; | ||
142 | + opacity: 0.2; | ||
143 | + filter: alpha(opacity=20); | ||
144 | +} | ||
145 | +.has-warning .tokenfield.focus { | ||
146 | + border-color: #66512c; | ||
147 | + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; | ||
148 | + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; | ||
149 | +} | ||
150 | +.has-error .tokenfield.focus { | ||
151 | + border-color: #843534; | ||
152 | + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; | ||
153 | + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; | ||
154 | +} | ||
155 | +.has-success .tokenfield.focus { | ||
156 | + border-color: #2b542c; | ||
157 | + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; | ||
158 | + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; | ||
159 | +} | ||
160 | +.tokenfield.input-sm, | ||
161 | +.input-group-sm .tokenfield { | ||
162 | + min-height: 30px; | ||
163 | + padding-bottom: 0px; | ||
164 | +} | ||
165 | +.input-group-sm .token, | ||
166 | +.tokenfield.input-sm .token { | ||
167 | + height: 20px; | ||
168 | + margin-bottom: 4px; | ||
169 | +} | ||
170 | +.input-group-sm .token-input, | ||
171 | +.tokenfield.input-sm .token-input { | ||
172 | + height: 18px; | ||
173 | + margin-bottom: 5px; | ||
174 | +} | ||
175 | +.tokenfield.input-lg, | ||
176 | +.input-group-lg .tokenfield { | ||
177 | + min-height: 45px; | ||
178 | + padding-bottom: 4px; | ||
179 | +} | ||
180 | +.input-group-lg .token, | ||
181 | +.tokenfield.input-lg .token { | ||
182 | + height: 25px; | ||
183 | +} | ||
184 | +.input-group-lg .token-label, | ||
185 | +.tokenfield.input-lg .token-label { | ||
186 | + line-height: 23px; | ||
187 | +} | ||
188 | +.input-group-lg .token .close, | ||
189 | +.tokenfield.input-lg .token .close { | ||
190 | + line-height: 1.3em; | ||
191 | +} | ||
192 | +.input-group-lg .token-input, | ||
193 | +.tokenfield.input-lg .token-input { | ||
194 | + height: 23px; | ||
195 | + line-height: 23px; | ||
196 | + margin-bottom: 6px; | ||
197 | + vertical-align: top; | ||
198 | +} | ||
199 | +.tokenfield.rtl { | ||
200 | + direction: rtl; | ||
201 | + text-align: right; | ||
202 | +} | ||
203 | +.tokenfield.rtl .token { | ||
204 | + margin: -1px 0 5px 5px; | ||
205 | +} | ||
206 | +.tokenfield.rtl .token .token-label { | ||
207 | + padding-left: 0px; | ||
208 | + padding-right: 4px; | ||
209 | +} |
@@ -0,0 +1,238 @@ | @@ -0,0 +1,238 @@ | ||
1 | +@import 'base'; | ||
2 | + | ||
3 | +body.controller-fb_app_plugin_page_tab { | ||
4 | + | ||
5 | + /* Catalog Tab in Facebook */ | ||
6 | + background: #fff; | ||
7 | + | ||
8 | + #top-bar, #theme-footer, #product-category, #product-page .button-bar { | ||
9 | + display: none; | ||
10 | + } | ||
11 | + .navbar-static-top { | ||
12 | + border-radius: 4px; | ||
13 | + } | ||
14 | + #wrap-1 { | ||
15 | + box-shadow: none; | ||
16 | + } | ||
17 | + .container { | ||
18 | + width: 100%; | ||
19 | + } | ||
20 | + #content .no-boxes { | ||
21 | + padding: 0; | ||
22 | + width: 100%; | ||
23 | + } | ||
24 | + #content-inner { | ||
25 | + padding-top: 0; | ||
26 | + } | ||
27 | + #content h1 { | ||
28 | + margin: 0; | ||
29 | + } | ||
30 | + #product-list { | ||
31 | + margin: 0 -10px; | ||
32 | + } | ||
33 | + #page-tab-subtitle { | ||
34 | + margin-bottom: 15px; | ||
35 | + background-color: rgb(241, 255, 107); | ||
36 | + border: 1px solid #ccc; | ||
37 | + border-top: none; | ||
38 | + border-radius: 0px 0px 6px 6px; | ||
39 | + font-style: italic; | ||
40 | + padding: 10px 10px 0px; | ||
41 | + } | ||
42 | + #page-tab-subtitle p { | ||
43 | + margin-bottom: 10px; | ||
44 | + } | ||
45 | + #product-owner { | ||
46 | + display:block; | ||
47 | + font-size: 120%; | ||
48 | + font-weight: bold; | ||
49 | + clear: both; | ||
50 | + } | ||
51 | + #product-list li.product { | ||
52 | + width: 190px; | ||
53 | + padding: 10px; | ||
54 | + } | ||
55 | + #product-list .product-big { | ||
56 | + width: 160px; | ||
57 | + } | ||
58 | + #product-list .product-image-link { | ||
59 | + height: 170px; | ||
60 | + } | ||
61 | + #product-list .expand-box { | ||
62 | + width: 162px; | ||
63 | + } | ||
64 | + #theme-footer { | ||
65 | + border: none; | ||
66 | + } | ||
67 | + #page-tab-footer { | ||
68 | + font-size: 11px; | ||
69 | + border-top: 3px solid rgb(241, 255, 107); | ||
70 | + margin-top: 70px; | ||
71 | + padding-top: 5px; | ||
72 | + } | ||
73 | + #page-tab-footer1 { | ||
74 | + background-size: 50px; | ||
75 | + padding-left: 50px; | ||
76 | + } | ||
77 | + | ||
78 | + /* End of Catalog Tab in Facebook */ | ||
79 | + | ||
80 | + #profile-title, | ||
81 | + #profile-header, | ||
82 | + #profile-theme-header, | ||
83 | + #profile-footer, | ||
84 | + #theme-header { | ||
85 | + display: none; | ||
86 | + } | ||
87 | + .product-catalog-ctrl { | ||
88 | + float: right; | ||
89 | + margin-left: 3px; | ||
90 | + } | ||
91 | + #product-catalog-actions { | ||
92 | + text-align: right; | ||
93 | + } | ||
94 | + #manage-fb-store-ctrl { | ||
95 | + margin-bottom: 15px; | ||
96 | + } | ||
97 | + .modal-content { | ||
98 | + #fb-app-page-tab-admin { | ||
99 | + height: 400px; | ||
100 | + } | ||
101 | + } | ||
102 | + | ||
103 | +} | ||
104 | + | ||
105 | +body.controller-fb_app_plugin_page_tab, | ||
106 | +body.controller-fb_app_plugin_myprofile { | ||
107 | + | ||
108 | + input.small-loading { | ||
109 | + background: transparent url(/images/loading-small.gif) no-repeat scroll right center; | ||
110 | + } | ||
111 | + | ||
112 | + .loading { | ||
113 | + background: white url(/plugins/fb_app/images/loading.gif) no-repeat center center; | ||
114 | + width: 80%; | ||
115 | + height: 300px; | ||
116 | + margin: auto; | ||
117 | + } | ||
118 | +} | ||
119 | + | ||
120 | +/* control panel - general */ | ||
121 | +#fb-app-modal-wrap { | ||
122 | + display: none; | ||
123 | +} | ||
124 | +#fb-app-error, #fb-app-modal { | ||
125 | + padding: 70px 30px; | ||
126 | + font-size: 120%; | ||
127 | +} | ||
128 | +#fb-app-error { | ||
129 | + color: #E44444; | ||
130 | + font-style: italic; | ||
131 | +} | ||
132 | +.controller-profile_editor a.control-panel-fb-app { | ||
133 | + background-image: url(/plugins/fb_app/images/control-panel.png); | ||
134 | +} | ||
135 | +#fb-app-intro { | ||
136 | + margin: 0 40px 20px 40px; | ||
137 | +} | ||
138 | +#fb-app-intro-text { | ||
139 | + border:2px #666 solid; | ||
140 | + padding: 10px; | ||
141 | + border-radius: 8px; | ||
142 | +} | ||
143 | +#fb-app-connect-status { | ||
144 | + background-color: #eee; | ||
145 | + border: 1px solid #ccc; | ||
146 | + margin: 30px 0; | ||
147 | + padding: 30px; | ||
148 | +} | ||
149 | +.fb-app-connection-button { | ||
150 | + margin-top: 15px; | ||
151 | +} | ||
152 | +#fb-app-auth { | ||
153 | + text-align: center; | ||
154 | + min-height: 60px; | ||
155 | +} | ||
156 | +#fb-connected { | ||
157 | + font-size: 36px; | ||
158 | + margin: 0 30px; | ||
159 | + color: #99f; | ||
160 | +} | ||
161 | +#fb-app-wrapper { | ||
162 | + padding: 40px; | ||
163 | + font-size: 20px; | ||
164 | +} | ||
165 | +#fb-app-wrapper h1 { | ||
166 | + font-size: 39px; | ||
167 | + margin-bottom: 50px; | ||
168 | +} | ||
169 | + | ||
170 | +/* Control panel - catalog settings */ | ||
171 | +#page-tab-new { | ||
172 | + margin-top: 30px; | ||
173 | +} | ||
174 | +#page-tab-new h3 { | ||
175 | + border-top: 2px solid #ddd; | ||
176 | + padding-top: 10px; | ||
177 | +} | ||
178 | +#page-tab-new h3, .edit-page-tab { | ||
179 | + display: none; | ||
180 | +} | ||
181 | +.edit-page-tab { | ||
182 | + background-color: #eee; | ||
183 | + padding: 15px; | ||
184 | + border-radius: 8px; | ||
185 | +} | ||
186 | +.edit-tab-button { | ||
187 | + float: right; | ||
188 | + margin-left: 10px; | ||
189 | +} | ||
190 | +#fb-app-timeline, #fb-app-catalogs { | ||
191 | + border: 1px solid #999; | ||
192 | + border-radius: 8px; | ||
193 | + padding: 10px; | ||
194 | +} | ||
195 | +#content #fb-app-catalogs h3 { | ||
196 | + font-size: 120%; | ||
197 | + color: inherit; | ||
198 | + margin-top: 20px; | ||
199 | +} | ||
200 | +#fb-app-catalogs label { | ||
201 | + margin-top: 20px; | ||
202 | +} | ||
203 | +.fb-app-submit-page-tab-options { | ||
204 | + margin-top: 20px; | ||
205 | +} | ||
206 | +.fb-app-final-back-button { | ||
207 | + margin-top: 70px; | ||
208 | +} | ||
209 | +.tokenfield .token { | ||
210 | + height: auto !important; | ||
211 | +} | ||
212 | + | ||
213 | +@media (min-width: 768px) { | ||
214 | + #noosfero-identity { | ||
215 | + float: left; | ||
216 | + } | ||
217 | + #fb-connected { | ||
218 | + font-size: 36px; | ||
219 | + float:left; | ||
220 | + margin: 0 30px; | ||
221 | + color: #99f; | ||
222 | + } | ||
223 | + #fb-identity { | ||
224 | + float: left; | ||
225 | + } | ||
226 | + #fb-app-timeline, #fb-app-catalogs { | ||
227 | + width: 50%-$marginp; | ||
228 | + float: left; | ||
229 | + } | ||
230 | + #fb-app-settings { | ||
231 | + overflow: hidden; | ||
232 | + padding-bottom: 400px; | ||
233 | + } | ||
234 | + #fb-app-catalogs { | ||
235 | + margin-right: $marginp; | ||
236 | + } | ||
237 | +} | ||
238 | + |
plugins/fb_app/public/stylesheets/tokenfield-typeahead.css
0 → 100644
@@ -0,0 +1,141 @@ | @@ -0,0 +1,141 @@ | ||
1 | +/*! | ||
2 | + * bootstrap-tokenfield | ||
3 | + * https://github.com/sliptree/bootstrap-tokenfield | ||
4 | + * Copyright 2013-2014 Sliptree and other contributors; Licensed MIT | ||
5 | + */ | ||
6 | +/* General Typeahead styling, from http://jsfiddle.net/ragulka/Dy9au/1/ */ | ||
7 | +.twitter-typeahead { | ||
8 | + width: 100%; | ||
9 | + position: relative; | ||
10 | + vertical-align: top; | ||
11 | +} | ||
12 | +.twitter-typeahead .tt-input, | ||
13 | +.twitter-typeahead .tt-hint { | ||
14 | + margin: 0; | ||
15 | + width: 100%; | ||
16 | + vertical-align: middle; | ||
17 | + background-color: #ffffff; | ||
18 | +} | ||
19 | +.twitter-typeahead .tt-hint { | ||
20 | + color: #999999; | ||
21 | + z-index: 1; | ||
22 | + border: 1px solid transparent; | ||
23 | +} | ||
24 | +.twitter-typeahead .tt-input { | ||
25 | + color: #555555; | ||
26 | + z-index: 2; | ||
27 | +} | ||
28 | +.twitter-typeahead .tt-input, | ||
29 | +.twitter-typeahead .tt-hint { | ||
30 | + height: 34px; | ||
31 | + padding: 6px 12px; | ||
32 | + font-size: 14px; | ||
33 | + line-height: 1.428571429; | ||
34 | +} | ||
35 | +.twitter-typeahead .input-sm.tt-input, | ||
36 | +.twitter-typeahead .hint-sm.tt-hint { | ||
37 | + border-radius: 3px; | ||
38 | +} | ||
39 | +.twitter-typeahead .input-lg.tt-input, | ||
40 | +.twitter-typeahead .hint-lg.tt-hint { | ||
41 | + border-radius: 6px; | ||
42 | +} | ||
43 | +.input-group .twitter-typeahead:first-child .tt-input, | ||
44 | +.input-group .twitter-typeahead:first-child .tt-hint { | ||
45 | + border-radius: 4px 0 0 4px !important; | ||
46 | +} | ||
47 | +.input-group .twitter-typeahead:last-child .tt-input, | ||
48 | +.input-group .twitter-typeahead:last-child .tt-hint { | ||
49 | + border-radius: 0 4px 4px 0 !important; | ||
50 | +} | ||
51 | +.input-group.input-group-sm .twitter-typeahead:first-child .tt-input, | ||
52 | +.input-group.input-group-sm .twitter-typeahead:first-child .tt-hint { | ||
53 | + border-radius: 3px 0 0 3px !important; | ||
54 | +} | ||
55 | +.input-group.input-group-sm .twitter-typeahead:last-child .tt-input, | ||
56 | +.input-group.input-group-sm .twitter-typeahead:last-child .tt-hint { | ||
57 | + border-radius: 0 3px 3px 0 !important; | ||
58 | +} | ||
59 | +.input-sm.tt-input, | ||
60 | +.hint-sm.tt-hint, | ||
61 | +.input-group.input-group-sm .tt-input, | ||
62 | +.input-group.input-group-sm .tt-hint { | ||
63 | + height: 30px; | ||
64 | + padding: 5px 10px; | ||
65 | + font-size: 12px; | ||
66 | + line-height: 1.5; | ||
67 | +} | ||
68 | +.input-group.input-group-lg .twitter-typeahead:first-child .tt-input, | ||
69 | +.input-group.input-group-lg .twitter-typeahead:first-child .tt-hint { | ||
70 | + border-radius: 6px 0 0 6px !important; | ||
71 | +} | ||
72 | +.input-group.input-group-lg .twitter-typeahead:last-child .tt-input, | ||
73 | +.input-group.input-group-lg .twitter-typeahead:last-child .tt-hint { | ||
74 | + border-radius: 0 6px 6px 0 !important; | ||
75 | +} | ||
76 | +.input-lg.tt-input, | ||
77 | +.hint-lg.tt-hint, | ||
78 | +.input-group.input-group-lg .tt-input, | ||
79 | +.input-group.input-group-lg .tt-hint { | ||
80 | + height: 45px; | ||
81 | + padding: 10px 16px; | ||
82 | + font-size: 18px; | ||
83 | + line-height: 1.33; | ||
84 | +} | ||
85 | +.tt-dropdown-menu { | ||
86 | + width: 100%; | ||
87 | + min-width: 160px; | ||
88 | + margin-top: 2px; | ||
89 | + padding: 5px 0; | ||
90 | + background-color: #ffffff; | ||
91 | + border: 1px solid #ccc; | ||
92 | + border: 1px solid rgba(0, 0, 0, 0.15); | ||
93 | + *border-right-width: 2px; | ||
94 | + *border-bottom-width: 2px; | ||
95 | + border-radius: 6px; | ||
96 | + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); | ||
97 | + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); | ||
98 | + -webkit-background-clip: padding-box; | ||
99 | + -moz-background-clip: padding; | ||
100 | + background-clip: padding-box; | ||
101 | +} | ||
102 | +.tt-suggestion { | ||
103 | + display: block; | ||
104 | + padding: 3px 20px; | ||
105 | +} | ||
106 | +.tt-suggestion.tt-cursor { | ||
107 | + color: #262626; | ||
108 | + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); | ||
109 | + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); | ||
110 | + background-repeat: repeat-x; | ||
111 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); | ||
112 | +} | ||
113 | +.tt-suggestion.tt-cursor a { | ||
114 | + color: #ffffff; | ||
115 | +} | ||
116 | +.tt-suggestion p { | ||
117 | + margin: 0; | ||
118 | +} | ||
119 | +/* Tokenfield-specific Typeahead styling */ | ||
120 | +.tokenfield .twitter-typeahead { | ||
121 | + width: auto; | ||
122 | +} | ||
123 | +.tokenfield .twitter-typeahead .tt-hint { | ||
124 | + padding: 0; | ||
125 | + height: 20px; | ||
126 | +} | ||
127 | +.tokenfield.input-sm .twitter-typeahead .tt-input, | ||
128 | +.tokenfield.input-sm .twitter-typeahead .tt-hint { | ||
129 | + height: 18px; | ||
130 | + font-size: 12px; | ||
131 | + line-height: 1.5; | ||
132 | +} | ||
133 | +.tokenfield.input-lg .twitter-typeahead .tt-input, | ||
134 | +.tokenfield.input-lg .twitter-typeahead .tt-hint { | ||
135 | + height: 23px; | ||
136 | + font-size: 18px; | ||
137 | + line-height: 1.33; | ||
138 | +} | ||
139 | +.tokenfield .twitter-typeahead .tt-suggestions { | ||
140 | + font-size: 14px; | ||
141 | +} |
@@ -0,0 +1,39 @@ | @@ -0,0 +1,39 @@ | ||
1 | += content_for :head do | ||
2 | + = javascript_include_tag 'typeahead.bundle.js' | ||
3 | + = stylesheet_link_tag 'typeahead' | ||
4 | + = stylesheet_link_tag 'plugins/fb_app/stylesheets/bootstrap-tokenfield.css' | ||
5 | + = stylesheet_link_tag 'plugins/fb_app/stylesheets/tokenfield-typeahead.css' | ||
6 | + = javascript_include_tag 'plugins/fb_app/javascripts/bootstrap-tokenfield.js' | ||
7 | + = javascript_include_tag 'plugins/open_graph/javascripts/open_graph.js' | ||
8 | + | ||
9 | +- callback = '' unless defined? callback | ||
10 | + | ||
11 | +#fb-root | ||
12 | +#fb-app-modal-wrap style="display:none" | ||
13 | + #fb-app-modal | ||
14 | + #fb-app-modal-intro | ||
15 | + = button_to_function 'cancel', '', "ff()", class: 'modal-button-no' | ||
16 | + = button_to_function 'ok', '', "ff()", class: 'modal-button-yes' | ||
17 | + | ||
18 | +javascript: | ||
19 | + // Adding locales: | ||
20 | + fb_app.locales.error_empty_title = #{t('fb_app_plugin.views.myprofile.error.empty_title').to_json} | ||
21 | + fb_app.locales.error_empty_settings = #{t('fb_app_plugin.views.myprofile.error.empty_settings').to_json} | ||
22 | + fb_app.locales.cancel_button = #{t('fb_app_plugin.views.myprofile.catalogs.cancel_button').to_json} | ||
23 | + fb_app.locales.confirm_removal = #{t('fb_app_plugin.views.myprofile.catalogs.confirm_removal').to_json} | ||
24 | + fb_app.locales.confirm_removal_button = #{t('fb_app_plugin.views.myprofile.catalogs.confirm_removal_button').to_json} | ||
25 | + fb_app.locales.confirm_disconnect = #{t('fb_app_plugin.views.myprofile.catalogs.confirm_disconnect').to_json} | ||
26 | + fb_app.locales.confirm_disconnect_button = #{t('fb_app_plugin.views.myprofile.catalogs.confirm_disconnect_button').to_json} | ||
27 | + | ||
28 | + // General settings: | ||
29 | + fb_app.current_url = #{url_for(params).to_json}; | ||
30 | + fb_app.base_url = 'https://#{environment.default_hostname}/plugin/fb_app'; | ||
31 | + | ||
32 | + fb_app.page_tab.appId = #{FbAppPlugin.page_tab_app_credentials[:id].to_json}, | ||
33 | + fb_app.timeline.appId = #{FbAppPlugin.timeline_app_credentials[:id].to_json}, | ||
34 | + fb_app.page_tab.nextUrl = #{url_for(protocol: 'https', only_path: false).to_json} | ||
35 | + | ||
36 | + fb_app.fb.prepareAsyncInit(fb_app.timeline.appId, #{callback.to_json}); | ||
37 | + fb_app.fb.init(); | ||
38 | +/ must come after window.fbAsyncInit is defined | ||
39 | += javascript_include_tag "https://connect.facebook.net/en_US/all.js" |
plugins/fb_app/views/fb_app_plugin_layouts/default.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_myprofile/_auth.html.slim
0 → 100644
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +- if @auth.connected? | ||
2 | + #noosfero-identity | ||
3 | + = profile_image profile | ||
4 | + = profile.name | ||
5 | + span#fb-connected.fa.fa-arrows-h | ||
6 | + #fb-identity | ||
7 | + = render 'identity', auth: @auth | ||
8 | + | ||
9 | + = button_to_function 'close', t('fb_app_plugin.views.myprofile.disconnect'), 'fb_app.timeline.disconnect()', class:'fb-app-connection-button' | ||
10 | + | ||
11 | +- elsif @auth.not_authorized? | ||
12 | + = button_to_function 'login', t('fb_app_plugin.views.myprofile.connect'), 'fb_app.timeline.connect()', size: '', option: 'primary', class:'fb-app-connection-button' | ||
13 | +- elsif @auth.expired? | ||
14 | + = button_to_function 'login', t('fb_app_plugin.views.myprofile.reconnect'), 'fb_app.timeline.reconnect()', class:'fb-app-connection-button' |
plugins/fb_app/views/fb_app_plugin_myprofile/_catalogs.html.slim
0 → 100644
@@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
1 | +h2 | ||
2 | + = t'fb_app_plugin.views.myprofile.catalogs.heading' | ||
3 | + = render 'catalogs_help' rescue nil | ||
4 | + | ||
5 | +- profile.fb_app_page_tabs.each do |page_tab| | ||
6 | + = render 'fb_app_plugin_page_tab/config', page_tab: page_tab | ||
7 | + | ||
8 | +#new-catalog | ||
9 | + = render 'fb_app_plugin_page_tab/config', page_tab: profile.fb_app_page_tabs.build(owner_profile: profile) | ||
10 | + | ||
11 | += render file: 'shared/tiny_mce', locals: {mode: 'simple'} |
plugins/fb_app/views/fb_app_plugin_myprofile/_identity.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_myprofile/_load.html.slim
0 → 100644
@@ -0,0 +1,7 @@ | @@ -0,0 +1,7 @@ | ||
1 | += render 'fb_app_plugin/load', callback: 'fb_app.fb.checkLoginStatus()' | ||
2 | + | ||
3 | +javascript: | ||
4 | + fb_app.config.url_prefix = #{url_for(action: :index).to_json} | ||
5 | + fb_app.config.save_auth_url = #{url_for(action: :save_auth).to_json} | ||
6 | + fb_app.config.show_login_url = #{url_for(action: :show_login).to_json} | ||
7 | + |
plugins/fb_app/views/fb_app_plugin_myprofile/_settings.html.slim
0 → 100644
@@ -0,0 +1,28 @@ | @@ -0,0 +1,28 @@ | ||
1 | +h1= t'fb_app_plugin.lib.plugin.name' | ||
2 | += button :back, _('Back to control panel'), controller: 'profile_editor' | ||
3 | + | ||
4 | +#fb-app-connect-status | ||
5 | + = render 'intro' rescue nil if @auth.not_authorized? | ||
6 | + #fb-app-auth | ||
7 | + = render 'auth' | ||
8 | + | ||
9 | +- if @auth.connected? or Rails.env.development? | ||
10 | + #fb-app-catalogs | ||
11 | + = render 'catalogs' | ||
12 | + #fb-app-timeline | ||
13 | + - if profile.person? | ||
14 | + h2= t'fb_app_plugin.views.myprofile.timeline.heading' | ||
15 | + | ||
16 | + - unless FbAppPlugin.test_user? user | ||
17 | + h3= t'fb_app_plugin.views.myprofile.timeline.explanation_title' | ||
18 | + p= t'fb_app_plugin.views.myprofile.timeline.explanation_text' | ||
19 | + - else | ||
20 | + #track-form | ||
21 | + = render 'track_form', context: :fb_app | ||
22 | + - else | ||
23 | + = t'fb_app_plugin.views.myprofile.timeline.organization_redirect', | ||
24 | + type: t("fb_app_plugin.views.myprofile.timeline.organization_from_#{profile.class.name.underscore}"), | ||
25 | + redirect_link: link_to(t('fb_app_plugin.views.myprofile.timeline.redirect_link'), host: FbAppPlugin.config[:app][:domain], profile: user.identifier, controller: :fb_app_plugin_myprofile) | ||
26 | +.clean | ||
27 | + | ||
28 | += button :back, _('Back to control panel'), {controller: 'profile_editor'}, class: 'fb-app-final-back-button' |
plugins/fb_app/views/fb_app_plugin_myprofile/index.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_page_tab/_config.html.slim
0 → 100644
@@ -0,0 +1,13 @@ | @@ -0,0 +1,13 @@ | ||
1 | +.page-tab id="page-tab-#{page_tab.id || 'new'}" | ||
2 | + - if page_tab.page_id | ||
3 | + h3 | ||
4 | + = page_tab.title | ||
5 | + = button_to_function_without_text 'edit', t('fb_app_plugin.views.myprofile.catalogs.edit_button') % {catalog_title: page_tab.title}, | ||
6 | + "fb_app.page_tab.config.edit($(this))", class: 'edit-tab-button' | ||
7 | + = button_to_function_without_text 'remove', t('fb_app_plugin.views.myprofile.catalogs.remove_button') % {catalog_title: page_tab.title}, | ||
8 | + "fb_app.page_tab.config.remove($(this), '#{url_for(controller: :fb_app_plugin_page_tab, action: :destroy, id: page_tab.id)}')", class: 'edit-tab-button' | ||
9 | + = button_without_text 'eyes', t('fb_app_plugin.views.myprofile.catalog.see_page'), page_tab.facebook_url, target: '_blank', class: 'edit-tab-button' | ||
10 | + - else | ||
11 | + = button_to_function 'add', t('fb_app_plugin.views.myprofile.catalogs.new'), "$(this).toggle(); $('#page-tab-new h3, #add_tab').toggle(400)" | ||
12 | + | ||
13 | + = render 'fb_app_plugin_page_tab/configure_form', page_tab: page_tab, signed_request: nil |
plugins/fb_app/views/fb_app_plugin_page_tab/_configure_button.html.slim
0 → 100644
@@ -0,0 +1,7 @@ | @@ -0,0 +1,7 @@ | ||
1 | +#manage-fb-store-ctrl | ||
2 | + - if @page_tab.owner_profile | ||
3 | + - if logged_in? and (user.is_admin? environment or user.is_admin? @page_tab.owner_profile) | ||
4 | + = button :edit, t('fb_app_plugin.views.page_tab.edit_catalog'), {controller: :fb_app_plugin_myprofile, profile: @page_tab.owner_profile.identifier}, | ||
5 | + target: '_parent' | ||
6 | + - elsif (@data[:page][:admin] rescue false) | ||
7 | + = button :edit, t('fb_app_plugin.views.page_tab.edit_catalog'), {controller: :fb_app_plugin_page_tab, action: :admin, signed_request: @signed_requests, page_id: @page_ids}, onclick: "noosfero.modal.url(this.href); return false;" |
plugins/fb_app/views/fb_app_plugin_page_tab/_configure_form.html.slim
0 → 100644
@@ -0,0 +1,37 @@ | @@ -0,0 +1,37 @@ | ||
1 | +- form_id = if page_tab.id then "edit_tab_#{page_tab.id}" else "add_tab" end | ||
2 | + | ||
3 | +- unless page_tab.id | ||
4 | + h3= t'fb_app_plugin.views.myprofile.catalogs.new' | ||
5 | + | ||
6 | += form_for page_tab, as: :page_tab, url: {controller: :fb_app_plugin_page_tab, action: :admin}, | ||
7 | + html: {id: form_id, class: "edit-page-tab", onsubmit: "return fb_app.page_tab.config.save($(this))"} do |f| | ||
8 | + | ||
9 | + = hidden_field_tag :signed_request, signed_request | ||
10 | + = hidden_field_tag :page_id, page_tab.page_id | ||
11 | + = f.hidden_field :profile_id, value: profile.id | ||
12 | + = f.hidden_field :page_id | ||
13 | + | ||
14 | + = f.label :title, t("fb_app_plugin.views.myprofile.catalogs.catalog_title_label") | ||
15 | + = f.text_field :title, class: 'form-control' | ||
16 | + | ||
17 | + = f.label :subtitle, t("fb_app_plugin.views.myprofile.catalogs.catalog_subtitle_label") | ||
18 | + = f.text_area :subtitle, class: 'form-control mceEditor', id: "page-tab-subtitle-#{page_tab.id}" | ||
19 | + | ||
20 | + = f.label :config_type, t("fb_app_plugin.views.myprofile.catalogs.catalog_type_chooser_label") | ||
21 | + = f.select :config_type, | ||
22 | + page_tab.types.map{ |type| [t("fb_app_plugin.models.page_tab.types.#{if profile.enterprise? and type == :profile then :other_profile else type end}"), type] }, | ||
23 | + {}, onchange: 'fb_app.page_tab.config.change_type($(this))', class: 'form-control' | ||
24 | + | ||
25 | + - page_tab.types.each do |type| | ||
26 | + div class="config-type config-type-#{type}" | ||
27 | + = render "fb_app_plugin_page_tab/configure_#{type}", f: f, page_tab: page_tab | ||
28 | + | ||
29 | + - if page_tab.new_record? | ||
30 | + = submit_button :add, t('fb_app_plugin.views.page_tab.add'), onclick: 'return fb_app.page_tab.config.add($(this.form))', class: 'fb-app-submit-page-tab-options' | ||
31 | + - else | ||
32 | + = submit_button :save, t('fb_app_plugin.views.page_tab.save'), class: 'fb-app-submit-page-tab-options' | ||
33 | + | ||
34 | +javascript: | ||
35 | + $('document').ready(function() { | ||
36 | + fb_app.page_tab.config.init(); | ||
37 | + }); |
plugins/fb_app/views/fb_app_plugin_page_tab/_configure_own_profile.html.slim
0 → 100644
No preview for this file type
plugins/fb_app/views/fb_app_plugin_page_tab/_configure_profile.html.slim
0 → 100644
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | += f.label t("fb_app_plugin.views.myprofile.catalogs.profile_chooser_label") | ||
2 | += f.text_field :profile_ids, "data-limit" => "1", "data-placeholder" => t('fb_app_plugin.views.page_tab.profile.placeholder'), onchange: 'fb_app.page_tab.config.profile.onchange($(this))' | ||
3 | + | ||
4 | +javascript: | ||
5 | + $(document).ready(function() { | ||
6 | + var selector = '#page-tab-#{page_tab.id || 'new'} .config-type-profile #page_tab_profile_ids' | ||
7 | + | ||
8 | + fb_app.page_tab.config.profile.onchange($(selector)) | ||
9 | + | ||
10 | + open_graph.autocomplete.init( | ||
11 | + #{url_for(controller: :fb_app_plugin_page_tab, action: :enterprise_search).to_json}, | ||
12 | + selector, | ||
13 | + #{[page_tab.profile].compact.map{ |p| {value: p.id, label: render('open_graph_plugin/myprofile/ac_profile', profile: p), } }.to_json}, | ||
14 | + {tokenfield: {limit: 1}} | ||
15 | + ) | ||
16 | + | ||
17 | + if (#{page_tab.profile.present?.to_json}) | ||
18 | + $(selector+'-tokenfield').hide() | ||
19 | + | ||
20 | + $(selector) | ||
21 | + .on('tokenfield:createdtoken', function (e) { | ||
22 | + $(selector+'-tokenfield').hide(); | ||
23 | + }) | ||
24 | + .on('tokenfield:removedtoken', function (e) { | ||
25 | + $(selector+'-tokenfield').show(); | ||
26 | + }) | ||
27 | + }) |
plugins/fb_app/views/fb_app_plugin_page_tab/_configure_profiles.html.slim
0 → 100644
@@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
1 | += f.label t("fb_app_plugin.views.myprofile.catalogs.profiles_chooser_label") | ||
2 | += f.text_field :profile_ids, placeholder: t('fb_app_plugin.views.page_tab.profiles.placeholder') | ||
3 | + | ||
4 | +javascript: | ||
5 | + $(document).ready(function() { | ||
6 | + open_graph.autocomplete.init( | ||
7 | + #{url_for(controller: :fb_app_plugin_page_tab, action: :enterprise_search).to_json}, | ||
8 | + '#page-tab-#{page_tab.id || 'new'} .config-type-profiles #page_tab_profile_ids', | ||
9 | + #{page_tab.profiles.map{ |p| {value: p.id, label: render('open_graph_plugin/myprofile/ac_profile', profile: p), } }.to_json} | ||
10 | + ) | ||
11 | + }) |
plugins/fb_app/views/fb_app_plugin_page_tab/_configure_query.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_page_tab/_footer.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_page_tab/_load.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_page_tab/_title_and_subtitle.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_page_tab/admin.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_page_tab/admin.js.erb
0 → 100644
plugins/fb_app/views/fb_app_plugin_page_tab/catalog.html.slim
0 → 100644
@@ -0,0 +1,21 @@ | @@ -0,0 +1,21 @@ | ||
1 | +#fb-app-catalog-wrapper class=('fb-app-standalone' if @signed_request.blank?) | ||
2 | + | ||
3 | + = render 'load' | ||
4 | + = render 'title_and_subtitle' | ||
5 | + | ||
6 | + #product-catalog | ||
7 | + #product-catalog-actions | ||
8 | + - if @page_tab.config_type == :profile | ||
9 | + - if profile and user.present? and (user.is_admin?(environment) or user.is_admin?(profile)) | ||
10 | + .product-catalog-ctrl | ||
11 | + = button :add, _('Add product or service'), controller: :manage_products, action: :new, profile: profile.identifier | ||
12 | + = render 'configure_button' | ||
13 | + = content_for :product_page do | ||
14 | + = render 'catalog/results' | ||
15 | + = render 'catalog/search' | ||
16 | + = render 'catalog/javascripts', external: false | ||
17 | + | ||
18 | + = render 'footer' | ||
19 | + | ||
20 | + javascript: | ||
21 | + catalog.base_url_path = #{url_for(controller: :fb_app_plugin_page_tab, action: :index, page_id: params[:page_id], signed_request: params[:signed_request]).to_json} + '&' |
plugins/fb_app/views/fb_app_plugin_page_tab/first_load.html.slim
0 → 100644
plugins/fb_app/views/fb_app_plugin_page_tab/product.html.slim
0 → 100644
@@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
1 | += render 'title_and_subtitle' | ||
2 | + | ||
3 | += button :back, t('fb_app_plugin.views.page_tab.back_to_catalog'), params.except(:product_id), target: '' | ||
4 | + | ||
5 | += render file: "#{Rails.root}/app/views/manage_products/show.html.erb" | ||
6 | + | ||
7 | += button :back, t('fb_app_plugin.views.page_tab.back_to_catalog'), params.except(:product_id), target: '' | ||
8 | + | ||
9 | += render 'footer' | ||
10 | + | ||
11 | += render 'load' |
plugins/metadata/lib/ext/article.rb
@@ -12,9 +12,9 @@ class Article | @@ -12,9 +12,9 @@ class Article | ||
12 | end, | 12 | end, |
13 | title: proc{ |a, plugin| "#{a.title} - #{a.profile.name}" }, | 13 | title: proc{ |a, plugin| "#{a.title} - #{a.profile.name}" }, |
14 | image: proc do |a, plugin| | 14 | image: proc do |a, plugin| |
15 | - img = a.body_images_paths | ||
16 | - img = "#{a.profile.environment.top_url}#{a.profile.image.public_filename}" if a.profile.image if img.blank? | ||
17 | - img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank? | 15 | + img = a.body_images_paths.map! &:html_safe |
16 | + img = "#{a.profile.environment.top_url}#{a.profile.image.public_filename}".html_safe if a.profile.image if img.blank? | ||
17 | + img ||= MetadataPlugin.config[:open_graph][:environment_logo].html_safe rescue nil if img.blank? | ||
18 | img | 18 | img |
19 | end, | 19 | end, |
20 | see_also: [], | 20 | see_also: [], |
@@ -31,10 +31,10 @@ class Article | @@ -31,10 +31,10 @@ class Article | ||
31 | card: 'summary', | 31 | card: 'summary', |
32 | description: proc do |a, plugin| | 32 | description: proc do |a, plugin| |
33 | description = a.body.to_s || a.environment.name | 33 | description = a.body.to_s || a.environment.name |
34 | - CGI.escapeHTML(plugin.helpers.truncate(plugin.helpers.strip_tags(description), length: 200)) | 34 | + plugin.helpers.truncate plugin.helpers.strip_tags(description), length: 200 |
35 | end, | 35 | end, |
36 | title: proc{ |a, plugin| "#{a.title} - #{a.profile.name}" }, | 36 | title: proc{ |a, plugin| "#{a.title} - #{a.profile.name}" }, |
37 | - image: proc{ |a, plugin| a.body_images_paths }, | 37 | + image: proc{ |a, plugin| a.body_images_paths.map! &:html_safe }, |
38 | } | 38 | } |
39 | 39 | ||
40 | metadata_spec namespace: :article, key_attr: :property, tags: { | 40 | metadata_spec namespace: :article, key_attr: :property, tags: { |
plugins/metadata/lib/ext/product.rb
@@ -14,8 +14,8 @@ class Product | @@ -14,8 +14,8 @@ class Product | ||
14 | description: proc{ |p, plugin| ActionView::Base.full_sanitizer.sanitize p.description }, | 14 | description: proc{ |p, plugin| ActionView::Base.full_sanitizer.sanitize p.description }, |
15 | 15 | ||
16 | image: proc do |p, plugin| | 16 | image: proc do |p, plugin| |
17 | - img = "#{p.environment.top_url}#{p.image.public_filename}" if p.image | ||
18 | - img = "#{p.environment.top_url}#{p.profile.image.public_filename}" if img.blank? and p.profile.image | 17 | + img = "#{p.environment.top_url}#{p.image.public_filename}".html_safe if p.image |
18 | + img = "#{p.environment.top_url}#{p.profile.image.public_filename}".html_safe if img.blank? and p.profile.image | ||
19 | img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank? | 19 | img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank? |
20 | img | 20 | img |
21 | end, | 21 | end, |
plugins/metadata/lib/ext/profile.rb
@@ -5,8 +5,8 @@ class Profile | @@ -5,8 +5,8 @@ class Profile | ||
5 | metadata_spec namespace: :og, tags: { | 5 | metadata_spec namespace: :og, tags: { |
6 | type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:profile] || :profile }, | 6 | type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:profile] || :profile }, |
7 | image: proc do |p, plugin| | 7 | image: proc do |p, plugin| |
8 | - img = "#{p.environment.top_url}#{p.image.public_filename}" if p.image | ||
9 | - img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank? | 8 | + img = "#{p.environment.top_url}#{p.image.public_filename}".html_safe if p.image |
9 | + img ||= MetadataPlugin.config[:open_graph][:environment_logo].html_safe rescue nil if img.blank? | ||
10 | img | 10 | img |
11 | end, | 11 | end, |
12 | title: proc{ |p, plugin| if p.nickname.present? then p.nickname else p.name end }, | 12 | title: proc{ |p, plugin| if p.nickname.present? then p.nickname else p.name end }, |
plugins/metadata/lib/ext/uploaded_file.rb
@@ -13,7 +13,7 @@ class UploadedFile | @@ -13,7 +13,7 @@ class UploadedFile | ||
13 | plugin.og_url_for url | 13 | plugin.og_url_for url |
14 | end, | 14 | end, |
15 | title: proc{ |u, plugin| u.title }, | 15 | title: proc{ |u, plugin| u.title }, |
16 | - image: proc{ |u, plugin| "#{u.environment.top_url}#{u.public_filename}" if u.image? }, | 16 | + image: proc{ |u, plugin| "#{u.environment.top_url}#{u.public_filename}".html_safe if u.image? }, |
17 | description: proc{ |u, plugin| u.abstract || u.title }, | 17 | description: proc{ |u, plugin| u.abstract || u.title }, |
18 | } | 18 | } |
19 | 19 |
plugins/metadata/lib/metadata_plugin/base.rb
@@ -50,7 +50,8 @@ class MetadataPlugin::Base < Noosfero::Plugin | @@ -50,7 +50,8 @@ class MetadataPlugin::Base < Noosfero::Plugin | ||
50 | Array(values).each do |value| | 50 | Array(values).each do |value| |
51 | value = value.call(object, plugin) if value.is_a? Proc rescue nil | 51 | value = value.call(object, plugin) if value.is_a? Proc rescue nil |
52 | next if value.blank? | 52 | next if value.blank? |
53 | - r << tag(:meta, key_attr => key, value_attr => CGI.escape_html(value.to_s)) | 53 | + value = h value unless value.html_safe? |
54 | + r << tag(:meta, {key_attr => key, value_attr => value.to_s}, false, false) | ||
54 | end | 55 | end |
55 | end | 56 | end |
56 | end | 57 | end |
plugins/metadata/lib/metadata_plugin/url_helper.rb
@@ -7,7 +7,8 @@ module MetadataPlugin::UrlHelper | @@ -7,7 +7,8 @@ module MetadataPlugin::UrlHelper | ||
7 | def og_url_for options | 7 | def og_url_for options |
8 | options.delete :port | 8 | options.delete :port |
9 | options[:host] = self.og_domain | 9 | options[:host] = self.og_domain |
10 | - Noosfero::Application.routes.url_helpers.url_for options | 10 | + url = Noosfero::Application.routes.url_helpers.url_for options |
11 | + url.html_safe | ||
11 | end | 12 | end |
12 | 13 | ||
13 | def og_profile_url profile | 14 | def og_profile_url profile |
plugins/metadata/test/functional/content_viewer_controller_test.rb
@@ -45,6 +45,14 @@ class ContentViewerControllerTest < ActionController::TestCase | @@ -45,6 +45,14 @@ class ContentViewerControllerTest < ActionController::TestCase | ||
45 | assert_tag tag: 'meta', attributes: { property: 'og:image', content: /\/images\/x.png/ } | 45 | assert_tag tag: 'meta', attributes: { property: 'og:image', content: /\/images\/x.png/ } |
46 | end | 46 | end |
47 | 47 | ||
48 | + should 'escape utf8 characters correctly' do | ||
49 | + a = TinyMceArticle.create(name: 'Article to be shared with images', body: 'This article should be shared with all social networks <img src="/images/ç.png" />', profile: profile) | ||
50 | + | ||
51 | + get :view_page, profile: profile.identifier, page: [ a.name.to_slug ] | ||
52 | + assert_tag tag: 'meta', attributes: { property: 'og:image', content: /\/images\/%C3%A7.png/ } | ||
53 | + end | ||
54 | + | ||
55 | + | ||
48 | should 'render not_found page properly' do | 56 | should 'render not_found page properly' do |
49 | assert_equal false, Article.exists?(:slug => 'non-existing-page') | 57 | assert_equal false, Article.exists?(:slug => 'non-existing-page') |
50 | assert_nothing_raised do | 58 | assert_nothing_raised do |
plugins/oauth_client/controllers/public/oauth_client_plugin_public_controller.rb
@@ -4,8 +4,8 @@ class OauthClientPluginPublicController < PublicController | @@ -4,8 +4,8 @@ class OauthClientPluginPublicController < PublicController | ||
4 | 4 | ||
5 | def callback | 5 | def callback |
6 | auth = request.env["omniauth.auth"] | 6 | auth = request.env["omniauth.auth"] |
7 | - user = environment.users.find_by_email(auth.info.email) | ||
8 | - user ? login(user) : signup(auth) | 7 | + auth_user = environment.users.where(email: auth.info.email).first |
8 | + if auth_user then login auth_user.person else signup auth end | ||
9 | end | 9 | end |
10 | 10 | ||
11 | def failure | 11 | def failure |
@@ -20,14 +20,12 @@ class OauthClientPluginPublicController < PublicController | @@ -20,14 +20,12 @@ class OauthClientPluginPublicController < PublicController | ||
20 | 20 | ||
21 | protected | 21 | protected |
22 | 22 | ||
23 | - def login(user) | 23 | + def login person |
24 | provider = OauthClientPlugin::Provider.find(session[:provider_id]) | 24 | provider = OauthClientPlugin::Provider.find(session[:provider_id]) |
25 | - user_provider = user.oauth_user_providers.find_by_provider_id(provider.id) | ||
26 | - unless user_provider | ||
27 | - user_provider = user.oauth_user_providers.create(:user => user, :provider => provider, :enabled => true) | ||
28 | - end | ||
29 | - if user_provider.enabled? && provider.enabled? | ||
30 | - session[:user] = user.id | 25 | + auth = person.oauth_auths.where(provider_id: provider.id).first |
26 | + auth ||= person.oauth_auths.create! profile: person, provider: provider, enabled: true | ||
27 | + if auth.enabled? && provider.enabled? | ||
28 | + self.current_user = person.user | ||
31 | else | 29 | else |
32 | session[:notice] = _("Can't login with %s") % provider.name | 30 | session[:notice] = _("Can't login with %s") % provider.name |
33 | end | 31 | end |
plugins/oauth_client/db/migrate/20150815170000_deserialize_fields_on_oauth_client_plugin_provider.rb
0 → 100644
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +class DeserializeFieldsOnOauthClientPluginProvider < ActiveRecord::Migration | ||
2 | + | ||
3 | + def up | ||
4 | + add_column :oauth_client_plugin_providers, :client_id, :text | ||
5 | + add_column :oauth_client_plugin_providers, :client_secret, :text | ||
6 | + | ||
7 | + OauthClientPlugin::Provider.find_each batch_size: 50 do |provider| | ||
8 | + provider.client_id = provider.options.delete :client_id | ||
9 | + provider.client_secret = provider.options.delete :client_secret | ||
10 | + provider.save! | ||
11 | + end | ||
12 | + | ||
13 | + add_index :oauth_client_plugin_providers, :client_id | ||
14 | + end | ||
15 | + | ||
16 | + def down | ||
17 | + say "this migration can't be reverted" | ||
18 | + end | ||
19 | + | ||
20 | +end |
plugins/oauth_client/db/migrate/20150815173209_add_authorization_data_to_oauth_client_user_provider.rb
0 → 100644
@@ -0,0 +1,26 @@ | @@ -0,0 +1,26 @@ | ||
1 | +class AddAuthorizationDataToOauthClientUserProvider < ActiveRecord::Migration | ||
2 | + | ||
3 | + def change | ||
4 | + rename_table :oauth_client_plugin_user_providers, :oauth_client_plugin_auths | ||
5 | + | ||
6 | + add_column :oauth_client_plugin_auths, :type, :string | ||
7 | + add_column :oauth_client_plugin_auths, :provider_user_id, :string | ||
8 | + add_column :oauth_client_plugin_auths, :access_token, :text | ||
9 | + add_column :oauth_client_plugin_auths, :expires_at, :datetime | ||
10 | + add_column :oauth_client_plugin_auths, :scope, :text | ||
11 | + add_column :oauth_client_plugin_auths, :data, :text, default: {}.to_yaml | ||
12 | + | ||
13 | + add_column :oauth_client_plugin_auths, :profile_id, :integer | ||
14 | + OauthClientPlugin::Auth.find_each batch_size: 50 do |auth| | ||
15 | + auth.profile = User.find(auth.user_id).person | ||
16 | + auth.save! | ||
17 | + end | ||
18 | + remove_column :oauth_client_plugin_auths, :user_id | ||
19 | + | ||
20 | + add_index :oauth_client_plugin_auths, :profile_id | ||
21 | + add_index :oauth_client_plugin_auths, :provider_id | ||
22 | + add_index :oauth_client_plugin_auths, :provider_user_id | ||
23 | + add_index :oauth_client_plugin_auths, :type | ||
24 | + end | ||
25 | + | ||
26 | +end |
plugins/oauth_client/lib/ext/user.rb
@@ -2,8 +2,14 @@ require_dependency 'user' | @@ -2,8 +2,14 @@ require_dependency 'user' | ||
2 | 2 | ||
3 | class User | 3 | class User |
4 | 4 | ||
5 | - has_many :oauth_user_providers, :class_name => 'OauthClientPlugin::UserProvider' | ||
6 | - has_many :oauth_providers, :through => :oauth_user_providers, :source => :provider | 5 | + has_many :oauth_auths, through: :person |
6 | + has_many :oauth_providers, through: :oauth_auths, source: :provider | ||
7 | + | ||
8 | + after_create :activate_oauth_user | ||
9 | + | ||
10 | + def activate_oauth_user | ||
11 | + self.activate if oauth_providers.present? | ||
12 | + end | ||
7 | 13 | ||
8 | def password_required_with_oauth? | 14 | def password_required_with_oauth? |
9 | password_required_without_oauth? && oauth_providers.empty? | 15 | password_required_without_oauth? && oauth_providers.empty? |
@@ -11,17 +17,6 @@ class User | @@ -11,17 +17,6 @@ class User | ||
11 | 17 | ||
12 | alias_method_chain :password_required?, :oauth | 18 | alias_method_chain :password_required?, :oauth |
13 | 19 | ||
14 | - after_create :activate_oauth_user | ||
15 | - | ||
16 | - def activate_oauth_user | ||
17 | - unless oauth_providers.empty? | ||
18 | - activate | ||
19 | - oauth_providers.each do |provider| | ||
20 | - OauthClientPlugin::UserProvider.create!(:user => self, :provider => provider, :enabled => true) | ||
21 | - end | ||
22 | - end | ||
23 | - end | ||
24 | - | ||
25 | def make_activation_code_with_oauth | 20 | def make_activation_code_with_oauth |
26 | oauth_providers.blank? ? make_activation_code_without_oauth : nil | 21 | oauth_providers.blank? ? make_activation_code_without_oauth : nil |
27 | end | 22 | end |
plugins/oauth_client/lib/oauth_client_plugin.rb
@@ -45,7 +45,9 @@ class OauthClientPlugin < Noosfero::Plugin | @@ -45,7 +45,9 @@ class OauthClientPlugin < Noosfero::Plugin | ||
45 | true | 45 | true |
46 | end | 46 | end |
47 | 47 | ||
48 | - OmniAuth.config.on_failure = OauthClientPluginPublicController.action(:failure) | 48 | + Rails.configuration.to_prepare do |
49 | + OmniAuth.config.on_failure = OauthClientPluginPublicController.action(:failure) | ||
50 | + end | ||
49 | 51 | ||
50 | Rails.application.config.middleware.use OmniAuth::Builder do | 52 | Rails.application.config.middleware.use OmniAuth::Builder do |
51 | PROVIDERS.each do |provider, options| | 53 | PROVIDERS.each do |provider, options| |
@@ -60,7 +62,8 @@ class OauthClientPlugin < Noosfero::Plugin | @@ -60,7 +62,8 @@ class OauthClientPlugin < Noosfero::Plugin | ||
60 | provider_id = request.params['id'] | 62 | provider_id = request.params['id'] |
61 | provider_id ||= request.session['omniauth.params']['id'] if request.session['omniauth.params'] | 63 | provider_id ||= request.session['omniauth.params']['id'] if request.session['omniauth.params'] |
62 | provider = environment.oauth_providers.find(provider_id) | 64 | provider = environment.oauth_providers.find(provider_id) |
63 | - strategy.options.merge!(provider.options.symbolize_keys) | 65 | + strategy.options.merge! client_id: provider.client_id, client_secret: provider.client_secret |
66 | + strategy.options.merge! provider.options.symbolize_keys | ||
64 | 67 | ||
65 | request.session[:provider_id] = provider_id | 68 | request.session[:provider_id] = provider_id |
66 | } | 69 | } |
plugins/oauth_client/lib/oauth_client_plugin/provider.rb
@@ -1,20 +0,0 @@ | @@ -1,20 +0,0 @@ | ||
1 | -class OauthClientPlugin::Provider < ActiveRecord::Base | ||
2 | - | ||
3 | - belongs_to :environment | ||
4 | - | ||
5 | - validates_presence_of :name, :strategy | ||
6 | - | ||
7 | - acts_as_having_image | ||
8 | - acts_as_having_settings :field => :options | ||
9 | - | ||
10 | - settings_items :client_id, :type => :string | ||
11 | - settings_items :client_secret, :type => :string | ||
12 | - settings_items :client_options, :type => Hash | ||
13 | - | ||
14 | - attr_accessible :name, :environment, :strategy, :client_id, :client_secret, :enabled, :client_options, :image_builder | ||
15 | - | ||
16 | - scope :enabled, -> { where enabled: true } | ||
17 | - | ||
18 | - acts_as_having_image | ||
19 | - | ||
20 | -end |
plugins/oauth_client/lib/oauth_client_plugin/user_provider.rb
@@ -1,10 +0,0 @@ | @@ -1,10 +0,0 @@ | ||
1 | -class OauthClientPlugin::UserProvider < ActiveRecord::Base | ||
2 | - | ||
3 | - belongs_to :user, :class_name => 'User' | ||
4 | - belongs_to :provider, :class_name => 'OauthClientPlugin::Provider' | ||
5 | - | ||
6 | - self.table_name = :oauth_client_plugin_user_providers | ||
7 | - | ||
8 | - attr_accessible :user, :provider, :enabled | ||
9 | - | ||
10 | -end |
@@ -0,0 +1,29 @@ | @@ -0,0 +1,29 @@ | ||
1 | +class OauthClientPlugin::Auth < ActiveRecord::Base | ||
2 | + | ||
3 | + attr_accessible :profile, :provider, :enabled, | ||
4 | + :access_token, :expires_in | ||
5 | + | ||
6 | + belongs_to :profile, class_name: 'Profile' | ||
7 | + belongs_to :provider, class_name: 'OauthClientPlugin::Provider' | ||
8 | + | ||
9 | + validates_presence_of :profile | ||
10 | + validates_presence_of :provider | ||
11 | + validates_uniqueness_of :profile_id, scope: :provider_id | ||
12 | + | ||
13 | + acts_as_having_settings field: :data | ||
14 | + | ||
15 | + def expires_in | ||
16 | + self.expires_at - Time.now | ||
17 | + end | ||
18 | + def expires_in= value | ||
19 | + self.expires_at = Time.now + value.to_i | ||
20 | + end | ||
21 | + | ||
22 | + def expired? | ||
23 | + Time.now > self.expires_at rescue true | ||
24 | + end | ||
25 | + def not_expired? | ||
26 | + not self.expired? | ||
27 | + end | ||
28 | + | ||
29 | +end |
plugins/oauth_client/models/oauth_client_plugin/provider.rb
0 → 100644
@@ -0,0 +1,21 @@ | @@ -0,0 +1,21 @@ | ||
1 | +class OauthClientPlugin::Provider < ActiveRecord::Base | ||
2 | + | ||
3 | + belongs_to :environment | ||
4 | + | ||
5 | + validates_presence_of :name, :strategy | ||
6 | + | ||
7 | + acts_as_having_image | ||
8 | + acts_as_having_settings field: :options | ||
9 | + | ||
10 | + settings_items :site, type: String | ||
11 | + settings_items :client_options, type: Hash | ||
12 | + | ||
13 | + attr_accessible :name, :strategy, :enabled, :site, :image_builder, | ||
14 | + :environment, :environment_id, | ||
15 | + :client_id, :client_secret, :client_options | ||
16 | + | ||
17 | + scope :enabled, -> { where enabled: true } | ||
18 | + | ||
19 | + acts_as_having_image | ||
20 | + | ||
21 | +end |
plugins/oauth_client/test/functional/oauth_client_plugin_public_controller_test.rb
1 | -require_relative '../test_helper' | 1 | +require 'test_helper' |
2 | 2 | ||
3 | class OauthClientPluginPublicControllerTest < ActionController::TestCase | 3 | class OauthClientPluginPublicControllerTest < ActionController::TestCase |
4 | 4 | ||
@@ -21,7 +21,7 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | @@ -21,7 +21,7 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | ||
21 | end | 21 | end |
22 | 22 | ||
23 | should 'redirect to login when user is found' do | 23 | should 'redirect to login when user is found' do |
24 | - user = fast_create(User, :environment_id => environment.id) | 24 | + user = create_user |
25 | auth.info.stubs(:email).returns(user.email) | 25 | auth.info.stubs(:email).returns(user.email) |
26 | auth.info.stubs(:name).returns(user.name) | 26 | auth.info.stubs(:name).returns(user.name) |
27 | session[:provider_id] = provider.id | 27 | session[:provider_id] = provider.id |
@@ -32,7 +32,7 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | @@ -32,7 +32,7 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | ||
32 | end | 32 | end |
33 | 33 | ||
34 | should 'do not login when the provider is disabled' do | 34 | should 'do not login when the provider is disabled' do |
35 | - user = fast_create(User, :environment_id => environment.id) | 35 | + user = create_user |
36 | auth.info.stubs(:email).returns(user.email) | 36 | auth.info.stubs(:email).returns(user.email) |
37 | auth.info.stubs(:name).returns(user.name) | 37 | auth.info.stubs(:name).returns(user.name) |
38 | session[:provider_id] = provider.id | 38 | session[:provider_id] = provider.id |
@@ -44,11 +44,11 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | @@ -44,11 +44,11 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | ||
44 | end | 44 | end |
45 | 45 | ||
46 | should 'do not login when the provider is disabled for a user' do | 46 | should 'do not login when the provider is disabled for a user' do |
47 | - user = fast_create(User, :environment_id => environment.id) | 47 | + user = create_user |
48 | auth.info.stubs(:email).returns(user.email) | 48 | auth.info.stubs(:email).returns(user.email) |
49 | auth.info.stubs(:name).returns(user.name) | 49 | auth.info.stubs(:name).returns(user.name) |
50 | session[:provider_id] = provider.id | 50 | session[:provider_id] = provider.id |
51 | - user.oauth_user_providers.create(:user => user, :provider => provider, :enabled => false) | 51 | + user.person.oauth_auths.create!(profile: user.person, provider: provider, enabled: false) |
52 | 52 | ||
53 | get :callback | 53 | get :callback |
54 | assert_redirected_to :controller => :account, :action => :login | 54 | assert_redirected_to :controller => :account, :action => :login |
@@ -56,7 +56,7 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | @@ -56,7 +56,7 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | ||
56 | end | 56 | end |
57 | 57 | ||
58 | should 'save provider when an user login with it' do | 58 | should 'save provider when an user login with it' do |
59 | - user = fast_create(User, :environment_id => environment.id) | 59 | + user = create_user |
60 | auth.info.stubs(:email).returns(user.email) | 60 | auth.info.stubs(:email).returns(user.email) |
61 | auth.info.stubs(:name).returns(user.name) | 61 | auth.info.stubs(:name).returns(user.name) |
62 | session[:provider_id] = provider.id | 62 | session[:provider_id] = provider.id |
@@ -66,13 +66,13 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | @@ -66,13 +66,13 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | ||
66 | end | 66 | end |
67 | 67 | ||
68 | should 'do not duplicate relations between an user and a provider when the same provider was used again in a login' do | 68 | should 'do not duplicate relations between an user and a provider when the same provider was used again in a login' do |
69 | - user = fast_create(User, :environment_id => environment.id) | 69 | + user = create_user |
70 | auth.info.stubs(:email).returns(user.email) | 70 | auth.info.stubs(:email).returns(user.email) |
71 | auth.info.stubs(:name).returns(user.name) | 71 | auth.info.stubs(:name).returns(user.name) |
72 | session[:provider_id] = provider.id | 72 | session[:provider_id] = provider.id |
73 | 73 | ||
74 | get :callback | 74 | get :callback |
75 | - assert_no_difference 'user.oauth_user_providers.count' do | 75 | + assert_no_difference 'user.oauth_auths.count' do |
76 | 3.times { get :callback } | 76 | 3.times { get :callback } |
77 | end | 77 | end |
78 | end | 78 | end |
plugins/oauth_client/test/unit/environment_test.rb
plugins/oauth_client/test/unit/oauth_client_plugin_test.rb
plugins/oauth_client/test/unit/user_test.rb
plugins/open_graph/db/migrate/20150814200324_add_story_and_published_at_to_open_graph_plugin_activity.rb
0 → 100644
@@ -0,0 +1,10 @@ | @@ -0,0 +1,10 @@ | ||
1 | +class AddStoryAndPublishedAtToOpenGraphPluginActivity < ActiveRecord::Migration | ||
2 | + | ||
3 | + def change | ||
4 | + add_column :open_graph_plugin_tracks, :published_at, :datetime | ||
5 | + add_column :open_graph_plugin_tracks, :story, :string | ||
6 | + add_index :open_graph_plugin_tracks, :published_at | ||
7 | + add_index :open_graph_plugin_tracks, :story | ||
8 | + end | ||
9 | + | ||
10 | +end |
plugins/open_graph/lib/open_graph_plugin.rb
plugins/open_graph/lib/open_graph_plugin/publisher.rb
1 | 1 | ||
2 | class OpenGraphPlugin::Publisher | 2 | class OpenGraphPlugin::Publisher |
3 | 3 | ||
4 | - attr_accessor :actions | ||
5 | - attr_accessor :objects | ||
6 | - | ||
7 | def self.default | 4 | def self.default |
8 | @default ||= self.new | 5 | @default ||= self.new |
9 | end | 6 | end |
10 | 7 | ||
11 | def initialize attributes = {} | 8 | def initialize attributes = {} |
12 | - # defaults | ||
13 | - self.actions = OpenGraphPlugin::Stories::DefaultActions | ||
14 | - self.objects = OpenGraphPlugin::Stories::DefaultObjects | ||
15 | - | ||
16 | attributes.each do |attr, value| | 9 | attributes.each do |attr, value| |
17 | self.send "#{attr}=", value | 10 | self.send "#{attr}=", value |
18 | end | 11 | end |
19 | end | 12 | end |
20 | 13 | ||
21 | - def publish actor, story_defs, object_data_url | ||
22 | - raise 'abstract method called' | ||
23 | - end | ||
24 | - | ||
25 | def publish_stories object_data, actor, stories | 14 | def publish_stories object_data, actor, stories |
26 | stories.each do |story| | 15 | stories.each do |story| |
27 | begin | 16 | begin |
28 | self.publish_story object_data, actor, story | 17 | self.publish_story object_data, actor, story |
29 | rescue => e | 18 | rescue => e |
19 | + raise unless Rails.env.production? | ||
30 | ExceptionNotifier.notify_exception e | 20 | ExceptionNotifier.notify_exception e |
31 | end | 21 | end |
32 | end | 22 | end |
33 | end | 23 | end |
34 | 24 | ||
35 | - def update_delay | ||
36 | - 1.day | ||
37 | - end | ||
38 | - | ||
39 | - # only publish recent objects to avoid multiple publications | ||
40 | - def recent_publish? actor, object_type, object_data_url | ||
41 | - activity_params = {actor_id: actor.id, object_type: object_type, object_data_url: object_data_url} | ||
42 | - activity = OpenGraphPlugin::Activity.where(activity_params).first | ||
43 | - activity.present? and activity.created_at <= self.update_delay.from_now | ||
44 | - end | ||
45 | - | ||
46 | def publish_story object_data, actor, story | 25 | def publish_story object_data, actor, story |
47 | - OpenGraphPlugin.context = self.context | ||
48 | - defs = OpenGraphPlugin::Stories::Definitions[story] | ||
49 | - passive = defs[:passive] | ||
50 | - | ||
51 | - print_debug "open_graph: publish_story #{story}" if debug? actor | ||
52 | - match_criteria = if (ret = self.call defs[:criteria], object_data, actor).nil? then true else ret end | ||
53 | - return unless match_criteria | ||
54 | - print_debug "open_graph: #{story} match criteria" if debug? actor | ||
55 | - match_condition = if (ret = self.call defs[:publish_if], object_data, actor).nil? then true else ret end | ||
56 | - return unless match_condition | ||
57 | - print_debug "open_graph: #{story} match publish_if" if debug? actor | ||
58 | - | ||
59 | - actors = self.story_trackers defs, actor, object_data | ||
60 | - return if actors.blank? | ||
61 | - print_debug "open_graph: #{story} has enabled trackers" if debug? actor | ||
62 | - | ||
63 | - if publish = defs[:publish] | ||
64 | - begin | ||
65 | - instance_exec actor, object_data, &publish | ||
66 | - rescue => e | ||
67 | - print_debug "open_graph: can't publish story: #{e.message}" if debug? actor | ||
68 | - ExceptionNotifier.notify_exception e | ||
69 | - end | ||
70 | - else | ||
71 | - # force profile identifier for custom domains and fixed host. see og_url_for | ||
72 | - object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil | ||
73 | - extra_params = if object_profile then {profile: object_profile.identifier} else {} end | ||
74 | - | ||
75 | - custom_object_data_url = self.call defs[:object_data_url], object_data, actor | ||
76 | - object_data_url = if passive then self.passive_url_for object_data, custom_object_data_url, defs, extra_params else self.url_for object_data, custom_object_data_url, extra_params end | ||
77 | - | ||
78 | - actors.each do |actor| | ||
79 | - print_debug "open_graph: start publishing" if debug? actor | ||
80 | - begin | ||
81 | - self.publish actor, defs, object_data_url | ||
82 | - rescue => e | ||
83 | - print_debug "open_graph: can't publish story: #{e.message}" if debug? actor | ||
84 | - ExceptionNotifier.notify_exception e | ||
85 | - end | ||
86 | - end | ||
87 | - end | ||
88 | - end | ||
89 | - | ||
90 | - def story_trackers story_defs, actor, object_data | ||
91 | - passive = story_defs[:passive] | ||
92 | - trackers = [] | ||
93 | - | ||
94 | - track_configs = Array(story_defs[:track_config]).compact.map(&:constantize) | ||
95 | - return if track_configs.empty? | ||
96 | - print_debug "open_graph: using configs: #{track_configs.map(&:name).inspect}" if debug? actor | ||
97 | - | ||
98 | - if passive | ||
99 | - object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil | ||
100 | - return unless object_profile | ||
101 | - | ||
102 | - track_configs.each do |c| | ||
103 | - trackers.concat c.trackers_to_profile(object_profile) | ||
104 | - end.flatten | ||
105 | - | ||
106 | - trackers.select! do |t| | ||
107 | - track_configs.any?{ |c| c.enabled? self.context, t } | ||
108 | - end | ||
109 | - else #active | ||
110 | - object_actor = self.call(story_defs[:object_actor], object_data) || object_data.profile rescue nil | ||
111 | - return unless object_actor and object_actor.person? | ||
112 | - custom_actor = self.call(story_defs[:custom_actor], object_data) | ||
113 | - actor = custom_actor if custom_actor | ||
114 | - | ||
115 | - match_track = track_configs.any? do |c| | ||
116 | - c.enabled?(self.context, actor) and | ||
117 | - actor.send("open_graph_#{c.track_name}_track_configs").where(object_type: story_defs[:object_type]).first | ||
118 | - end | ||
119 | - trackers << actor if match_track | ||
120 | - end | ||
121 | - | ||
122 | - trackers | 26 | + OpenGraphPlugin.context = OpenGraphPlugin::Activity.context |
27 | + a = OpenGraphPlugin::Activity.new object_data: object_data, actor: actor, story: story | ||
28 | + a.dispatch_publications | ||
29 | + a.save | ||
123 | end | 30 | end |
124 | 31 | ||
125 | protected | 32 | protected |
126 | 33 | ||
127 | - include MetadataPlugin::UrlHelper | ||
128 | - | ||
129 | - def register_publish attributes | ||
130 | - OpenGraphPlugin::Activity.create! attributes | ||
131 | - end | ||
132 | - | ||
133 | - # Call don't ask: move to a og_url method inside object | ||
134 | - def url_for object, custom_url=nil, extra_params={} | ||
135 | - return custom_url if custom_url.is_a? String | ||
136 | - url = custom_url || if object.is_a? Profile then og_profile_url object else object.url end | ||
137 | - # for profile when custom domain is used | ||
138 | - url.merge! profile: object.profile.identifier if object.respond_to? :profile | ||
139 | - url.merge! extra_params | ||
140 | - self.og_url_for url | ||
141 | - end | ||
142 | - | ||
143 | - def passive_url_for object, custom_url, story_defs, extra_params={} | ||
144 | - object_type = story_defs[:object_type] | ||
145 | - extra_params.merge! og_type: MetadataPlugin.og_types[object_type] | ||
146 | - self.url_for object, custom_url, extra_params | ||
147 | - end | ||
148 | - | ||
149 | - def call p, *args | ||
150 | - p and instance_exec *args, &p | ||
151 | - end | ||
152 | - | ||
153 | - def context | ||
154 | - :open_graph | ||
155 | - end | ||
156 | - | ||
157 | - def print_debug msg | ||
158 | - puts msg | ||
159 | - Delayed::Worker.logger.debug msg | ||
160 | - end | ||
161 | - def debug? actor=nil | ||
162 | - !Rails.env.production? | ||
163 | - end | ||
164 | - | ||
165 | end | 34 | end |
166 | 35 |
plugins/open_graph/lib/open_graph_plugin/stories.rb
@@ -99,13 +99,6 @@ class OpenGraphPlugin::Stories | @@ -99,13 +99,6 @@ class OpenGraphPlugin::Stories | ||
99 | publish_if: proc do |article, actor| | 99 | publish_if: proc do |article, actor| |
100 | article.published? | 100 | article.published? |
101 | end, | 101 | end, |
102 | - object_data_url: proc do |article, actor| | ||
103 | - url = article.url | ||
104 | - if og_type = MetadataPlugin::og_types[:forum] | ||
105 | - url[:og_type] = og_type | ||
106 | - end | ||
107 | - url | ||
108 | - end, | ||
109 | }, | 102 | }, |
110 | 103 | ||
111 | # these a published as passive to give focus to the enterprise | 104 | # these a published as passive to give focus to the enterprise |
@@ -0,0 +1,24 @@ | @@ -0,0 +1,24 @@ | ||
1 | +module OpenGraphPlugin::UrlHelper | ||
2 | + | ||
3 | + protected | ||
4 | + | ||
5 | + include MetadataPlugin::UrlHelper | ||
6 | + | ||
7 | + # Call don't ask: move to a og_url method inside object | ||
8 | + def url_for object, custom_url=nil, extra_params={} | ||
9 | + return custom_url if custom_url.is_a? String | ||
10 | + url = custom_url || if object.is_a? Profile then og_profile_url object else object.url end | ||
11 | + # for profile when custom domain is used | ||
12 | + url.merge! profile: object.profile.identifier if object.respond_to? :profile | ||
13 | + url.merge! extra_params | ||
14 | + self.og_url_for url | ||
15 | + end | ||
16 | + | ||
17 | + def passive_url_for object, custom_url, story_defs, extra_params={} | ||
18 | + object_type = story_defs[:object_type] | ||
19 | + og_type = MetadataPlugin.og_types[object_type] | ||
20 | + extra_params.merge! og_type: og_type if og_type.present? | ||
21 | + self.url_for object, custom_url, extra_params | ||
22 | + end | ||
23 | + | ||
24 | +end |
plugins/open_graph/models/open_graph_plugin/activity.rb
1 | # This is a log of activities, unlike ActivityTrack that is a configuration | 1 | # This is a log of activities, unlike ActivityTrack that is a configuration |
2 | class OpenGraphPlugin::Activity < OpenGraphPlugin::Track | 2 | class OpenGraphPlugin::Activity < OpenGraphPlugin::Track |
3 | 3 | ||
4 | + Defs = OpenGraphPlugin::Stories::Definitions | ||
5 | + | ||
6 | + UpdateDelay = 1.day | ||
7 | + | ||
8 | + class_attribute :actions, :objects | ||
9 | + self.actions = OpenGraphPlugin::Stories::DefaultActions | ||
10 | + self.objects = OpenGraphPlugin::Stories::DefaultObjects | ||
11 | + | ||
12 | + validates_presence_of :action | ||
13 | + validates_presence_of :object_type | ||
14 | + | ||
4 | # subclass this to define (e.g. FbAppPlugin::Activity) | 15 | # subclass this to define (e.g. FbAppPlugin::Activity) |
5 | def scrape | 16 | def scrape |
17 | + raise NotImplementedError | ||
18 | + end | ||
19 | + def publish! actor = self.actor | ||
20 | + self.published_at = Time.now | ||
21 | + print_debug "open_graph: published with success" if debug? actor | ||
22 | + end | ||
23 | + | ||
24 | + def defs | ||
25 | + @defs ||= Defs[self.story.to_sym] | ||
26 | + end | ||
27 | + def object_profile | ||
28 | + @object_profile ||= self.call(self.defs[:object_profile], self.object_data) || self.object_data.profile rescue nil | ||
29 | + end | ||
30 | + def track_configs | ||
31 | + @track_configs ||= Array(self.defs[:track_config]).compact.map(&:constantize) | ||
32 | + end | ||
33 | + def match_criteria? | ||
34 | + if (ret = self.call self.defs[:criteria], self.object_data, self.actor).nil? then true else ret end | ||
35 | + end | ||
36 | + def match_publish_if? | ||
37 | + if (ret = self.call self.defs[:publish_if], self.object_data, self.actor).nil? then true else ret end | ||
38 | + end | ||
39 | + def custom_object_data_url | ||
40 | + @custom_object_data_url ||= self.call defs[:object_data_url], self.object_data, self.actor | ||
41 | + end | ||
42 | + def object_actor | ||
43 | + @object_actor ||= self.call(self.defs[:object_actor], self.object_data) || self.object_data.profile rescue nil | ||
44 | + end | ||
45 | + def custom_actor | ||
46 | + @custom_actor ||= self.call self.defs[:custom_actor], self.object_data | ||
47 | + end | ||
48 | + | ||
49 | + def set_object_data_url | ||
50 | + # force profile identifier for custom domains and fixed host. see og_url_for | ||
51 | + extra_params = if self.object_profile then {profile: self.object_profile.identifier} else {} end | ||
52 | + | ||
53 | + self.object_data_url = if self.defs[:passive] then self.passive_url_for self.object_data, self.custom_object_data_url, self.defs, extra_params else self.url_for self.object_data, self.custom_object_data_url, extra_params end | ||
54 | + end | ||
55 | + | ||
56 | + def dispatch_publications | ||
57 | + print_debug "open_graph: dispatch_publications of #{story}" if debug? self.actor | ||
58 | + | ||
59 | + return unless self.match_criteria? | ||
60 | + print_debug "open_graph: #{story} match criteria" if debug? self.actor | ||
61 | + return unless self.match_publish_if? | ||
62 | + print_debug "open_graph: #{story} match publish_if" if debug? self.actor | ||
63 | + return unless (actors = self.trackers).present? | ||
64 | + print_debug "open_graph: #{story} has enabled trackers" if debug? self.actor | ||
65 | + | ||
66 | + self.set_object_data_url | ||
67 | + self.action = self.class.actions[self.defs[:action]] | ||
68 | + self.object_type = self.class.objects[self.defs[:object_type]] | ||
69 | + | ||
70 | + print_debug "open_graph: start publishing" if debug? actor | ||
71 | + unless (publish = self.defs[:publish]).present? | ||
72 | + actors.each do |actor| | ||
73 | + begin | ||
74 | + self.publish! actor | ||
75 | + rescue => e | ||
76 | + print_debug "open_graph: can't publish story: #{e.message}" if debug? actor | ||
77 | + raise unless Rails.env.production? | ||
78 | + ExceptionNotifier.notify_exception e | ||
79 | + end | ||
80 | + end | ||
81 | + else # custom publish proc | ||
82 | + begin | ||
83 | + instance_exec self.actor, self.object_data, &publish | ||
84 | + rescue => e | ||
85 | + print_debug "open_graph: can't publish story: #{e.message}" if debug? self.actor | ||
86 | + raise unless Rails.env.production? | ||
87 | + ExceptionNotifier.notify_exception e | ||
88 | + end | ||
89 | + end | ||
90 | + end | ||
91 | + | ||
92 | + def trackers | ||
93 | + @trackers ||= begin | ||
94 | + return if self.track_configs.empty? | ||
95 | + trackers = [] | ||
96 | + | ||
97 | + print_debug "open_graph: using configs: #{self.track_configs.map(&:name).inspect}" if debug? self.actor | ||
98 | + | ||
99 | + if self.defs[:passive] | ||
100 | + return unless self.object_profile | ||
101 | + | ||
102 | + self.track_configs.each do |c| | ||
103 | + trackers.concat c.trackers_to_profile(self.object_profile) | ||
104 | + end.flatten | ||
105 | + | ||
106 | + trackers.select! do |t| | ||
107 | + self.track_configs.any?{ |c| c.enabled? self.context, t } | ||
108 | + end | ||
109 | + else #active | ||
110 | + return unless self.object_actor and self.object_actor.person? | ||
111 | + actor = self.custom_actor || self.actor | ||
112 | + | ||
113 | + match_track = self.track_configs.any? do |c| | ||
114 | + c.enabled?(self.context, actor) and | ||
115 | + actor.send("open_graph_#{c.track_name}_track_configs").where(object_type: self.defs[:object_type]).first | ||
116 | + end | ||
117 | + trackers << actor if match_track | ||
118 | + end | ||
119 | + | ||
120 | + trackers | ||
121 | + end | ||
122 | + end | ||
123 | + | ||
124 | + protected | ||
125 | + | ||
126 | + include OpenGraphPlugin::UrlHelper | ||
127 | + | ||
128 | + def update_delay | ||
129 | + UpdateDelay | ||
130 | + end | ||
131 | + | ||
132 | + # only publish recent objects to avoid multiple publications | ||
133 | + def recent_publish? actor, object_type, object_data_url | ||
134 | + activity_params = {actor_id: actor.id, object_type: object_type, object_data_url: object_data_url} | ||
135 | + activity = OpenGraphPlugin::Activity.where(activity_params).first | ||
136 | + activity.present? and activity.created_at <= self.update_delay.from_now | ||
137 | + end | ||
138 | + | ||
139 | + def call p, *args | ||
140 | + p and instance_exec *args, &p | ||
6 | end | 141 | end |
7 | 142 | ||
8 | end | 143 | end |
plugins/open_graph/models/open_graph_plugin/track.rb
1 | class OpenGraphPlugin::Track < ActiveRecord::Base | 1 | class OpenGraphPlugin::Track < ActiveRecord::Base |
2 | 2 | ||
3 | + class_attribute :context | ||
4 | + self.context = :open_graph | ||
5 | + | ||
3 | attr_accessible :type, :context, :tracker_id, :tracker, :actor_id, :action, | 6 | attr_accessible :type, :context, :tracker_id, :tracker, :actor_id, :action, |
4 | - :object_type, :object_data, :object_data_id, :object_data_type, :object_data_url | 7 | + :object_type, :object_data_id, :object_data_type, :object_data_url, |
8 | + :story, :object_data, :actor | ||
5 | 9 | ||
6 | belongs_to :tracker, class_name: 'Profile' | 10 | belongs_to :tracker, class_name: 'Profile' |
7 | belongs_to :actor, class_name: 'Profile' | 11 | belongs_to :actor, class_name: 'Profile' |
8 | belongs_to :object_data, polymorphic: true | 12 | belongs_to :object_data, polymorphic: true |
9 | 13 | ||
10 | - validates_presence_of :context | ||
11 | before_validation :set_context | 14 | before_validation :set_context |
12 | 15 | ||
13 | def self.objects | 16 | def self.objects |
@@ -21,7 +24,15 @@ class OpenGraphPlugin::Track < ActiveRecord::Base | @@ -21,7 +24,15 @@ class OpenGraphPlugin::Track < ActiveRecord::Base | ||
21 | protected | 24 | protected |
22 | 25 | ||
23 | def set_context | 26 | def set_context |
24 | - self.context = OpenGraphPlugin.context | 27 | + self[:context] = self.class.context |
28 | + end | ||
29 | + | ||
30 | + def print_debug msg | ||
31 | + puts msg | ||
32 | + Delayed::Worker.logger.debug msg | ||
33 | + end | ||
34 | + def debug? actor=nil | ||
35 | + OpenGraphPlugin.debug? actor | ||
25 | end | 36 | end |
26 | 37 | ||
27 | end | 38 | end |
plugins/open_graph/plugin.yml
plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb
@@ -2,14 +2,16 @@ require "test_helper" | @@ -2,14 +2,16 @@ require "test_helper" | ||
2 | 2 | ||
3 | class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase | 3 | class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase |
4 | 4 | ||
5 | + include OpenGraphPlugin::UrlHelper | ||
6 | + | ||
5 | def setup | 7 | def setup |
6 | @actor = create_user.person | 8 | @actor = create_user.person |
7 | User.current = @actor.user | 9 | User.current = @actor.user |
8 | - @stories = OpenGraphPlugin::Stories::Definitions | ||
9 | @publisher = OpenGraphPlugin::Publisher.new | 10 | @publisher = OpenGraphPlugin::Publisher.new |
10 | OpenGraphPlugin::Stories.stubs(:publishers).returns([@publisher]) | 11 | OpenGraphPlugin::Stories.stubs(:publishers).returns([@publisher]) |
11 | - @publisher.stubs(:context).returns(:open_graph) | ||
12 | - @publisher.stubs(:og_domain).returns('noosfero.net') | 12 | + # for MetadataPlugin::UrlHelper#og_url_for |
13 | + stubs(:og_domain).returns('noosfero.net') | ||
14 | + OpenGraphPlugin::Activity.any_instance.stubs(:og_domain).returns('noosfero.net') | ||
13 | end | 15 | end |
14 | 16 | ||
15 | should "publish only tracked stuff" do | 17 | should "publish only tracked stuff" do |
@@ -46,66 +48,70 @@ class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase | @@ -46,66 +48,70 @@ class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase | ||
46 | 48 | ||
47 | # active | 49 | # active |
48 | User.current = @actor.user | 50 | User.current = @actor.user |
51 | + user = User.current.person | ||
52 | + | ||
53 | + blog = Blog.create! profile: user, name: 'blog' | ||
54 | + blog_post = TinyMceArticle.create! profile: user, parent: blog, name: 'blah', author: user | ||
55 | + assert_last_activity user, :create_an_article, url_for(blog_post) | ||
56 | + | ||
57 | + gallery = Gallery.create! name: 'gallery', profile: user | ||
58 | + image = UploadedFile.create! uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'), parent: gallery, profile: user | ||
59 | + assert_last_activity user, :add_an_image, url_for(image, image.url.merge(view: true)) | ||
60 | + | ||
61 | + document = UploadedFile.create! uploaded_data: fixture_file_upload('/files/doctest.en.xhtml', 'text/html'), profile: user | ||
62 | + assert_last_activity user, :add_a_document, url_for(document, document.url.merge(view: true)) | ||
63 | + | ||
64 | + event = Event.create! name: 'event', profile: user | ||
65 | + assert_last_activity user, :create_an_event, url_for(event) | ||
66 | + | ||
67 | + forum = Forum.create! name: 'forum', profile: user | ||
68 | + topic = TinyMceArticle.create! profile: user, parent: forum, name: 'blah2', author: user | ||
69 | + assert_last_activity user, :start_a_discussion, url_for(topic, topic.url.merge(og_type: MetadataPlugin.og_types[:forum])) | ||
49 | 70 | ||
50 | - blog = Blog.create! profile: @actor, name: 'blog' | ||
51 | - blog_post = TinyMceArticle.new profile: User.current.person, parent: blog, name: 'blah', author: User.current.person | ||
52 | - @publisher.expects(:publish).with(User.current.person, @stories[:create_an_article], @publisher.send(:url_for, blog_post)) | ||
53 | - blog_post.save! | ||
54 | - | ||
55 | - gallery = Gallery.create! name: 'gallery', profile: User.current.person | ||
56 | - image = UploadedFile.new uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'), parent: gallery, profile: User.current.person | ||
57 | - @publisher.expects(:publish).with(User.current.person, @stories[:add_an_image], @publisher.send(:url_for, image, image.url.merge(view: true))) | ||
58 | - image.save! | ||
59 | - | ||
60 | - document = UploadedFile.new uploaded_data: fixture_file_upload('/files/doctest.en.xhtml', 'text/html'), profile: User.current.person | ||
61 | - @publisher.expects(:publish).with(User.current.person, @stories[:add_a_document], @publisher.send(:url_for, document, document.url.merge(view: true))) | ||
62 | - document.save! | ||
63 | - | ||
64 | - event = Event.new name: 'event', profile: User.current.person | ||
65 | - @publisher.expects(:publish).with(User.current.person, @stories[:create_an_event], @publisher.send(:url_for, event)) | ||
66 | - event.save! | ||
67 | - | ||
68 | - forum = Forum.create! name: 'forum', profile: User.current.person | ||
69 | - topic = TinyMceArticle.new profile: User.current.person, parent: forum, name: 'blah2', author: User.current.person | ||
70 | - @publisher.expects(:publish).with(User.current.person, @stories[:start_a_discussion], @publisher.send(:url_for, topic, topic.url.merge(og_type: MetadataPlugin.og_types[:forum]))) | ||
71 | - topic.save! | ||
72 | - | ||
73 | - @publisher.expects(:publish).with(@actor, @stories[:make_friendship_with], @publisher.send(:url_for, @other_actor)).twice | ||
74 | - @publisher.expects(:publish).with(@other_actor, @stories[:make_friendship_with], @publisher.send(:url_for, @actor)).twice | ||
75 | - AddFriend.create!(person: @actor, friend: @other_actor).finish | ||
76 | - Friendship.remove_friendship @actor, @other_actor | 71 | + AddFriend.create!(person: user, friend: @other_actor).finish |
72 | + #assert_last_activity user, :make_friendship_with, url_for(@other_actor) | ||
73 | + Friendship.remove_friendship user, @other_actor | ||
77 | # friend verb is groupable | 74 | # friend verb is groupable |
78 | - AddFriend.create!(person: @actor, friend: @other_actor).finish | 75 | + AddFriend.create!(person: user, friend: @other_actor).finish |
76 | + #assert_last_activity @other_actor, :make_friendship_with, url_for(user) | ||
79 | 77 | ||
80 | - @publisher.expects(:publish).with(User.current.person, @stories[:favorite_a_sse_initiative], @publisher.send(:url_for, @enterprise)) | ||
81 | - @enterprise.fans << User.current.person | 78 | + @enterprise.fans << user |
79 | + assert_last_activity user, :favorite_a_sse_initiative, url_for(@enterprise) | ||
82 | 80 | ||
83 | # active but published as passive | 81 | # active but published as passive |
84 | User.current = @actor.user | 82 | User.current = @actor.user |
83 | + user = User.current.person | ||
85 | 84 | ||
86 | - blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah', author: User.current.person | ||
87 | - story = @stories[:announce_news_from_a_sse_initiative] | ||
88 | - @publisher.expects(:publish).with(User.current.person, story, @publisher.send(:passive_url_for, blog_post, nil, story)) | ||
89 | - blog_post.save! | 85 | + blog_post = TinyMceArticle.create! profile: @enterprise, parent: @enterprise.blog, name: 'blah', author: user |
86 | + story = :announce_news_from_a_sse_initiative | ||
87 | + assert_last_activity user, story, passive_url_for(blog_post, nil, OpenGraphPlugin::Stories::Definitions[story]) | ||
90 | 88 | ||
91 | # passive | 89 | # passive |
92 | User.current = @other_actor.user | 90 | User.current = @other_actor.user |
91 | + user = User.current.person | ||
93 | 92 | ||
94 | # fan | 93 | # fan |
95 | - blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah2', author: User.current.person | ||
96 | - story = @stories[:announce_news_from_a_sse_initiative] | ||
97 | - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/coop/blog/blah2') | ||
98 | - blog_post.save! | 94 | + blog_post = TinyMceArticle.create! profile: @enterprise, parent: @enterprise.blog, name: 'blah2', author: user |
95 | + assert_last_activity user, :announce_news_from_a_sse_initiative, 'http://noosfero.net/coop/blog/blah2' | ||
99 | # member | 96 | # member |
100 | - blog_post = TinyMceArticle.new profile: @myenterprise, parent: @myenterprise.blog, name: 'blah2', author: User.current.person | ||
101 | - story = @stories[:announce_news_from_a_sse_initiative] | ||
102 | - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/mycoop/blog/blah2') | ||
103 | - blog_post.save! | ||
104 | - | ||
105 | - blog_post = TinyMceArticle.new profile: @community, parent: @community.blog, name: 'blah', author: User.current.person | ||
106 | - story = @stories[:announce_news_from_a_community] | ||
107 | - @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/comm/blog/blah') | ||
108 | - blog_post.save! | 97 | + blog_post = TinyMceArticle.create! profile: @myenterprise, parent: @myenterprise.blog, name: 'blah2', author: user |
98 | + assert_last_activity user, :announce_news_from_a_sse_initiative, 'http://noosfero.net/mycoop/blog/blah2' | ||
99 | + | ||
100 | + blog_post = TinyMceArticle.create! profile: @community, parent: @community.blog, name: 'blah', author: user | ||
101 | + assert_last_activity user, :announce_news_from_a_community, 'http://noosfero.net/comm/blog/blah' | ||
102 | + end | ||
103 | + | ||
104 | + protected | ||
105 | + | ||
106 | + def assert_activity activity, actor, story, object_data_url | ||
107 | + assert_equal actor, activity.actor, actor | ||
108 | + assert_equal story.to_s, activity.story | ||
109 | + assert_equal object_data_url, activity.object_data_url | ||
110 | + end | ||
111 | + | ||
112 | + def assert_last_activity actor, story, object_data_url | ||
113 | + a = OpenGraphPlugin::Activity.order('id DESC').first | ||
114 | + assert_activity a, actor, story, object_data_url | ||
109 | end | 115 | end |
110 | 116 | ||
111 | end | 117 | end |
plugins/site_tour/controllers/public/site_tour_plugin_public_controller.rb
0 → 100644
@@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
1 | +class SiteTourPluginPublicController < PublicController | ||
2 | + | ||
3 | + before_filter :login_required | ||
4 | + | ||
5 | + def mark_action | ||
6 | + user.site_tour_plugin_actions += [params[:action_name]].flatten | ||
7 | + user.site_tour_plugin_actions.uniq! | ||
8 | + render :json => {:ok => user.save} | ||
9 | + end | ||
10 | + | ||
11 | +end |
plugins/site_tour/controllers/site_tour_plugin_admin_controller.rb
0 → 100644
@@ -0,0 +1,50 @@ | @@ -0,0 +1,50 @@ | ||
1 | +require 'csv' | ||
2 | + | ||
3 | +class SiteTourPluginAdminController < PluginAdminController | ||
4 | + | ||
5 | + no_design_blocks | ||
6 | + | ||
7 | + def index | ||
8 | + settings = params[:settings] | ||
9 | + settings ||= {} | ||
10 | + | ||
11 | + @settings = Noosfero::Plugin::Settings.new(environment, SiteTourPlugin, settings) | ||
12 | + @settings.actions_csv = convert_to_csv(@settings.actions) | ||
13 | + @settings.group_triggers_csv = convert_to_csv(@settings.group_triggers) | ||
14 | + | ||
15 | + if request.post? | ||
16 | + @settings.actions = convert_actions_from_csv(settings[:actions_csv]) | ||
17 | + @settings.settings.delete(:actions_csv) | ||
18 | + | ||
19 | + @settings.group_triggers = convert_group_triggers_from_csv(settings[:group_triggers_csv]) | ||
20 | + @settings.settings.delete(:group_triggers_csv) | ||
21 | + | ||
22 | + @settings.save! | ||
23 | + session[:notice] = 'Settings succefully saved.' | ||
24 | + redirect_to :action => 'index' | ||
25 | + end | ||
26 | + end | ||
27 | + | ||
28 | + protected | ||
29 | + | ||
30 | + def convert_to_csv(actions) | ||
31 | + CSV.generate do |csv| | ||
32 | + (actions||[]).each { |action| csv << action.values } | ||
33 | + end | ||
34 | + end | ||
35 | + | ||
36 | + def convert_actions_from_csv(actions_csv) | ||
37 | + return [] if actions_csv.blank? | ||
38 | + CSV.parse(actions_csv).map do |action| | ||
39 | + {:language => action[0], :group_name => action[1], :selector => action[2], :description => action[3]} | ||
40 | + end | ||
41 | + end | ||
42 | + | ||
43 | + def convert_group_triggers_from_csv(group_triggers_csv) | ||
44 | + return [] if group_triggers_csv.blank? | ||
45 | + CSV.parse(group_triggers_csv).map do |group| | ||
46 | + {:group_name => group[0], :selector => group[1], :event => group[2]} | ||
47 | + end | ||
48 | + end | ||
49 | + | ||
50 | +end |
@@ -0,0 +1,44 @@ | @@ -0,0 +1,44 @@ | ||
1 | +class SiteTourPlugin < Noosfero::Plugin | ||
2 | + | ||
3 | + def self.plugin_name | ||
4 | + 'SiteTourPlugin' | ||
5 | + end | ||
6 | + | ||
7 | + def self.plugin_description | ||
8 | + _("A site tour to show users how to use the application.") | ||
9 | + end | ||
10 | + | ||
11 | + def stylesheet? | ||
12 | + true | ||
13 | + end | ||
14 | + | ||
15 | + def js_files | ||
16 | + ['intro.min.js', 'main.js'] | ||
17 | + end | ||
18 | + | ||
19 | + def user_data_extras | ||
20 | + proc do | ||
21 | + logged_in? ? {:site_tour_plugin_actions => user.site_tour_plugin_actions}:{} | ||
22 | + end | ||
23 | + end | ||
24 | + | ||
25 | + def body_ending | ||
26 | + proc do | ||
27 | + tour_file = "/plugins/site_tour/tour/#{language}/tour.js" | ||
28 | + js_file = File.exists?(Rails.root.join("public#{tour_file}").to_s) ? tour_file : "" | ||
29 | + settings = Noosfero::Plugin::Settings.new(environment, SiteTourPlugin) | ||
30 | + actions = (settings.actions||[]).select {|action| action[:language] == language} | ||
31 | + | ||
32 | + render(:file => 'tour_actions', :locals => { :actions => actions, :group_triggers => settings.group_triggers, :js_file => js_file}) | ||
33 | + end | ||
34 | + end | ||
35 | + | ||
36 | + def self.extra_blocks | ||
37 | + { SiteTourPlugin::TourBlock => {} } | ||
38 | + end | ||
39 | + | ||
40 | + def self.actions_csv_default_setting | ||
41 | + 'en,tour_plugin,.site-tour-plugin_tour-block .tour-button,"Click to start tour!"' | ||
42 | + end | ||
43 | + | ||
44 | +end |
plugins/site_tour/lib/site_tour_plugin/site_tour_helper.rb
0 → 100644
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +module SiteTourPlugin::SiteTourHelper | ||
2 | + | ||
3 | + def parse_tour_description(description) | ||
4 | + p = profile rescue nil | ||
5 | + if !p.nil? && description.present? | ||
6 | + description.gsub('{profile.identifier}', p.identifier). | ||
7 | + gsub('{profile.name}', p.name). | ||
8 | + gsub('{profile.url}', url_for(p.url)) | ||
9 | + else | ||
10 | + description | ||
11 | + end | ||
12 | + end | ||
13 | + | ||
14 | +end |
@@ -0,0 +1,29 @@ | @@ -0,0 +1,29 @@ | ||
1 | +class SiteTourPlugin::TourBlock < Block | ||
2 | + | ||
3 | + settings_items :actions, :type => Array, :default => [{:group_name => 'tour_plugin', :selector => '.site-tour-plugin_tour-block .tour-button', :description => _('Click to start tour!')}] | ||
4 | + settings_items :group_triggers, :type => Array, :default => [] | ||
5 | + settings_items :display_button, :type => :boolean, :default => true | ||
6 | + | ||
7 | + attr_accessible :actions, :display_button, :group_triggers | ||
8 | + | ||
9 | + before_save do |block| | ||
10 | + block.actions.reject! {|i| i[:group_name].blank? && i[:selector].blank? && i[:description].blank?} | ||
11 | + block.group_triggers.reject! {|i| i[:group_name].blank? && i[:selector].blank?} | ||
12 | + end | ||
13 | + | ||
14 | + def self.description | ||
15 | + _('Site Tour Block') | ||
16 | + end | ||
17 | + | ||
18 | + def help | ||
19 | + _('Configure a step-by-step tour.') | ||
20 | + end | ||
21 | + | ||
22 | + def content(args={}) | ||
23 | + block = self | ||
24 | + proc do | ||
25 | + render :file => 'blocks/tour', :locals => {:block => block} | ||
26 | + end | ||
27 | + end | ||
28 | + | ||
29 | +end |
@@ -0,0 +1,119 @@ | @@ -0,0 +1,119 @@ | ||
1 | +msgid "" | ||
2 | +msgstr "" | ||
3 | +"Project-Id-Version: 1.2~rc1-2843-g999a037\n" | ||
4 | +"POT-Creation-Date: 2015-08-06 08:53-0300\n" | ||
5 | +"PO-Revision-Date: 2015-02-03 17:27-0300\n" | ||
6 | +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||
7 | +"Language-Team: LANGUAGE <LL@li.org>\n" | ||
8 | +"Language: \n" | ||
9 | +"MIME-Version: 1.0\n" | ||
10 | +"Content-Type: text/plain; charset=UTF-8\n" | ||
11 | +"Content-Transfer-Encoding: 8bit\n" | ||
12 | +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | ||
13 | + | ||
14 | +#: plugins/site_tour/lib/site_tour_plugin.rb:8 | ||
15 | +msgid "A site tour to show users how to use the application." | ||
16 | +msgstr "Um plugin para apresentar aos usuários um tour da aplicação" | ||
17 | + | ||
18 | +#: plugins/site_tour/lib/site_tour_plugin/tour_block.rb:3 | ||
19 | +msgid "Click to start tour!" | ||
20 | +msgstr "Clique para iniciar o tour!" | ||
21 | + | ||
22 | +#: plugins/site_tour/lib/site_tour_plugin/tour_block.rb:15 | ||
23 | +msgid "Site Tour Block" | ||
24 | +msgstr "Bloco para Site Tour" | ||
25 | + | ||
26 | +#: plugins/site_tour/lib/site_tour_plugin/tour_block.rb:19 | ||
27 | +msgid "Configure a step-by-step tour." | ||
28 | +msgstr "Configure o passo a passo do tour." | ||
29 | + | ||
30 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block_item.html.erb:13 | ||
31 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block_group_item.html.erb:15 | ||
32 | +msgid "Delete" | ||
33 | +msgstr "Apagar" | ||
34 | + | ||
35 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:4 | ||
36 | +msgid "Display help button" | ||
37 | +msgstr "Mostrar o botão de ajuda" | ||
38 | + | ||
39 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:8 | ||
40 | +msgid "Tooltip Actions" | ||
41 | +msgstr "Ações do Tooltip" | ||
42 | + | ||
43 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:9 | ||
44 | +msgid "" | ||
45 | +"Special fields for description: {profile.name}, {profile.identifier}, " | ||
46 | +"{profile.url}." | ||
47 | +msgstr "" | ||
48 | +"Campos especiais para a descrição: {profile.name}, {profile.identifier}, " | ||
49 | +"{profile.url}" | ||
50 | + | ||
51 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:11 | ||
52 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:29 | ||
53 | +msgid "Group Name" | ||
54 | +msgstr "Nome do Grupo" | ||
55 | + | ||
56 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:12 | ||
57 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:30 | ||
58 | +msgid "Selector" | ||
59 | +msgstr "Seletor" | ||
60 | + | ||
61 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:13 | ||
62 | +msgid "Description" | ||
63 | +msgstr "Descrição" | ||
64 | + | ||
65 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:23 | ||
66 | +msgid "New Tooltip" | ||
67 | +msgstr "Novo Tooltip" | ||
68 | + | ||
69 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:27 | ||
70 | +msgid "Group Triggers" | ||
71 | +msgstr "Gatilhos dos Grupos" | ||
72 | + | ||
73 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:31 | ||
74 | +msgid "Event" | ||
75 | +msgstr "Evento" | ||
76 | + | ||
77 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:41 | ||
78 | +msgid "New Group Trigger" | ||
79 | +msgstr "Novo Gatilho de Grupo" | ||
80 | + | ||
81 | +#: plugins/site_tour/views/blocks/tour.html.erb:4 | ||
82 | +msgid "Help" | ||
83 | +msgstr "Ajuda" | ||
84 | + | ||
85 | +#: plugins/site_tour/views/tour_actions.html.erb:16 | ||
86 | +msgid "Next" | ||
87 | +msgstr "Próximo" | ||
88 | + | ||
89 | +#: plugins/site_tour/views/tour_actions.html.erb:17 | ||
90 | +msgid "Back" | ||
91 | +msgstr "Anterior" | ||
92 | + | ||
93 | +#: plugins/site_tour/views/tour_actions.html.erb:18 | ||
94 | +msgid "Skip" | ||
95 | +msgstr "Pular" | ||
96 | + | ||
97 | +#: plugins/site_tour/views/tour_actions.html.erb:19 | ||
98 | +msgid "Finish" | ||
99 | +msgstr "Fim" | ||
100 | + | ||
101 | +#: plugins/site_tour/views/site_tour_plugin_admin/index.html.erb:1 | ||
102 | +msgid "Site Tour Settings" | ||
103 | +msgstr "Configurações do Site Tour" | ||
104 | + | ||
105 | +#: plugins/site_tour/views/site_tour_plugin_admin/index.html.erb:5 | ||
106 | +msgid "Tooltips (CSV format: language, group name, selector, description)" | ||
107 | +msgstr "Tooltips (Formato CSV: idioma, nome do grupo, seletor, descrição)" | ||
108 | + | ||
109 | +#: plugins/site_tour/views/site_tour_plugin_admin/index.html.erb:6 | ||
110 | +msgid "" | ||
111 | +"Group Triggers (CSV format: group name, selector, event (e.g. mouseenter, " | ||
112 | +"click))" | ||
113 | +msgstr "" | ||
114 | +"Gatilhos dos grupos (Formato CSV: nome do grupo, seletor, evento (e.g. " | ||
115 | +"mouseenter, click))" | ||
116 | + | ||
117 | +#: plugins/site_tour/views/site_tour_plugin_admin/index.html.erb:9 | ||
118 | +msgid "Save" | ||
119 | +msgstr "Salvar" |
@@ -0,0 +1,121 @@ | @@ -0,0 +1,121 @@ | ||
1 | +# SOME DESCRIPTIVE TITLE. | ||
2 | +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER | ||
3 | +# This file is distributed under the same license as the PACKAGE package. | ||
4 | +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | ||
5 | +# | ||
6 | +#, fuzzy | ||
7 | +msgid "" | ||
8 | +msgstr "" | ||
9 | +"Project-Id-Version: 1.2~rc1-2843-g999a037\n" | ||
10 | +"POT-Creation-Date: 2015-08-06 08:53-0300\n" | ||
11 | +"PO-Revision-Date: 2015-02-03 17:27-0300\n" | ||
12 | +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||
13 | +"Language-Team: LANGUAGE <LL@li.org>\n" | ||
14 | +"Language: \n" | ||
15 | +"MIME-Version: 1.0\n" | ||
16 | +"Content-Type: text/plain; charset=UTF-8\n" | ||
17 | +"Content-Transfer-Encoding: 8bit\n" | ||
18 | +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | ||
19 | + | ||
20 | +#: plugins/site_tour/lib/site_tour_plugin.rb:8 | ||
21 | +msgid "A site tour to show users how to use the application." | ||
22 | +msgstr "" | ||
23 | + | ||
24 | +#: plugins/site_tour/lib/site_tour_plugin/tour_block.rb:3 | ||
25 | +msgid "Click to start tour!" | ||
26 | +msgstr "" | ||
27 | + | ||
28 | +#: plugins/site_tour/lib/site_tour_plugin/tour_block.rb:15 | ||
29 | +msgid "Site Tour Block" | ||
30 | +msgstr "" | ||
31 | + | ||
32 | +#: plugins/site_tour/lib/site_tour_plugin/tour_block.rb:19 | ||
33 | +msgid "Configure a step-by-step tour." | ||
34 | +msgstr "" | ||
35 | + | ||
36 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block_item.html.erb:13 | ||
37 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block_group_item.html.erb:15 | ||
38 | +msgid "Delete" | ||
39 | +msgstr "" | ||
40 | + | ||
41 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:4 | ||
42 | +msgid "Display help button" | ||
43 | +msgstr "" | ||
44 | + | ||
45 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:8 | ||
46 | +msgid "Tooltip Actions" | ||
47 | +msgstr "" | ||
48 | + | ||
49 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:9 | ||
50 | +msgid "" | ||
51 | +"Special fields for description: {profile.name}, {profile.identifier}, " | ||
52 | +"{profile.url}." | ||
53 | +msgstr "" | ||
54 | + | ||
55 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:11 | ||
56 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:29 | ||
57 | +msgid "Group Name" | ||
58 | +msgstr "" | ||
59 | + | ||
60 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:12 | ||
61 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:30 | ||
62 | +msgid "Selector" | ||
63 | +msgstr "" | ||
64 | + | ||
65 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:13 | ||
66 | +msgid "Description" | ||
67 | +msgstr "" | ||
68 | + | ||
69 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:23 | ||
70 | +msgid "New Tooltip" | ||
71 | +msgstr "" | ||
72 | + | ||
73 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:27 | ||
74 | +msgid "Group Triggers" | ||
75 | +msgstr "" | ||
76 | + | ||
77 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:31 | ||
78 | +msgid "Event" | ||
79 | +msgstr "" | ||
80 | + | ||
81 | +#: plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb:41 | ||
82 | +msgid "New Group Trigger" | ||
83 | +msgstr "" | ||
84 | + | ||
85 | +#: plugins/site_tour/views/blocks/tour.html.erb:4 | ||
86 | +msgid "Help" | ||
87 | +msgstr "" | ||
88 | + | ||
89 | +#: plugins/site_tour/views/tour_actions.html.erb:16 | ||
90 | +msgid "Next" | ||
91 | +msgstr "" | ||
92 | + | ||
93 | +#: plugins/site_tour/views/tour_actions.html.erb:17 | ||
94 | +msgid "Back" | ||
95 | +msgstr "" | ||
96 | + | ||
97 | +#: plugins/site_tour/views/tour_actions.html.erb:18 | ||
98 | +msgid "Skip" | ||
99 | +msgstr "" | ||
100 | + | ||
101 | +#: plugins/site_tour/views/tour_actions.html.erb:19 | ||
102 | +msgid "Finish" | ||
103 | +msgstr "" | ||
104 | + | ||
105 | +#: plugins/site_tour/views/site_tour_plugin_admin/index.html.erb:1 | ||
106 | +msgid "Site Tour Settings" | ||
107 | +msgstr "" | ||
108 | + | ||
109 | +#: plugins/site_tour/views/site_tour_plugin_admin/index.html.erb:5 | ||
110 | +msgid "Tooltips (CSV format: language, group name, selector, description)" | ||
111 | +msgstr "" | ||
112 | + | ||
113 | +#: plugins/site_tour/views/site_tour_plugin_admin/index.html.erb:6 | ||
114 | +msgid "" | ||
115 | +"Group Triggers (CSV format: group name, selector, event (e.g. mouseenter, " | ||
116 | +"click))" | ||
117 | +msgstr "" | ||
118 | + | ||
119 | +#: plugins/site_tour/views/site_tour_plugin_admin/index.html.erb:9 | ||
120 | +msgid "Save" | ||
121 | +msgstr "" |
@@ -0,0 +1,73 @@ | @@ -0,0 +1,73 @@ | ||
1 | +#edit-tour-block #tooltip-actions h3 { | ||
2 | + margin-bottom: 5px; | ||
3 | +} | ||
4 | + | ||
5 | +#edit-tour-block .special-attributes { | ||
6 | + color: rgb(157, 157, 157); | ||
7 | +} | ||
8 | + | ||
9 | +#edit-tour-block .list-items { | ||
10 | + margin-bottom: 25px; | ||
11 | +} | ||
12 | + | ||
13 | +#edit-tour-block .droppable-items { | ||
14 | + padding-left: 0; | ||
15 | + margin-top: -12px; | ||
16 | +} | ||
17 | + | ||
18 | +#edit-tour-block .droppable-items li { | ||
19 | + list-style-type: none; | ||
20 | +} | ||
21 | + | ||
22 | +#edit-tour-block .item-row { | ||
23 | + line-height: 25px; | ||
24 | + margin-bottom: 5px; | ||
25 | + padding: 0; | ||
26 | + cursor: pointer; | ||
27 | + width: 97%; | ||
28 | +} | ||
29 | + | ||
30 | +#edit-tour-block .item-row:hover { | ||
31 | + background: #ddd url(/images/drag-and-drop.png) no-repeat; | ||
32 | + background-position: 98% 15px; | ||
33 | +} | ||
34 | + | ||
35 | +#edit-tour-block .item-row li { | ||
36 | + list-style-type: none; | ||
37 | + display: inline; | ||
38 | + margin-left: 5px; | ||
39 | +} | ||
40 | + | ||
41 | +#edit-tour-block { | ||
42 | + width: 620px; | ||
43 | + position: relative; | ||
44 | +} | ||
45 | + | ||
46 | +#edit-tour-block #new-template { | ||
47 | + display: none; | ||
48 | +} | ||
49 | + | ||
50 | +#edit-tour-block .list-header { | ||
51 | + width: 98%; | ||
52 | + padding: 0 1px 10px 10px; | ||
53 | + margin-bottom: 5px; | ||
54 | + cursor: pointer; | ||
55 | +} | ||
56 | + | ||
57 | +#edit-tour-block .list-header li { | ||
58 | + list-style-type: none; | ||
59 | + display: inline; | ||
60 | + font-weight: bold; | ||
61 | + font-size: 12px; | ||
62 | + text-align: center; | ||
63 | +} | ||
64 | + | ||
65 | +#edit-tour-block .list-header .list-name { | ||
66 | + margin-left: 20px; | ||
67 | +} | ||
68 | +#edit-tour-block .list-header .list-selector { | ||
69 | + margin-left: 63px; | ||
70 | +} | ||
71 | +#edit-tour-block .list-header .list-description, #edit-tour-block .list-header .list-event { | ||
72 | + margin-left: 68px; | ||
73 | +} |
@@ -0,0 +1,18 @@ | @@ -0,0 +1,18 @@ | ||
1 | +jQuery(document).ready(function(){ | ||
2 | + jQuery('#edit-tour-block').on('click', '.add-item', function() { | ||
3 | + var container = jQuery(this).closest('.list-items'); | ||
4 | + var new_action = container.find('#new-template>li').clone(); | ||
5 | + new_action.show(); | ||
6 | + container.find('.droppable-items').append(new_action); | ||
7 | + }); | ||
8 | + | ||
9 | + jQuery('#edit-tour-block').on('click', '.delete-tour-block-item', function() { | ||
10 | + jQuery(this).parent().parent().remove(); | ||
11 | + return false; | ||
12 | + }); | ||
13 | + | ||
14 | + jQuery("#edit-tour-block .droppable-items").sortable({ | ||
15 | + revert: true, | ||
16 | + axis: "y" | ||
17 | + }); | ||
18 | +}); |
@@ -0,0 +1,33 @@ | @@ -0,0 +1,33 @@ | ||
1 | +(function(w,p){"object"===typeof exports?p(exports):"function"===typeof define&&define.amd?define(["exports"],p):p(w)})(this,function(w){function p(a){this._targetElement=a;this._options={nextLabel:"Next →",prevLabel:"← Back",skipLabel:"Skip",doneLabel:"Done",tooltipPosition:"bottom",tooltipClass:"",highlightClass:"",exitOnEsc:!0,exitOnOverlayClick:!0,showStepNumbers:!0,keyboardNavigation:!0,showButtons:!0,showBullets:!0,showProgress:!1,scrollToElement:!0,overlayOpacity:0.8,positionPrecedence:["bottom", | ||
2 | +"top","right","left"],disableInteraction:!1}}function J(a){var b=[],c=this;if(this._options.steps)for(var d=[],e=0,d=this._options.steps.length;e<d;e++){var f=A(this._options.steps[e]);f.step=b.length+1;"string"===typeof f.element&&(f.element=document.querySelector(f.element));if("undefined"===typeof f.element||null==f.element){var h=document.querySelector(".introjsFloatingElement");null==h&&(h=document.createElement("div"),h.className="introjsFloatingElement",document.body.appendChild(h));f.element= | ||
3 | +h;f.position="floating"}null!=f.element&&b.push(f)}else{d=a.querySelectorAll("*[data-intro]");if(1>d.length)return!1;e=0;for(f=d.length;e<f;e++){var h=d[e],k=parseInt(h.getAttribute("data-step"),10);0<k&&(b[k-1]={element:h,intro:h.getAttribute("data-intro"),step:parseInt(h.getAttribute("data-step"),10),tooltipClass:h.getAttribute("data-tooltipClass"),highlightClass:h.getAttribute("data-highlightClass"),position:h.getAttribute("data-position")||this._options.tooltipPosition})}e=k=0;for(f=d.length;e< | ||
4 | +f;e++)if(h=d[e],null==h.getAttribute("data-step")){for(;"undefined"!=typeof b[k];)k++;b[k]={element:h,intro:h.getAttribute("data-intro"),step:k+1,tooltipClass:h.getAttribute("data-tooltipClass"),highlightClass:h.getAttribute("data-highlightClass"),position:h.getAttribute("data-position")||this._options.tooltipPosition}}}e=[];for(d=0;d<b.length;d++)b[d]&&e.push(b[d]);b=e;b.sort(function(a,b){return a.step-b.step});c._introItems=b;K.call(c,a)&&(x.call(c),a.querySelector(".introjs-skipbutton"),a.querySelector(".introjs-nextbutton"), | ||
5 | +c._onKeyDown=function(b){if(27===b.keyCode&&!0==c._options.exitOnEsc)y.call(c,a),void 0!=c._introExitCallback&&c._introExitCallback.call(c);else if(37===b.keyCode)C.call(c);else if(39===b.keyCode)x.call(c);else if(13===b.keyCode){var d=b.target||b.srcElement;d&&0<d.className.indexOf("introjs-prevbutton")?C.call(c):d&&0<d.className.indexOf("introjs-skipbutton")?y.call(c,a):x.call(c);b.preventDefault?b.preventDefault():b.returnValue=!1}},c._onResize=function(a){t.call(c,document.querySelector(".introjs-helperLayer")); | ||
6 | +t.call(c,document.querySelector(".introjs-tooltipReferenceLayer"))},window.addEventListener?(this._options.keyboardNavigation&&window.addEventListener("keydown",c._onKeyDown,!0),window.addEventListener("resize",c._onResize,!0)):document.attachEvent&&(this._options.keyboardNavigation&&document.attachEvent("onkeydown",c._onKeyDown),document.attachEvent("onresize",c._onResize)));return!1}function A(a){if(null==a||"object"!=typeof a||"undefined"!=typeof a.nodeType)return a;var b={},c;for(c in a)b[c]= | ||
7 | +A(a[c]);return b}function x(){this._direction="forward";"undefined"===typeof this._currentStep?this._currentStep=0:++this._currentStep;if(this._introItems.length<=this._currentStep)"function"===typeof this._introCompleteCallback&&this._introCompleteCallback.call(this),y.call(this,this._targetElement);else{var a=this._introItems[this._currentStep];"undefined"!==typeof this._introBeforeChangeCallback&&this._introBeforeChangeCallback.call(this,a.element);G.call(this,a)}}function C(){this._direction= | ||
8 | +"backward";if(0===this._currentStep)return!1;var a=this._introItems[--this._currentStep];"undefined"!==typeof this._introBeforeChangeCallback&&this._introBeforeChangeCallback.call(this,a.element);G.call(this,a)}function y(a){var b=a.querySelector(".introjs-overlay");if(null!=b){b.style.opacity=0;setTimeout(function(){b.parentNode&&b.parentNode.removeChild(b)},500);var c=a.querySelector(".introjs-helperLayer");c&&c.parentNode.removeChild(c);(c=a.querySelector(".introjs-tooltipReferenceLayer"))&&c.parentNode.removeChild(c); | ||
9 | +(a=a.querySelector(".introjs-disableInteraction"))&&a.parentNode.removeChild(a);(a=document.querySelector(".introjsFloatingElement"))&&a.parentNode.removeChild(a);if(a=document.querySelector(".introjs-showElement"))a.className=a.className.replace(/introjs-[a-zA-Z]+/g,"").replace(/^\s+|\s+$/g,"");if((a=document.querySelectorAll(".introjs-fixParent"))&&0<a.length)for(c=a.length-1;0<=c;c--)a[c].className=a[c].className.replace(/introjs-fixParent/g,"").replace(/^\s+|\s+$/g,"");window.removeEventListener? | ||
10 | +window.removeEventListener("keydown",this._onKeyDown,!0):document.detachEvent&&document.detachEvent("onkeydown",this._onKeyDown);this._currentStep=void 0}}function H(a,b,c,d){var e="";b.style.top=null;b.style.right=null;b.style.bottom=null;b.style.left=null;b.style.marginLeft=null;b.style.marginTop=null;c.style.display="inherit";"undefined"!=typeof d&&null!=d&&(d.style.top=null,d.style.left=null);if(this._introItems[this._currentStep]){e=this._introItems[this._currentStep];e="string"===typeof e.tooltipClass? | ||
11 | +e.tooltipClass:this._options.tooltipClass;b.className=("introjs-tooltip "+e).replace(/^\s+|\s+$/g,"");currentTooltipPosition=this._introItems[this._currentStep].position;if(("auto"==currentTooltipPosition||"auto"==this._options.tooltipPosition)&&"floating"!=currentTooltipPosition){var e=currentTooltipPosition,f=this._options.positionPrecedence.slice(),h=F(),p=k(b).height+10,s=k(b).width+20,l=k(a),m="floating";l.left+s>h.width||0>l.left+l.width/2-s?(q(f,"bottom"),q(f,"top")):(l.height+l.top+p>h.height&& | ||
12 | +q(f,"bottom"),0>l.top-p&&q(f,"top"));l.width+l.left+s>h.width&&q(f,"right");0>l.left-s&&q(f,"left");0<f.length&&(m=f[0]);e&&"auto"!=e&&-1<f.indexOf(e)&&(m=e);currentTooltipPosition=m}e=k(a);f=k(b).height;h=F();switch(currentTooltipPosition){case "top":b.style.left="15px";b.style.top="-"+(f+10)+"px";c.className="introjs-arrow bottom";break;case "right":b.style.left=k(a).width+20+"px";e.top+f>h.height&&(c.className="introjs-arrow left-bottom",b.style.top="-"+(f-e.height-20)+"px");c.className="introjs-arrow left"; | ||
13 | +break;case "left":!0==this._options.showStepNumbers&&(b.style.top="15px");e.top+f>h.height?(b.style.top="-"+(f-e.height-20)+"px",c.className="introjs-arrow right-bottom"):c.className="introjs-arrow right";b.style.right=e.width+20+"px";break;case "floating":c.style.display="none";a=k(b);b.style.left="50%";b.style.top="50%";b.style.marginLeft="-"+a.width/2+"px";b.style.marginTop="-"+a.height/2+"px";"undefined"!=typeof d&&null!=d&&(d.style.left="-"+(a.width/2+18)+"px",d.style.top="-"+(a.height/2+18)+ | ||
14 | +"px");break;case "bottom-right-aligned":c.className="introjs-arrow top-right";b.style.right="0px";b.style.bottom="-"+(k(b).height+10)+"px";break;case "bottom-middle-aligned":d=k(a);a=k(b);c.className="introjs-arrow top-middle";b.style.left=d.width/2-a.width/2+"px";b.style.bottom="-"+(a.height+10)+"px";break;default:b.style.bottom="-"+(k(b).height+10)+"px",b.style.left=k(a).width/2-k(b).width/2+"px",c.className="introjs-arrow top"}}}function q(a,b){-1<a.indexOf(b)&&a.splice(a.indexOf(b),1)}function t(a){if(a&& | ||
15 | +this._introItems[this._currentStep]){var b=this._introItems[this._currentStep],c=k(b.element),d=10;"floating"==b.position&&(d=0);a.setAttribute("style","width: "+(c.width+d)+"px; height:"+(c.height+d)+"px; top:"+(c.top-5)+"px;left: "+(c.left-5)+"px;")}}function L(){var a=document.querySelector(".introjs-disableInteraction");null===a&&(a=document.createElement("div"),a.className="introjs-disableInteraction",this._targetElement.appendChild(a));t.call(this,a)}function G(a){"undefined"!==typeof this._introChangeCallback&& | ||
16 | +this._introChangeCallback.call(this,a.element);var b=this,c=document.querySelector(".introjs-helperLayer"),d=document.querySelector(".introjs-tooltipReferenceLayer"),e="introjs-helperLayer";k(a.element);"string"===typeof a.highlightClass&&(e+=" "+a.highlightClass);"string"===typeof this._options.highlightClass&&(e+=" "+this._options.highlightClass);if(null!=c){var f=d.querySelector(".introjs-helperNumberLayer"),h=d.querySelector(".introjs-tooltiptext"),p=d.querySelector(".introjs-arrow"),s=d.querySelector(".introjs-tooltip"), | ||
17 | +l=d.querySelector(".introjs-skipbutton"),m=d.querySelector(".introjs-prevbutton"),r=d.querySelector(".introjs-nextbutton");c.className=e;s.style.opacity=0;s.style.display="none";if(null!=f){var g=this._introItems[0<=a.step-2?a.step-2:0];if(null!=g&&"forward"==this._direction&&"floating"==g.position||"backward"==this._direction&&"floating"==a.position)f.style.opacity=0}t.call(b,c);t.call(b,d);if((g=document.querySelectorAll(".introjs-fixParent"))&&0<g.length)for(e=g.length-1;0<=e;e--)g[e].className= | ||
18 | +g[e].className.replace(/introjs-fixParent/g,"").replace(/^\s+|\s+$/g,"");g=document.querySelector(".introjs-showElement");g.className=g.className.replace(/introjs-[a-zA-Z]+/g,"").replace(/^\s+|\s+$/g,"");b._lastShowElementTimer&&clearTimeout(b._lastShowElementTimer);b._lastShowElementTimer=setTimeout(function(){null!=f&&(f.innerHTML=a.step);h.innerHTML=a.intro;s.style.display="block";H.call(b,a.element,s,p,f);d.querySelector(".introjs-bullets li > a.active").className="";d.querySelector('.introjs-bullets li > a[data-stepnumber="'+ | ||
19 | +a.step+'"]').className="active";d.querySelector(".introjs-progress .introjs-progressbar").setAttribute("style","width:"+I.call(b)+"%;");s.style.opacity=1;f&&(f.style.opacity=1);-1===r.tabIndex?l.focus():r.focus()},350)}else{var q=document.createElement("div"),m=document.createElement("div"),c=document.createElement("div"),n=document.createElement("div"),w=document.createElement("div"),D=document.createElement("div"),E=document.createElement("div"),u=document.createElement("div");q.className=e;m.className= | ||
20 | +"introjs-tooltipReferenceLayer";t.call(b,q);t.call(b,m);this._targetElement.appendChild(q);this._targetElement.appendChild(m);c.className="introjs-arrow";w.className="introjs-tooltiptext";w.innerHTML=a.intro;D.className="introjs-bullets";!1===this._options.showBullets&&(D.style.display="none");for(var q=document.createElement("ul"),e=0,B=this._introItems.length;e<B;e++){var A=document.createElement("li"),z=document.createElement("a");z.onclick=function(){b.goToStep(this.getAttribute("data-stepnumber"))}; | ||
21 | +e===a.step-1&&(z.className="active");z.href="javascript:void(0);";z.innerHTML=" ";z.setAttribute("data-stepnumber",this._introItems[e].step);A.appendChild(z);q.appendChild(A)}D.appendChild(q);E.className="introjs-progress";!1===this._options.showProgress&&(E.style.display="none");e=document.createElement("div");e.className="introjs-progressbar";e.setAttribute("style","width:"+I.call(this)+"%;");E.appendChild(e);u.className="introjs-tooltipbuttons";!1===this._options.showButtons&&(u.style.display= | ||
22 | +"none");n.className="introjs-tooltip";n.appendChild(w);n.appendChild(D);n.appendChild(E);!0==this._options.showStepNumbers&&(g=document.createElement("span"),g.className="introjs-helperNumberLayer",g.innerHTML=a.step,m.appendChild(g));n.appendChild(c);m.appendChild(n);r=document.createElement("a");r.onclick=function(){b._introItems.length-1!=b._currentStep&&x.call(b)};r.href="javascript:void(0);";r.innerHTML=this._options.nextLabel;m=document.createElement("a");m.onclick=function(){0!=b._currentStep&& | ||
23 | +C.call(b)};m.href="javascript:void(0);";m.innerHTML=this._options.prevLabel;l=document.createElement("a");l.className="introjs-button introjs-skipbutton";l.href="javascript:void(0);";l.innerHTML=this._options.skipLabel;l.onclick=function(){b._introItems.length-1==b._currentStep&&"function"===typeof b._introCompleteCallback&&b._introCompleteCallback.call(b);b._introItems.length-1!=b._currentStep&&"function"===typeof b._introExitCallback&&b._introExitCallback.call(b);y.call(b,b._targetElement)};u.appendChild(l); | ||
24 | +1<this._introItems.length&&(u.appendChild(m),u.appendChild(r));n.appendChild(u);H.call(b,a.element,n,c,g)}!0===this._options.disableInteraction&&L.call(b);m.removeAttribute("tabIndex");r.removeAttribute("tabIndex");0==this._currentStep&&1<this._introItems.length?(m.className="introjs-button introjs-prevbutton introjs-disabled",m.tabIndex="-1",r.className="introjs-button introjs-nextbutton",l.innerHTML=this._options.skipLabel):this._introItems.length-1==this._currentStep||1==this._introItems.length? | ||
25 | +(l.innerHTML=this._options.doneLabel,m.className="introjs-button introjs-prevbutton",r.className="introjs-button introjs-nextbutton introjs-disabled",r.tabIndex="-1"):(m.className="introjs-button introjs-prevbutton",r.className="introjs-button introjs-nextbutton",l.innerHTML=this._options.skipLabel);r.focus();a.element.className+=" introjs-showElement";g=v(a.element,"position");"absolute"!==g&&"relative"!==g&&(a.element.className+=" introjs-relativePosition");for(g=a.element.parentNode;null!=g&&"body"!== | ||
26 | +g.tagName.toLowerCase();){c=v(g,"z-index");n=parseFloat(v(g,"opacity"));u=v(g,"transform")||v(g,"-webkit-transform")||v(g,"-moz-transform")||v(g,"-ms-transform")||v(g,"-o-transform");if(/[0-9]+/.test(c)||1>n||"none"!==u)g.className+=" introjs-fixParent";g=g.parentNode}M(a.element)||!0!==this._options.scrollToElement||(n=a.element.getBoundingClientRect(),g=F().height,c=n.bottom-(n.bottom-n.top),n=n.bottom-g,0>c||a.element.clientHeight>g?window.scrollBy(0,c-30):window.scrollBy(0,n+100));"undefined"!== | ||
27 | +typeof this._introAfterChangeCallback&&this._introAfterChangeCallback.call(this,a.element)}function v(a,b){var c="";a.currentStyle?c=a.currentStyle[b]:document.defaultView&&document.defaultView.getComputedStyle&&(c=document.defaultView.getComputedStyle(a,null).getPropertyValue(b));return c&&c.toLowerCase?c.toLowerCase():c}function F(){if(void 0!=window.innerWidth)return{width:window.innerWidth,height:window.innerHeight};var a=document.documentElement;return{width:a.clientWidth,height:a.clientHeight}} | ||
28 | +function M(a){a=a.getBoundingClientRect();return 0<=a.top&&0<=a.left&&a.bottom+80<=window.innerHeight&&a.right<=window.innerWidth}function K(a){var b=document.createElement("div"),c="",d=this;b.className="introjs-overlay";if("body"===a.tagName.toLowerCase())c+="top: 0;bottom: 0; left: 0;right: 0;position: fixed;",b.setAttribute("style",c);else{var e=k(a);e&&(c+="width: "+e.width+"px; height:"+e.height+"px; top:"+e.top+"px;left: "+e.left+"px;",b.setAttribute("style",c))}a.appendChild(b);b.onclick= | ||
29 | +function(){!0==d._options.exitOnOverlayClick&&(y.call(d,a),void 0!=d._introExitCallback&&d._introExitCallback.call(d))};setTimeout(function(){c+="opacity: "+d._options.overlayOpacity.toString()+";";b.setAttribute("style",c)},10);return!0}function k(a){var b={};b.width=a.offsetWidth;b.height=a.offsetHeight;for(var c=0,d=0;a&&!isNaN(a.offsetLeft)&&!isNaN(a.offsetTop);)c+=a.offsetLeft,d+=a.offsetTop,a=a.offsetParent;b.top=d;b.left=c;return b}function I(){return 100*(parseInt(this._currentStep+1,10)/ | ||
30 | +this._introItems.length)}var B=function(a){if("object"===typeof a)return new p(a);if("string"===typeof a){if(a=document.querySelector(a))return new p(a);throw Error("There is no element with given selector.");}return new p(document.body)};B.version="1.0.0";B.fn=p.prototype={clone:function(){return new p(this)},setOption:function(a,b){this._options[a]=b;return this},setOptions:function(a){var b=this._options,c={},d;for(d in b)c[d]=b[d];for(d in a)c[d]=a[d];this._options=c;return this},start:function(){J.call(this, | ||
31 | +this._targetElement);return this},goToStep:function(a){this._currentStep=a-2;"undefined"!==typeof this._introItems&&x.call(this);return this},nextStep:function(){x.call(this);return this},previousStep:function(){C.call(this);return this},exit:function(){y.call(this,this._targetElement);return this},refresh:function(){t.call(this,document.querySelector(".introjs-helperLayer"));t.call(this,document.querySelector(".introjs-tooltipReferenceLayer"));return this},onbeforechange:function(a){if("function"=== | ||
32 | +typeof a)this._introBeforeChangeCallback=a;else throw Error("Provided callback for onbeforechange was not a function");return this},onchange:function(a){if("function"===typeof a)this._introChangeCallback=a;else throw Error("Provided callback for onchange was not a function.");return this},onafterchange:function(a){if("function"===typeof a)this._introAfterChangeCallback=a;else throw Error("Provided callback for onafterchange was not a function");return this},oncomplete:function(a){if("function"=== | ||
33 | +typeof a)this._introCompleteCallback=a;else throw Error("Provided callback for oncomplete was not a function.");return this},onexit:function(a){if("function"===typeof a)this._introExitCallback=a;else throw Error("Provided callback for onexit was not a function.");return this}};return w.introJs=B}); |
@@ -0,0 +1 @@ | @@ -0,0 +1 @@ | ||
1 | +.introjs-overlay{position:absolute;z-index:999999;background-color:#000;opacity:0;background:-moz-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);background:-webkit-gradient(radial,center center,0px,center center,100%,color-stop(0%,rgba(0,0,0,0.4)),color-stop(100%,rgba(0,0,0,0.9)));background:-webkit-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);background:-o-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);background:-ms-radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);background:radial-gradient(center,ellipse cover,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#66000000',endColorstr='#e6000000',GradientType=1);-ms-filter:"alpha(opacity=50)";filter:alpha(opacity=50);-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.introjs-fixParent{z-index:auto!important;opacity:1.0!important;position:absolute!important;-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important;-o-transform:none!important;transform:none!important}.introjs-showElement,tr.introjs-showElement>td,tr.introjs-showElement>th{z-index:9999999!important}.introjs-disableInteraction{z-index:99999999!important;position:absolute}.introjs-relativePosition,tr.introjs-showElement>td,tr.introjs-showElement>th{position:relative}.introjs-helperLayer{position:absolute;z-index:9999998;background-color:#FFF;background-color:rgba(255,255,255,.9);border:1px solid #777;border:1px solid rgba(0,0,0,.5);border-radius:4px;box-shadow:0 2px 15px rgba(0,0,0,.4);-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.introjs-tooltipReferenceLayer{position:absolute;z-index:10000000;background-color:transparent;-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.introjs-helperLayer *,.introjs-helperLayer *:before,.introjs-helperLayer *:after{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;-ms-box-sizing:content-box;-o-box-sizing:content-box;box-sizing:content-box}.introjs-helperNumberLayer{position:absolute;top:-16px;left:-16px;z-index:9999999999!important;padding:2px;font-family:Arial,verdana,tahoma;font-size:13px;font-weight:bold;color:white;text-align:center;text-shadow:1px 1px 1px rgba(0,0,0,.3);background:#ff3019;background:-webkit-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ff3019),color-stop(100%,#cf0404));background:-moz-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-ms-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-o-linear-gradient(top,#ff3019 0,#cf0404 100%);background:linear-gradient(to bottom,#ff3019 0,#cf0404 100%);width:20px;height:20px;line-height:20px;border:3px solid white;border-radius:50%;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3019',endColorstr='#cf0404',GradientType=0);filter:progid:DXImageTransform.Microsoft.Shadow(direction=135,strength=2,color=ff0000);box-shadow:0 2px 5px rgba(0,0,0,.4)}.introjs-arrow{border:5px solid white;content:'';position:absolute}.introjs-arrow.top{top:-10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:white;border-left-color:transparent}.introjs-arrow.top-right{top:-10px;right:10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:white;border-left-color:transparent}.introjs-arrow.top-middle{top:-10px;left:50%;margin-left:-5px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:white;border-left-color:transparent}.introjs-arrow.right{right:-10px;top:10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:transparent;border-left-color:white}.introjs-arrow.right-bottom{bottom:10px;right:-10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:transparent;border-left-color:white}.introjs-arrow.bottom{bottom:-10px;border-top-color:white;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}.introjs-arrow.left{left:-10px;top:10px;border-top-color:transparent;border-right-color:white;border-bottom-color:transparent;border-left-color:transparent}.introjs-arrow.left-bottom{left:-10px;bottom:10px;border-top-color:transparent;border-right-color:white;border-bottom-color:transparent;border-left-color:transparent}.introjs-tooltip{position:absolute;padding:10px;background-color:white;min-width:200px;max-width:300px;border-radius:3px;box-shadow:0 1px 10px rgba(0,0,0,.4);-webkit-transition:opacity .1s ease-out;-moz-transition:opacity .1s ease-out;-ms-transition:opacity .1s ease-out;-o-transition:opacity .1s ease-out;transition:opacity .1s ease-out}.introjs-tooltipbuttons{text-align:right;white-space:nowrap}.introjs-button{position:relative;overflow:visible;display:inline-block;padding:.3em .8em;border:1px solid #d4d4d4;margin:0;text-decoration:none;text-shadow:1px 1px 0 #fff;font:11px/normal sans-serif;color:#333;white-space:nowrap;cursor:pointer;outline:0;background-color:#ececec;background-image:-webkit-gradient(linear,0 0,0 100%,from(#f4f4f4),to(#ececec));background-image:-moz-linear-gradient(#f4f4f4,#ececec);background-image:-o-linear-gradient(#f4f4f4,#ececec);background-image:linear-gradient(#f4f4f4,#ececec);-webkit-background-clip:padding;-moz-background-clip:padding;-o-background-clip:padding-box;-webkit-border-radius:.2em;-moz-border-radius:.2em;border-radius:.2em;zoom:1;*display:inline;margin-top:10px}.introjs-button:hover{border-color:#bcbcbc;text-decoration:none;box-shadow:0 1px 1px #e3e3e3}.introjs-button:focus,.introjs-button:active{background-image:-webkit-gradient(linear,0 0,0 100%,from(#ececec),to(#f4f4f4));background-image:-moz-linear-gradient(#ececec,#f4f4f4);background-image:-o-linear-gradient(#ececec,#f4f4f4);background-image:linear-gradient(#ececec,#f4f4f4)}.introjs-button::-moz-focus-inner{padding:0;border:0}.introjs-skipbutton{margin-right:5px;color:#7a7a7a}.introjs-prevbutton{-webkit-border-radius:.2em 0 0 .2em;-moz-border-radius:.2em 0 0 .2em;border-radius:.2em 0 0 .2em;border-right:0}.introjs-nextbutton{-webkit-border-radius:0 .2em .2em 0;-moz-border-radius:0 .2em .2em 0;border-radius:0 .2em .2em 0}.introjs-disabled,.introjs-disabled:hover,.introjs-disabled:focus{color:#9a9a9a;border-color:#d4d4d4;box-shadow:none;cursor:default;background-color:#f4f4f4;background-image:none;text-decoration:none}.introjs-bullets{text-align:center}.introjs-bullets ul{clear:both;margin:15px auto 0;padding:0;display:inline-block}.introjs-bullets ul li{list-style:none;float:left;margin:0 2px}.introjs-bullets ul li a{display:block;width:6px;height:6px;background:#ccc;border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:10px;text-decoration:none}.introjs-bullets ul li a:hover{background:#999}.introjs-bullets ul li a.active{background:#999}.introjs-progress{overflow:hidden;height:10px;margin:10px 0 5px 0;border-radius:4px;background-color:#ecf0f1}.introjs-progressbar{float:left;width:0;height:100%;font-size:10px;line-height:10px;text-align:center;background-color:#08c}.introjsFloatingElement{position:absolute;height:0;width:0;left:50%;top:50%} | ||
0 | \ No newline at end of file | 2 | \ No newline at end of file |
@@ -0,0 +1,99 @@ | @@ -0,0 +1,99 @@ | ||
1 | +var siteTourPlugin = (function() { | ||
2 | + | ||
3 | + var actions = []; | ||
4 | + var groupTriggers = []; | ||
5 | + var userData = {}; | ||
6 | + var intro; | ||
7 | + var options = {}; | ||
8 | + | ||
9 | + function hasMark(name) { | ||
10 | + return jQuery.cookie("_noosfero_.sitetour." + name) || | ||
11 | + jQuery.inArray(name, userData.site_tour_plugin_actions)>=0; | ||
12 | + } | ||
13 | + | ||
14 | + function mark(name) { | ||
15 | + jQuery.cookie("_noosfero_.sitetour." + name, 1, {expires: 365}); | ||
16 | + if(userData.login) { | ||
17 | + jQuery.post('/plugin/site_tour/public/mark_action', {action_name: name}, function(data) { }); | ||
18 | + } | ||
19 | + } | ||
20 | + | ||
21 | + function clearAll() { | ||
22 | + jQuery('.site-tour-plugin').removeAttr('data-intro data-intro-name data-step'); | ||
23 | + } | ||
24 | + | ||
25 | + function configureIntro(force, actions) { | ||
26 | + clearAll(); | ||
27 | + for(var i=0; i<actions.length; i++) { | ||
28 | + var action = actions[i]; | ||
29 | + | ||
30 | + if(force || !hasMark(action.name)) { | ||
31 | + var el = jQuery(action.selector).filter(function() { | ||
32 | + return jQuery(this).is(":visible") && jQuery(this).css('visibility') != 'hidden'; | ||
33 | + }); | ||
34 | + el.addClass('site-tour-plugin'); | ||
35 | + el.attr('data-intro', action.text); | ||
36 | + el.attr('data-intro-name', action.name); | ||
37 | + if(action.step) { | ||
38 | + el.attr('data-step', action.step); | ||
39 | + } | ||
40 | + } | ||
41 | + } | ||
42 | + } | ||
43 | + | ||
44 | + function actionsOnload() { | ||
45 | + var groups = jQuery.map(groupTriggers, function(g) { return g.name; }); | ||
46 | + return jQuery.grep(actions, function(n, i) { return jQuery.inArray(n.name, groups); }); | ||
47 | + } | ||
48 | + | ||
49 | + function actionsByGroup(group) { | ||
50 | + return jQuery.grep(actions, function(n, i) { return n.name===group }); | ||
51 | + } | ||
52 | + | ||
53 | + function forceParam() { | ||
54 | + return jQuery.deparam.querystring()['siteTourPlugin']==='force'; | ||
55 | + } | ||
56 | + | ||
57 | + return { | ||
58 | + setOption: function(key, value) { | ||
59 | + options[key] = value; | ||
60 | + }, | ||
61 | + add: function (name, selector, text, step) { | ||
62 | + actions.push({name: name, selector: selector, text: text, step: step}); | ||
63 | + }, | ||
64 | + addGroupTrigger: function(name, selector, ev) { | ||
65 | + groupTriggers.push({name: name, selector: selector, event: ev}); | ||
66 | + plugin = this; | ||
67 | + var handler = function() { | ||
68 | + configureIntro(forceParam(), actionsByGroup(name)); | ||
69 | + intro.start(); | ||
70 | + jQuery(document).off(ev, selector, handler); | ||
71 | + }; | ||
72 | + jQuery(document).on(ev, selector, handler); | ||
73 | + }, | ||
74 | + start: function(data, force) { | ||
75 | + force = typeof force !== 'undefined' ? force : false || forceParam(); | ||
76 | + userData = data; | ||
77 | + | ||
78 | + intro = introJs(); | ||
79 | + intro.setOption('tooltipPosition', 'auto'); | ||
80 | + intro.setOption('showStepNumbers', 'false'); | ||
81 | + intro.setOptions(options); | ||
82 | + intro.onafterchange(function(targetElement) { | ||
83 | + var name = jQuery(targetElement).attr('data-intro-name'); | ||
84 | + mark(name); | ||
85 | + }); | ||
86 | + configureIntro(force, actionsOnload()); | ||
87 | + intro.start(); | ||
88 | + }, | ||
89 | + force: function() { | ||
90 | + this.start({}, true); | ||
91 | + } | ||
92 | + } | ||
93 | +})(); | ||
94 | + | ||
95 | +jQuery( document ).ready(function( $ ) { | ||
96 | + $(window).bind('userDataLoaded', function(event, data) { | ||
97 | + siteTourPlugin.start(data); | ||
98 | + }); | ||
99 | +}); |
@@ -0,0 +1,6 @@ | @@ -0,0 +1,6 @@ | ||
1 | +jQuery( document ).ready(function( $ ) { | ||
2 | + siteTourPlugin.add('login_button','.action-home-index #link_login', "Click to login!"); | ||
3 | + siteTourPlugin.add('login_button','.action-home-index .signup', "Click to signup!"); | ||
4 | + siteTourPlugin.add('logout_button','.action-home-index #logout', "Click to logout"); | ||
5 | + siteTourPlugin.add('navigation_bar','#navigation', "Click to navigate"); | ||
6 | +}); |
plugins/site_tour/test/functional/site_tour_plugin_admin_controller_test.rb
0 → 100644
@@ -0,0 +1,58 @@ | @@ -0,0 +1,58 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | + | ||
3 | +class SiteTourPluginAdminControllerTest < ActionController::TestCase | ||
4 | + | ||
5 | + def setup | ||
6 | + @environment = Environment.default | ||
7 | + login_as(create_admin_user(@environment)) | ||
8 | + end | ||
9 | + | ||
10 | + attr_reader :environment | ||
11 | + | ||
12 | + should 'parse csv and save actions array in plugin settings' do | ||
13 | + actions_csv = "en,tour_plugin,.tour-button,Click" | ||
14 | + post :index, :settings => {"actions_csv" => actions_csv} | ||
15 | + @settings = Noosfero::Plugin::Settings.new(environment.reload, SiteTourPlugin) | ||
16 | + assert_equal [{:language => 'en', :group_name => 'tour_plugin', :selector => '.tour-button', :description => 'Click'}], @settings.actions | ||
17 | + end | ||
18 | + | ||
19 | + should 'parse csv and save group triggers array in plugin settings' do | ||
20 | + group_triggers_csv = "tour_plugin,.tour-button,mouseenter" | ||
21 | + post :index, :settings => {"group_triggers_csv" => group_triggers_csv} | ||
22 | + @settings = Noosfero::Plugin::Settings.new(environment.reload, SiteTourPlugin) | ||
23 | + assert_equal [{:group_name => 'tour_plugin', :selector => '.tour-button', :event => 'mouseenter'}], @settings.group_triggers | ||
24 | + end | ||
25 | + | ||
26 | + should 'do not store actions_csv' do | ||
27 | + actions_csv = "en,tour_plugin,.tour-button,Click" | ||
28 | + post :index, :settings => {"actions_csv" => actions_csv} | ||
29 | + @settings = Noosfero::Plugin::Settings.new(environment.reload, SiteTourPlugin) | ||
30 | + assert_equal nil, @settings.settings[:actions_csv] | ||
31 | + end | ||
32 | + | ||
33 | + should 'do not store group_triggers_csv' do | ||
34 | + group_triggers_csv = "tour_plugin,.tour-button,click" | ||
35 | + post :index, :settings => {"group_triggers_csv" => group_triggers_csv} | ||
36 | + @settings = Noosfero::Plugin::Settings.new(environment.reload, SiteTourPlugin) | ||
37 | + assert_equal nil, @settings.settings[:group_triggers_csv] | ||
38 | + end | ||
39 | + | ||
40 | + should 'convert actions array to csv to enable user edition' do | ||
41 | + @settings = Noosfero::Plugin::Settings.new(environment.reload, SiteTourPlugin) | ||
42 | + @settings.actions = [{:language => 'en', :group_name => 'tour_plugin', :selector => '.tour-button', :description => 'Click'}] | ||
43 | + @settings.save! | ||
44 | + | ||
45 | + get :index | ||
46 | + assert_tag :tag => 'textarea', :attributes => {:class => 'actions-csv'}, :content => "\nen,tour_plugin,.tour-button,Click\n" | ||
47 | + end | ||
48 | + | ||
49 | + should 'convert group_triggers array to csv to enable user edition' do | ||
50 | + @settings = Noosfero::Plugin::Settings.new(environment.reload, SiteTourPlugin) | ||
51 | + @settings.group_triggers = [{:group_name => 'tour_plugin', :selector => '.tour-button', :event => 'click'}] | ||
52 | + @settings.save! | ||
53 | + | ||
54 | + get :index | ||
55 | + assert_tag :tag => 'textarea', :attributes => {:class => 'groups-csv'}, :content => "\ntour_plugin,.tour-button,click\n" | ||
56 | + end | ||
57 | + | ||
58 | +end |
plugins/site_tour/test/functional/site_tour_plugin_public_controller_test.rb
0 → 100644
@@ -0,0 +1,30 @@ | @@ -0,0 +1,30 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | + | ||
3 | +class SiteTourPluginPublicControllerTest < ActionController::TestCase | ||
4 | + | ||
5 | + def setup | ||
6 | + @person = create_user('testuser').person | ||
7 | + end | ||
8 | + | ||
9 | + attr_accessor :person | ||
10 | + | ||
11 | + should 'not be able to mark an action if is not logged in' do | ||
12 | + xhr :post, :mark_action, :action_name => 'test' | ||
13 | + assert_response 401 | ||
14 | + end | ||
15 | + | ||
16 | + should 'be able to mark one action' do | ||
17 | + login_as(person.identifier) | ||
18 | + xhr :post, :mark_action, :action_name => 'test' | ||
19 | + assert_equal({'ok' => true}, ActiveSupport::JSON.decode(response.body)) | ||
20 | + assert_equal ['test'], person.reload.site_tour_plugin_actions | ||
21 | + end | ||
22 | + | ||
23 | + should 'be able to mark multiple actions' do | ||
24 | + login_as(person.identifier) | ||
25 | + xhr :post, :mark_action, :action_name => ['test1', 'test2'] | ||
26 | + assert_equal({'ok' => true}, ActiveSupport::JSON.decode(response.body)) | ||
27 | + assert_equal ['test1', 'test2'], person.reload.site_tour_plugin_actions | ||
28 | + end | ||
29 | + | ||
30 | +end |
@@ -0,0 +1 @@ | @@ -0,0 +1 @@ | ||
1 | +require File.dirname(__FILE__) + '/../../../test/test_helper' |
@@ -0,0 +1,17 @@ | @@ -0,0 +1,17 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | + | ||
3 | +class SiteTourHelperTest < ActionView::TestCase | ||
4 | + | ||
5 | + include SiteTourPlugin::SiteTourHelper | ||
6 | + | ||
7 | + should 'parse tooltip description' do | ||
8 | + assert_equal 'test', parse_tour_description("test") | ||
9 | + end | ||
10 | + | ||
11 | + should 'replace profile attributes in tooltip description' do | ||
12 | + profile = fast_create(Profile) | ||
13 | + expects(:profile).returns(profile).at_least_once | ||
14 | + assert_equal "name #{profile.name}, identifier #{profile.identifier}, url #{url_for profile.url}", parse_tour_description("name {profile.name}, identifier {profile.identifier}, url {profile.url}") | ||
15 | + end | ||
16 | + | ||
17 | +end |
@@ -0,0 +1,73 @@ | @@ -0,0 +1,73 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | + | ||
3 | +class SiteTourPluginTest < ActionView::TestCase | ||
4 | + | ||
5 | + def setup | ||
6 | + @plugin = SiteTourPlugin.new | ||
7 | + end | ||
8 | + | ||
9 | + attr_accessor :plugin | ||
10 | + | ||
11 | + should 'include site tour plugin actions in user data for logged in users' do | ||
12 | + expects(:logged_in?).returns(true) | ||
13 | + person = create_user('testuser').person | ||
14 | + person.site_tour_plugin_actions = ['login', 'navigation'] | ||
15 | + expects(:user).returns(person) | ||
16 | + | ||
17 | + assert_equal({:site_tour_plugin_actions => ['login', 'navigation']}, instance_eval(&plugin.user_data_extras)) | ||
18 | + end | ||
19 | + | ||
20 | + should 'return empty hash when user is not logged in' do | ||
21 | + expects(:logged_in?).returns(false) | ||
22 | + assert_equal({}, instance_eval(&plugin.user_data_extras)) | ||
23 | + end | ||
24 | + | ||
25 | + should 'include javascript related to tour instructions if file exists' do | ||
26 | + file = '/plugins/site_tour/tour/pt/tour.js' | ||
27 | + expects(:language).returns('pt') | ||
28 | + File.expects(:exists?).with(Rails.root.join("public#{file}").to_s).returns(true) | ||
29 | + expects(:environment).returns(Environment.default) | ||
30 | + assert_tag_in_string instance_exec(&plugin.body_ending), :tag => 'script' | ||
31 | + end | ||
32 | + | ||
33 | + should 'not include javascript file that not exists' do | ||
34 | + file = '/plugins/site_tour/tour/pt/tour.js' | ||
35 | + expects(:language).returns('pt') | ||
36 | + File.expects(:exists?).with(Rails.root.join("public#{file}").to_s).returns(false) | ||
37 | + expects(:environment).returns(Environment.default) | ||
38 | + assert_no_tag_in_string instance_exec(&plugin.body_ending), :tag => "script" | ||
39 | + end | ||
40 | + | ||
41 | + should 'render javascript tag with tooltip actions and group triggers' do | ||
42 | + expects(:language).returns('en').at_least_once | ||
43 | + | ||
44 | + settings = Noosfero::Plugin::Settings.new(Environment.default, SiteTourPlugin) | ||
45 | + settings.actions = [{:language => 'en', :group_name => 'test', :selector => 'body', :description => 'Test'}] | ||
46 | + settings.group_triggers = [{:group_name => 'test', :selector => 'body', :event => 'click'}] | ||
47 | + settings.save! | ||
48 | + | ||
49 | + expects(:environment).returns(Environment.default) | ||
50 | + body_ending = instance_exec(&plugin.body_ending) | ||
51 | + assert_match /siteTourPlugin\.add\('test', 'body', 'Test', 1\);/, body_ending | ||
52 | + assert_match /siteTourPlugin\.addGroupTrigger\('test', 'body', 'click'\);/, body_ending | ||
53 | + end | ||
54 | + | ||
55 | + should 'start each tooltip group with the correct step order' do | ||
56 | + expects(:language).returns('en').at_least_once | ||
57 | + | ||
58 | + settings = Noosfero::Plugin::Settings.new(Environment.default, SiteTourPlugin) | ||
59 | + settings.actions = [ | ||
60 | + {:language => 'en', :group_name => 'test_a', :selector => 'body', :description => 'Test A1'}, | ||
61 | + {:language => 'en', :group_name => 'test_a', :selector => 'body', :description => 'Test A2'}, | ||
62 | + {:language => 'en', :group_name => 'test_b', :selector => 'body', :description => 'Test B1'}, | ||
63 | + ] | ||
64 | + settings.save! | ||
65 | + | ||
66 | + expects(:environment).returns(Environment.default) | ||
67 | + body_ending = instance_exec(&plugin.body_ending) | ||
68 | + assert_match /siteTourPlugin\.add\('test_a', 'body', 'Test A1', 1\);/, body_ending | ||
69 | + assert_match /siteTourPlugin\.add\('test_a', 'body', 'Test A2', 2\);/, body_ending | ||
70 | + assert_match /siteTourPlugin\.add\('test_b', 'body', 'Test B1', 3\);/, body_ending | ||
71 | + end | ||
72 | + | ||
73 | +end |
@@ -0,0 +1,41 @@ | @@ -0,0 +1,41 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | + | ||
3 | +class TrackListBlockTest < ActionView::TestCase | ||
4 | + | ||
5 | + ActionView::Base.send :include, ApplicationHelper | ||
6 | + | ||
7 | + def setup | ||
8 | + @block = fast_create(SiteTourPlugin::TourBlock) | ||
9 | + end | ||
10 | + | ||
11 | + attr_accessor :block | ||
12 | + | ||
13 | + should 'do not save empty actions' do | ||
14 | + block.actions = [{:group_name => '', :selector => nil, :description => ' '}] | ||
15 | + block.save! | ||
16 | + assert_equal [], block.actions | ||
17 | + end | ||
18 | + | ||
19 | + should 'render script tag in visualization mode' do | ||
20 | + controller.expects(:boxes_editor?).returns(false) | ||
21 | + assert_tag_in_string instance_eval(&block.content), :tag => 'script' | ||
22 | + end | ||
23 | + | ||
24 | + should 'do not render script tag when editing' do | ||
25 | + controller.expects(:boxes_editor?).returns(true) | ||
26 | + controller.expects(:uses_design_blocks?).returns(true) | ||
27 | + assert_no_tag_in_string instance_eval(&block.content), :tag => 'script' | ||
28 | + end | ||
29 | + | ||
30 | + should 'display help button' do | ||
31 | + controller.expects(:boxes_editor?).returns(false) | ||
32 | + assert_tag_in_string instance_eval(&block.content), :tag => 'a', :attributes => {:class => 'button icon-help with-text tour-button'} | ||
33 | + end | ||
34 | + | ||
35 | + should 'do not display help button when display_button is false' do | ||
36 | + block.display_button = false | ||
37 | + controller.expects(:boxes_editor?).returns(false) | ||
38 | + assert_no_tag_in_string instance_eval(&block.content), :tag => 'a', :attributes => {:class => 'button icon-help with-text tour-button'} | ||
39 | + end | ||
40 | + | ||
41 | +end |
@@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
1 | +<%= block_title(block.title) %> | ||
2 | + | ||
3 | +<% if block.display_button %> | ||
4 | + <%= button :help, _('Help'), '#', :class => 'tour-button', :onclick => 'siteTourPlugin.force();' %> | ||
5 | +<% end %> | ||
6 | + | ||
7 | +<% edit_mode = controller.send(:boxes_editor?) && controller.send(:uses_design_blocks?) %> | ||
8 | + | ||
9 | +<% unless edit_mode %> | ||
10 | + <%= render :file => 'tour_actions', :locals => {:actions => block.actions, :group_triggers => block.group_triggers} %> | ||
11 | +<% end %> |
plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block.html.erb
0 → 100644
@@ -0,0 +1,44 @@ | @@ -0,0 +1,44 @@ | ||
1 | +<%= javascript_include_tag '/plugins/site_tour/edit_tour_block.js' %> | ||
2 | +<%= stylesheet_link_tag '/plugins/site_tour/edit_tour_block.css' %> | ||
3 | + | ||
4 | +<%= labelled_form_field check_box(:block, :display_button) + _('Display help button'), '' %> | ||
5 | + | ||
6 | +<div id='edit-tour-block'> | ||
7 | + <div id="tooltip-actions" class="list-items"> | ||
8 | + <h3><%= _('Tooltip Actions') %></h3> | ||
9 | + <div class="special-attributes"><%= _('Special fields for description: {profile.name}, {profile.identifier}, {profile.url}.') %></div> | ||
10 | + <ul class='list-header'> | ||
11 | + <li class='list-name'><%= _('Group Name') %></li> | ||
12 | + <li class='list-selector'><%= _('Selector') %></li> | ||
13 | + <li class='list-description'><%= _('Description') %></li> | ||
14 | + </ul> | ||
15 | + <ul id="droppable-tour-actions" class="droppable-items"> | ||
16 | + <% for action in @block.actions do %> | ||
17 | + <%= render :partial => 'box_organizer/site_tour_plugin/tour_block_item', :locals => {:action => action} %> | ||
18 | + <% end %> | ||
19 | + </ul> | ||
20 | + <div id="new-template"> | ||
21 | + <%= render :partial => 'box_organizer/site_tour_plugin/tour_block_item', :locals => {:action => {} } %> | ||
22 | + </div> | ||
23 | + <%= link_to_function(_('New Tooltip'), :class => 'add-item button icon-add with-text') %> | ||
24 | + </div> | ||
25 | + | ||
26 | + <div id="group-triggers" class="list-items"> | ||
27 | + <h3><%= _('Group Triggers') %></h3> | ||
28 | + <ul class='list-header'> | ||
29 | + <li class='list-name'><%= _('Group Name') %></li> | ||
30 | + <li class='list-selector'><%= _('Selector') %></li> | ||
31 | + <li class='list-event'><%= _('Event') %></li> | ||
32 | + </ul> | ||
33 | + <ul id="droppable-tour-group-triggers" class="droppable-items"> | ||
34 | + <% for group in @block.group_triggers do %> | ||
35 | + <%= render :partial => 'box_organizer/site_tour_plugin/tour_block_group_item', :locals => {:group => group} %> | ||
36 | + <% end %> | ||
37 | + </ul> | ||
38 | + <div id="new-template"> | ||
39 | + <%= render :partial => 'box_organizer/site_tour_plugin/tour_block_group_item', :locals => {:group => {} } %> | ||
40 | + </div> | ||
41 | + <%= link_to_function(_('New Group Trigger'), :class => 'add-item button icon-add with-text') %> | ||
42 | + </div> | ||
43 | + | ||
44 | +</div> |
plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block_group_item.html.erb
0 → 100644
@@ -0,0 +1,19 @@ | @@ -0,0 +1,19 @@ | ||
1 | +<li> | ||
2 | + <ul class="item-row"> | ||
3 | + <li> | ||
4 | + <%= text_field_tag 'block[group_triggers][][group_name]', group[:group_name], :class => 'group-name', :maxlength => 20 %> | ||
5 | + </li> | ||
6 | + <li> | ||
7 | + <%= text_field_tag 'block[group_triggers][][selector]', group[:selector], :class => 'selector' %> | ||
8 | + </li> | ||
9 | + <li> | ||
10 | + <%= select_tag 'block[group_triggers][][event]', | ||
11 | + options_for_select(['mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'click', 'change', 'select', 'keydown', 'keyup', 'keypress', 'focus', 'blur', 'submit', 'drag', 'drop'], group[:event]), | ||
12 | + :class => 'description' %> | ||
13 | + </li> | ||
14 | + <li> | ||
15 | + <%= button_without_text(:delete, _('Delete'), "#" , :class=>"delete-tour-block-item") %> | ||
16 | + </li> | ||
17 | + </ul> | ||
18 | +</li> | ||
19 | + |
plugins/site_tour/views/box_organizer/site_tour_plugin/_tour_block_item.html.erb
0 → 100644
@@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
1 | +<li> | ||
2 | + <ul class="item-row"> | ||
3 | + <li> | ||
4 | + <%= text_field_tag 'block[actions][][group_name]', action[:group_name], :class => 'group-name', :maxlength => 20 %> | ||
5 | + </li> | ||
6 | + <li> | ||
7 | + <%= text_field_tag 'block[actions][][selector]', action[:selector], :class => 'selector' %> | ||
8 | + </li> | ||
9 | + <li> | ||
10 | + <%= text_field_tag 'block[actions][][description]', action[:description], :class => 'description' %> | ||
11 | + </li> | ||
12 | + <li> | ||
13 | + <%= button_without_text(:delete, _('Delete'), "#" , :class=>"delete-tour-block-item") %> | ||
14 | + </li> | ||
15 | + </ul> | ||
16 | +</li> |
plugins/site_tour/views/environment_design/site_tour_plugin
0 → 120000
plugins/site_tour/views/site_tour_plugin_admin/index.html.erb
0 → 100644
@@ -0,0 +1,13 @@ | @@ -0,0 +1,13 @@ | ||
1 | +<h1><%= _('Site Tour Settings')%></h1> | ||
2 | + | ||
3 | +<%= form_for(:settings) do |f| %> | ||
4 | + | ||
5 | + <%= labelled_form_field _('Tooltips (CSV format: language, group name, selector, description)'), f.text_area(:actions_csv, :style => 'width: 100%', :class => 'actions-csv') %> | ||
6 | + <%= labelled_form_field _('Group Triggers (CSV format: group name, selector, event (e.g. mouseenter, click))'), f.text_area(:group_triggers_csv, :style => 'width: 100%', :class => 'groups-csv', :rows => 7) %> | ||
7 | + | ||
8 | + <% button_bar do %> | ||
9 | + <%= submit_button(:save, _('Save'), :cancel => {:controller => 'plugins', :action => 'index'}) %> | ||
10 | + <% end %> | ||
11 | + | ||
12 | +<% end %> | ||
13 | + |
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +<% extend SiteTourPlugin::SiteTourHelper %> | ||
2 | +<% js_file = defined?(:js_file) ? js_file : nil %> | ||
3 | +<%= javascript_include_tag(js_file) if js_file.present? %> | ||
4 | + | ||
5 | +<% if actions.present? %> | ||
6 | +<script> | ||
7 | + jQuery( document ).ready(function( $ ) { | ||
8 | + <% actions.each_with_index do |action, index| %> | ||
9 | + <%= "siteTourPlugin.add('#{j action[:group_name]}', '#{j action[:selector]}', '#{j parse_tour_description(action[:description])}', #{index + 1});" %> | ||
10 | + <% end %> | ||
11 | + | ||
12 | + <% (group_triggers||[]).each do |group| %> | ||
13 | + <%= "siteTourPlugin.addGroupTrigger('#{j group[:group_name]}', '#{j group[:selector]}', '#{j group[:event]}');" %> | ||
14 | + <% end %> | ||
15 | + | ||
16 | + siteTourPlugin.setOption('nextLabel', '<%= _('Next') %>'); | ||
17 | + siteTourPlugin.setOption('prevLabel', '<%= _('Back') %>'); | ||
18 | + siteTourPlugin.setOption('skipLabel', '<%= _('Skip') %>'); | ||
19 | + siteTourPlugin.setOption('doneLabel', '<%= _('Finish') %>'); | ||
20 | + }); | ||
21 | +</script> | ||
22 | +<% end %> |
po/de/noosfero-doc.po
@@ -7,8 +7,8 @@ msgid "" | @@ -7,8 +7,8 @@ msgid "" | ||
7 | msgstr "" | 7 | msgstr "" |
8 | "Project-Id-Version: PACKAGE VERSION\n" | 8 | "Project-Id-Version: PACKAGE VERSION\n" |
9 | "POT-Creation-Date: 2013-12-10 15:48-0300\n" | 9 | "POT-Creation-Date: 2013-12-10 15:48-0300\n" |
10 | -"PO-Revision-Date: 2015-02-23 11:32+0200\n" | ||
11 | -"Last-Translator: Michal Čihař <michal@cihar.com>\n" | 10 | +"PO-Revision-Date: 2015-08-14 09:48+0200\n" |
11 | +"Last-Translator: Phillip Rohmberger <rohmberger@hotmail.de>\n" | ||
12 | "Language-Team: German " | 12 | "Language-Team: German " |
13 | "<https://hosted.weblate.org/projects/noosfero/documentation/de/>\n" | 13 | "<https://hosted.weblate.org/projects/noosfero/documentation/de/>\n" |
14 | "Language: de\n" | 14 | "Language: de\n" |
@@ -16,7 +16,7 @@ msgstr "" | @@ -16,7 +16,7 @@ msgstr "" | ||
16 | "Content-Type: text/plain; charset=UTF-8\n" | 16 | "Content-Type: text/plain; charset=UTF-8\n" |
17 | "Content-Transfer-Encoding: 8bit\n" | 17 | "Content-Transfer-Encoding: 8bit\n" |
18 | "Plural-Forms: nplurals=2; plural=n != 1;\n" | 18 | "Plural-Forms: nplurals=2; plural=n != 1;\n" |
19 | -"X-Generator: Weblate 2.3-dev\n" | 19 | +"X-Generator: Weblate 2.4-dev\n" |
20 | 20 | ||
21 | # type: Content of: <h1> | 21 | # type: Content of: <h1> |
22 | #. type: Content of: <h1> | 22 | #. type: Content of: <h1> |
@@ -28,7 +28,7 @@ msgstr "" | @@ -28,7 +28,7 @@ msgstr "" | ||
28 | #. type: Content of: <p> | 28 | #. type: Content of: <p> |
29 | #: doc/noosfero/plugins/send_email.en.xhtml:2 | 29 | #: doc/noosfero/plugins/send_email.en.xhtml:2 |
30 | msgid "Allows to send e-mails through an e-mail form." | 30 | msgid "Allows to send e-mails through an e-mail form." |
31 | -msgstr "" | 31 | +msgstr "Ermöglicht das Senden von E-mails über ein E-mail-Formular." |
32 | 32 | ||
33 | # type: Content of: <h2> | 33 | # type: Content of: <h2> |
34 | #. type: Content of: <h2> | 34 | #. type: Content of: <h2> |
@@ -36,7 +36,7 @@ msgstr "" | @@ -36,7 +36,7 @@ msgstr "" | ||
36 | #: doc/noosfero/plugins/google_cse.en.xhtml:3 | 36 | #: doc/noosfero/plugins/google_cse.en.xhtml:3 |
37 | #: doc/noosfero/plugins/google_analytics.en.xhtml:3 | 37 | #: doc/noosfero/plugins/google_analytics.en.xhtml:3 |
38 | msgid "Usage" | 38 | msgid "Usage" |
39 | -msgstr "" | 39 | +msgstr "Verwendung" |
40 | 40 | ||
41 | # type: Content of: <ul><li> | 41 | # type: Content of: <ul><li> |
42 | #. type: Content of: <ul><li> | 42 | #. type: Content of: <ul><li> |
@@ -52,6 +52,7 @@ msgstr "" | @@ -52,6 +52,7 @@ msgstr "" | ||
52 | msgid "" | 52 | msgid "" |
53 | "Add a “to” and “message” field and a submit button" | 53 | "Add a “to” and “message” field and a submit button" |
54 | msgstr "" | 54 | msgstr "" |
55 | +"Fügt ein ``to``- und ein ``message``-Feld, mit einem ``submit`` Knopf hinzu" | ||
55 | 56 | ||
56 | # type: Content of: <ul><li> | 57 | # type: Content of: <ul><li> |
57 | #. type: Content of: <ul><li> | 58 | #. type: Content of: <ul><li> |
po/pt/noosfero.po
@@ -13,7 +13,7 @@ msgid "" | @@ -13,7 +13,7 @@ msgid "" | ||
13 | msgstr "" | 13 | msgstr "" |
14 | "Project-Id-Version: 1.2~rc2-15-gba5ae5b\n" | 14 | "Project-Id-Version: 1.2~rc2-15-gba5ae5b\n" |
15 | "POT-Creation-Date: 2015-08-06 17:22-0300\n" | 15 | "POT-Creation-Date: 2015-08-06 17:22-0300\n" |
16 | -"PO-Revision-Date: 2015-08-07 16:56+0200\n" | 16 | +"PO-Revision-Date: 2015-08-28 12:36-0300\n" |
17 | "Last-Translator: Antonio Terceiro <terceiro@softwarelivre.org>\n" | 17 | "Last-Translator: Antonio Terceiro <terceiro@softwarelivre.org>\n" |
18 | "Language-Team: Portuguese " | 18 | "Language-Team: Portuguese " |
19 | "<https://hosted.weblate.org/projects/noosfero/noosfero/pt/>\n" | 19 | "<https://hosted.weblate.org/projects/noosfero/noosfero/pt/>\n" |
@@ -6176,8 +6176,8 @@ msgstr "Desativar perfil" | @@ -6176,8 +6176,8 @@ msgstr "Desativar perfil" | ||
6176 | 6176 | ||
6177 | #: app/views/profile_editor/edit.html.erb:83 | 6177 | #: app/views/profile_editor/edit.html.erb:83 |
6178 | #: app/views/profile_editor/edit.html.erb:85 | 6178 | #: app/views/profile_editor/edit.html.erb:85 |
6179 | -msgid "Are you sure you want to deactivate this profile?" | ||
6180 | -msgstr "Tem certeza que deseja desativar este perfil?" | 6179 | +msgid "Are you sure you want to activate this profile?" |
6180 | +msgstr "Tem certeza que deseja ativar este perfil?" | ||
6181 | 6181 | ||
6182 | #: app/views/profile_editor/edit.html.erb:85 | 6182 | #: app/views/profile_editor/edit.html.erb:85 |
6183 | msgid "Activate profile" | 6183 | msgid "Activate profile" |
public/designs/icons/prev_icons.yml
@@ -1,23 +0,0 @@ | @@ -1,23 +0,0 @@ | ||
1 | -# Define the list of icons to preview themes: | ||
2 | -icons: | ||
3 | - - add.png | ||
4 | - - cancel.png | ||
5 | - - mass_mails.png | ||
6 | - - store.png | ||
7 | - | ||
8 | -# Define the main html block for each finded theme: | ||
9 | -generate_preview_block_code: | | ||
10 | - <div class="preview_icon_theme"> | ||
11 | - %EACH_ICON_CODE% | ||
12 | - </div> | ||
13 | - | ||
14 | -# Define the html code to show each icon: | ||
15 | -generate_each_icon_code: | | ||
16 | - <div class="icon_num_%ICON_NUM%"> | ||
17 | - <img src="%PATH%/%ICON_NAME%" /> | ||
18 | - </div> | ||
19 | - | ||
20 | -# The non obvious variables are: | ||
21 | -# %ICON_NUM% - each icon has a number equal to it's list position. It can help the layout definition. | ||
22 | -# %PATH% - the path for the theme. | ||
23 | -# %ICON_NAME% - the icon name without path (exatily equal the name on the icons list) |
public/designs/templates/lefttopright/javascripts/template.js
public/designs/templates/lefttopright/stylesheets/style.css
1 | #boxes { | 1 | #boxes { |
2 | - display: table; | ||
3 | width: 100%; | 2 | width: 100%; |
3 | + height: 100%; | ||
4 | } | 4 | } |
5 | 5 | ||
6 | -.box-1 { | ||
7 | - width: 58%; | ||
8 | - float: left; | ||
9 | - margin: 1% 1% 0% 1%; | 6 | +.box-4 { |
7 | + position: relative; | ||
8 | + float: right; | ||
9 | + width: 78.5%; | ||
10 | + max-height: 400px; | ||
11 | + overflow: hidden; | ||
12 | + margin-left: 1%; | ||
10 | } | 13 | } |
11 | 14 | ||
12 | - | ||
13 | -.box-2 { | 15 | +.box-3 { |
16 | + width: 20.5%; | ||
17 | + height: 100%; | ||
18 | + min-height: 410px; | ||
14 | position: relative; | 19 | position: relative; |
15 | float: left; | 20 | float: left; |
16 | - width: 20%; | ||
17 | } | 21 | } |
18 | 22 | ||
19 | -.box-3 { | 23 | +.box-2 { |
20 | position: relative; | 24 | position: relative; |
21 | float: right; | 25 | float: right; |
22 | - width: 20%; | 26 | + width: 20.5%; |
23 | margin-top: 1%; | 27 | margin-top: 1%; |
24 | } | 28 | } |
25 | 29 | ||
26 | -.box-4 { | ||
27 | - float: left; | ||
28 | - width: 79%; | ||
29 | - margin-left: 21%; | ||
30 | -} | ||
31 | - | ||
32 | -#profile-activity ul, | ||
33 | -#profile-network ul, | ||
34 | -#profile-wall ul { | ||
35 | - width: 460px; | ||
36 | -} | ||
37 | -#profile-activity ul.comment-replies, | ||
38 | -#profile-network ul.comment-replies, | ||
39 | -#profile-wall ul.comment-replies { | ||
40 | - width: auto; | ||
41 | -} | ||
42 | - | 30 | +.box-1 { |
31 | + position: relative; | ||
32 | + float: right; | ||
33 | + width: 57%; | ||
34 | + margin: 1% 1% 0% 1%; | ||
35 | +} | ||
43 | \ No newline at end of file | 36 | \ No newline at end of file |
public/designs/themes/base/style.scss
@@ -1505,7 +1505,8 @@ table#recaptcha_table tr:hover td { | @@ -1505,7 +1505,8 @@ table#recaptcha_table tr:hover td { | ||
1505 | 1505 | ||
1506 | .event-date { | 1506 | .event-date { |
1507 | background: url('/images/calendar_date_select/calendar-icon.png') no-repeat left center; | 1507 | background: url('/images/calendar_date_select/calendar-icon.png') no-repeat left center; |
1508 | - padding: 5px; | 1508 | + padding: 2px; |
1509 | + padding-left: 15px; | ||
1509 | } | 1510 | } |
1510 | 1511 | ||
1511 | .event-link { | 1512 | .event-link { |
public/javascripts/application.js
@@ -107,11 +107,11 @@ function convToValidEmail( str ) { | @@ -107,11 +107,11 @@ function convToValidEmail( str ) { | ||
107 | } | 107 | } |
108 | 108 | ||
109 | function updateUrlField(name_field, id) { | 109 | function updateUrlField(name_field, id) { |
110 | - url_field = $(id); | ||
111 | - old_url_value = url_field.value; | 110 | + url_field = jQuery('#'+id); |
111 | + old_url_value = url_field.val(); | ||
112 | new_url_value = convToValidIdentifier(name_field.value, "-"); | 112 | new_url_value = convToValidIdentifier(name_field.value, "-"); |
113 | 113 | ||
114 | - url_field.value = new_url_value; | 114 | + url_field.val(new_url_value); |
115 | 115 | ||
116 | if (!/^\s*$/.test(old_url_value) | 116 | if (!/^\s*$/.test(old_url_value) |
117 | && old_url_value != new_url_value | 117 | && old_url_value != new_url_value |
test/functional/account_controller_test.rb
@@ -35,6 +35,14 @@ class AccountControllerTest < ActionController::TestCase | @@ -35,6 +35,14 @@ class AccountControllerTest < ActionController::TestCase | ||
35 | post :login, :user => { :login => 'fake', :password => 'fake' } | 35 | post :login, :user => { :login => 'fake', :password => 'fake' } |
36 | end | 36 | end |
37 | 37 | ||
38 | + should 'fail login if a user is inactive and show a warning message' do | ||
39 | + user = User.create!(login: 'testuser', email: 'test@email.com', password:'test', password_confirmation:'test', activation_code: nil) | ||
40 | + post :login, :user => { :login => 'testuser', :password => 'test' } | ||
41 | + | ||
42 | + assert_match 'not activated', session[:notice] | ||
43 | + assert_nil session[:user] | ||
44 | + end | ||
45 | + | ||
38 | def test_should_fail_login_and_not_redirect | 46 | def test_should_fail_login_and_not_redirect |
39 | @request.env["HTTP_REFERER"] = 'bli' | 47 | @request.env["HTTP_REFERER"] = 'bli' |
40 | post :login, :user => {:login => 'johndoe', :password => 'bad password'} | 48 | post :login, :user => {:login => 'johndoe', :password => 'bad password'} |
@@ -260,8 +268,9 @@ class AccountControllerTest < ActionController::TestCase | @@ -260,8 +268,9 @@ class AccountControllerTest < ActionController::TestCase | ||
260 | assert_template 'invalid_change_password_code' | 268 | assert_template 'invalid_change_password_code' |
261 | end | 269 | end |
262 | 270 | ||
263 | - should 'require password confirmation correctly to enter new pasword' do | 271 | + should 'require password confirmation correctly to enter new password' do |
264 | user = create_user('testuser', :email => 'testuser@example.com', :password => 'test', :password_confirmation => 'test') | 272 | user = create_user('testuser', :email => 'testuser@example.com', :password => 'test', :password_confirmation => 'test') |
273 | + user.activate | ||
265 | change = ChangePassword.create!(:requestor => user.person) | 274 | change = ChangePassword.create!(:requestor => user.person) |
266 | 275 | ||
267 | post :new_password, :code => change.code, :change_password => { :password => 'onepass', :password_confirmation => 'another_pass' } | 276 | post :new_password, :code => change.code, :change_password => { :password => 'onepass', :password_confirmation => 'another_pass' } |
@@ -700,6 +709,8 @@ class AccountControllerTest < ActionController::TestCase | @@ -700,6 +709,8 @@ class AccountControllerTest < ActionController::TestCase | ||
700 | get :activate | 709 | get :activate |
701 | assert_nil assigns(:message) | 710 | assert_nil assigns(:message) |
702 | post :login, :user => {:login => 'testuser', :password => 'test123'} | 711 | post :login, :user => {:login => 'testuser', :password => 'test123'} |
712 | + | ||
713 | + assert_match 'not activated', session[:notice] | ||
703 | assert_nil session[:user] | 714 | assert_nil session[:user] |
704 | end | 715 | end |
705 | 716 | ||
@@ -709,6 +720,8 @@ class AccountControllerTest < ActionController::TestCase | @@ -709,6 +720,8 @@ class AccountControllerTest < ActionController::TestCase | ||
709 | get :activate, :activation_code => 'wrongcode' | 720 | get :activate, :activation_code => 'wrongcode' |
710 | assert_nil assigns(:message) | 721 | assert_nil assigns(:message) |
711 | post :login, :user => {:login => 'testuser', :password => 'test123'} | 722 | post :login, :user => {:login => 'testuser', :password => 'test123'} |
723 | + | ||
724 | + assert_match 'not activated', session[:notice] | ||
712 | assert_nil session[:user] | 725 | assert_nil session[:user] |
713 | end | 726 | end |
714 | 727 |
test/functional/events_controller_test.rb
@@ -8,12 +8,12 @@ class EventsControllerTest < ActionController::TestCase | @@ -8,12 +8,12 @@ class EventsControllerTest < ActionController::TestCase | ||
8 | attr_reader :profile | 8 | attr_reader :profile |
9 | 9 | ||
10 | should 'list today events by default' do | 10 | should 'list today events by default' do |
11 | - profile.events << Event.new(:name => 'Joao Birthday', :start_date => Date.today) | ||
12 | - profile.events << Event.new(:name => 'Maria Birthday', :start_date => Date.today) | 11 | + profile.events << Event.new(:name => 'Joao Birthday', :start_date => DateTime.now) |
12 | + profile.events << Event.new(:name => 'Maria Birthday', :start_date => DateTime.now) | ||
13 | 13 | ||
14 | get :events, :profile => profile.identifier | 14 | get :events, :profile => profile.identifier |
15 | 15 | ||
16 | - today = Date.today.strftime("%B %d, %Y") | 16 | + today = DateTime.now.strftime("%B %d, %Y") |
17 | assert_tag :tag => 'div', :attributes => {:id => "agenda-items"}, | 17 | assert_tag :tag => 'div', :attributes => {:id => "agenda-items"}, |
18 | :descendant => {:tag => 'h3', :content => "Events for #{today}"}, | 18 | :descendant => {:tag => 'h3', :content => "Events for #{today}"}, |
19 | :descendant => {:tag => 'tr', :content => "Joao Birthday"}, | 19 | :descendant => {:tag => 'tr', :content => "Joao Birthday"}, |
@@ -23,15 +23,15 @@ class EventsControllerTest < ActionController::TestCase | @@ -23,15 +23,15 @@ class EventsControllerTest < ActionController::TestCase | ||
23 | should 'display calendar of current month' do | 23 | should 'display calendar of current month' do |
24 | get :events, :profile => profile.identifier | 24 | get :events, :profile => profile.identifier |
25 | 25 | ||
26 | - month = Date.today.strftime("%B %Y") | 26 | + month = DateTime.now.strftime("%B %Y") |
27 | assert_tag :tag => 'table', :attributes => {:class => /current-month/}, :descendant => {:tag => 'caption', :content => /#{month}/} | 27 | assert_tag :tag => 'table', :attributes => {:class => /current-month/}, :descendant => {:tag => 'caption', :content => /#{month}/} |
28 | end | 28 | end |
29 | 29 | ||
30 | should 'display links to previous and next month' do | 30 | should 'display links to previous and next month' do |
31 | get :events, :profile => profile.identifier | 31 | get :events, :profile => profile.identifier |
32 | 32 | ||
33 | - prev_month = Date.today - 1.month | ||
34 | - next_month = Date.today + 1.month | 33 | + prev_month = DateTime.now - 1.month |
34 | + next_month = DateTime.now + 1.month | ||
35 | prev_month_name = prev_month.strftime("%B") | 35 | prev_month_name = prev_month.strftime("%B") |
36 | next_month_name = next_month.strftime("%B") | 36 | next_month_name = next_month.strftime("%B") |
37 | assert_tag :tag =>'a', :attributes => {:href => "/profile/#{profile.identifier}/events/#{prev_month.year}/#{prev_month.month}"}, :content => prev_month_name | 37 | assert_tag :tag =>'a', :attributes => {:href => "/profile/#{profile.identifier}/events/#{prev_month.year}/#{prev_month.month}"}, :content => prev_month_name |
@@ -40,14 +40,14 @@ class EventsControllerTest < ActionController::TestCase | @@ -40,14 +40,14 @@ class EventsControllerTest < ActionController::TestCase | ||
40 | 40 | ||
41 | should 'see the events paginated' do | 41 | should 'see the events paginated' do |
42 | 30.times do |i| | 42 | 30.times do |i| |
43 | - profile.events << Event.new(:name => "Lesson #{i}", :start_date => Date.today) | 43 | + profile.events << Event.new(:name => "Lesson #{i}", :start_date => DateTime.now) |
44 | end | 44 | end |
45 | get :events, :profile => profile.identifier | 45 | get :events, :profile => profile.identifier |
46 | assert_equal 20, assigns(:events).size | 46 | assert_equal 20, assigns(:events).size |
47 | end | 47 | end |
48 | 48 | ||
49 | should 'show events of specific day' do | 49 | should 'show events of specific day' do |
50 | - profile.events << Event.new(:name => 'Joao Birthday', :start_date => Date.new(2009, 10, 28)) | 50 | + profile.events << Event.new(:name => 'Joao Birthday', :start_date => DateTime.new(2009, 10, 28)) |
51 | 51 | ||
52 | get :events_by_day, :profile => profile.identifier, :year => 2009, :month => 10, :day => 28 | 52 | get :events_by_day, :profile => profile.identifier, :year => 2009, :month => 10, :day => 28 |
53 | 53 |
test/functional/search_controller_test.rb
@@ -302,7 +302,7 @@ class SearchControllerTest < ActionController::TestCase | @@ -302,7 +302,7 @@ class SearchControllerTest < ActionController::TestCase | ||
302 | 302 | ||
303 | should 'search for events' do | 303 | should 'search for events' do |
304 | person = create_user('teste').person | 304 | person = create_user('teste').person |
305 | - event = create_event(person, :name => 'an event to be found', :start_date => Date.today) | 305 | + event = create_event(person, :name => 'an event to be found', :start_date => DateTime.now) |
306 | 306 | ||
307 | get :events, :query => 'event to be found' | 307 | get :events, :query => 'event to be found' |
308 | 308 | ||
@@ -311,10 +311,10 @@ class SearchControllerTest < ActionController::TestCase | @@ -311,10 +311,10 @@ class SearchControllerTest < ActionController::TestCase | ||
311 | 311 | ||
312 | should 'return events of the day' do | 312 | should 'return events of the day' do |
313 | person = create_user('someone').person | 313 | person = create_user('someone').person |
314 | - ten_days_ago = Date.today - 10.day | 314 | + ten_days_ago = DateTime.now - 10.day |
315 | 315 | ||
316 | ev1 = create_event(person, :name => 'event 1', :category_ids => [@category.id], :start_date => ten_days_ago) | 316 | ev1 = create_event(person, :name => 'event 1', :category_ids => [@category.id], :start_date => ten_days_ago) |
317 | - ev2 = create_event(person, :name => 'event 2', :category_ids => [@category.id], :start_date => Date.today - 2.month) | 317 | + ev2 = create_event(person, :name => 'event 2', :category_ids => [@category.id], :start_date => DateTime.now - 2.month) |
318 | 318 | ||
319 | get :events, :day => ten_days_ago.day, :month => ten_days_ago.month, :year => ten_days_ago.year | 319 | get :events, :day => ten_days_ago.day, :month => ten_days_ago.month, :year => ten_days_ago.year |
320 | assert_equal [ev1], assigns(:events) | 320 | assert_equal [ev1], assigns(:events) |
@@ -322,9 +322,11 @@ class SearchControllerTest < ActionController::TestCase | @@ -322,9 +322,11 @@ class SearchControllerTest < ActionController::TestCase | ||
322 | 322 | ||
323 | should 'return events of the day with category' do | 323 | should 'return events of the day with category' do |
324 | person = create_user('someone').person | 324 | person = create_user('someone').person |
325 | - ten_days_ago = Date.today - 10.day | 325 | + ten_days_ago = DateTime.now - 10.day |
326 | 326 | ||
327 | - ev1 = create_event(person, :name => 'event 1', :category_ids => [@category.id], :start_date => ten_days_ago) | 327 | + ev1 = create_event(person, :name => 'event 1', :start_date => ten_days_ago) |
328 | + ev1.categories = [@category] | ||
329 | + | ||
328 | ev2 = create_event(person, :name => 'event 2', :start_date => ten_days_ago) | 330 | ev2 = create_event(person, :name => 'event 2', :start_date => ten_days_ago) |
329 | 331 | ||
330 | get :events, :day => ten_days_ago.day, :month => ten_days_ago.month, :year => ten_days_ago.year, :category_path => @category.path.split('/') | 332 | get :events, :day => ten_days_ago.day, :month => ten_days_ago.month, :year => ten_days_ago.year, :category_path => @category.path.split('/') |
@@ -334,8 +336,8 @@ class SearchControllerTest < ActionController::TestCase | @@ -334,8 +336,8 @@ class SearchControllerTest < ActionController::TestCase | ||
334 | 336 | ||
335 | should 'return events of today when no date specified' do | 337 | should 'return events of today when no date specified' do |
336 | person = create_user('someone').person | 338 | person = create_user('someone').person |
337 | - ev1 = create_event(person, :name => 'event 1', :category_ids => [@category.id], :start_date => Date.today) | ||
338 | - ev2 = create_event(person, :name => 'event 2', :category_ids => [@category.id], :start_date => Date.today - 2.month) | 339 | + ev1 = create_event(person, :name => 'event 1', :category_ids => [@category.id], :start_date => DateTime.now) |
340 | + ev2 = create_event(person, :name => 'event 2', :category_ids => [@category.id], :start_date => DateTime.now - 2.month) | ||
339 | 341 | ||
340 | get :events | 342 | get :events |
341 | 343 | ||
@@ -346,9 +348,9 @@ class SearchControllerTest < ActionController::TestCase | @@ -346,9 +348,9 @@ class SearchControllerTest < ActionController::TestCase | ||
346 | person = create_user('someone').person | 348 | person = create_user('someone').person |
347 | 349 | ||
348 | ev1 = create_event(person, :name => 'event 1', :category_ids => [@category.id], | 350 | ev1 = create_event(person, :name => 'event 1', :category_ids => [@category.id], |
349 | - :start_date => Date.today + 2.month) | 351 | + :start_date => DateTime.now + 2.month) |
350 | ev2 = create_event(person, :name => 'event 2', :category_ids => [@category.id], | 352 | ev2 = create_event(person, :name => 'event 2', :category_ids => [@category.id], |
351 | - :start_date => Date.today + 2.day) | 353 | + :start_date => DateTime.now + 2.day) |
352 | 354 | ||
353 | get :events | 355 | get :events |
354 | 356 | ||
@@ -359,8 +361,8 @@ class SearchControllerTest < ActionController::TestCase | @@ -359,8 +361,8 @@ class SearchControllerTest < ActionController::TestCase | ||
359 | should 'list events for a given month' do | 361 | should 'list events for a given month' do |
360 | person = create_user('testuser').person | 362 | person = create_user('testuser').person |
361 | 363 | ||
362 | - create_event(person, :name => 'upcoming event 1', :category_ids => [@category.id], :start_date => Date.new(2008, 1, 25)) | ||
363 | - create_event(person, :name => 'upcoming event 2', :category_ids => [@category.id], :start_date => Date.new(2008, 4, 27)) | 364 | + create_event(person, :name => 'upcoming event 1', :category_ids => [@category.id], :start_date => DateTime.new(2008, 1, 25)) |
365 | + create_event(person, :name => 'upcoming event 2', :category_ids => [@category.id], :start_date => DateTime.new(2008, 4, 27)) | ||
364 | 366 | ||
365 | get :events, :year => '2008', :month => '1' | 367 | get :events, :year => '2008', :month => '1' |
366 | 368 | ||
@@ -370,7 +372,7 @@ class SearchControllerTest < ActionController::TestCase | @@ -370,7 +372,7 @@ class SearchControllerTest < ActionController::TestCase | ||
370 | should 'see the events paginated' do | 372 | should 'see the events paginated' do |
371 | person = create_user('testuser').person | 373 | person = create_user('testuser').person |
372 | 30.times do |i| | 374 | 30.times do |i| |
373 | - create_event(person, :name => "Event #{i}", :start_date => Date.today) | 375 | + create_event(person, :name => "Event #{i}", :start_date => DateTime.now) |
374 | end | 376 | end |
375 | get :events | 377 | get :events |
376 | assert_equal 20, assigns(:events).size | 378 | assert_equal 20, assigns(:events).size |
@@ -413,7 +415,7 @@ class SearchControllerTest < ActionController::TestCase | @@ -413,7 +415,7 @@ class SearchControllerTest < ActionController::TestCase | ||
413 | end | 415 | end |
414 | 416 | ||
415 | should 'display current year/month by default as caption of current month' do | 417 | should 'display current year/month by default as caption of current month' do |
416 | - Date.expects(:today).returns(Date.new(2008, 8, 1)).at_least_once | 418 | + DateTime.expects(:now).returns(DateTime.new(2008, 8, 1)).at_least_once |
417 | 419 | ||
418 | get :events | 420 | get :events |
419 | assert_tag :tag => 'table', :attributes => {:class => /current-month/}, :descendant => {:tag => 'caption', :content => /August 2008/} | 421 | assert_tag :tag => 'table', :attributes => {:class => /current-month/}, :descendant => {:tag => 'caption', :content => /August 2008/} |
@@ -472,7 +474,7 @@ class SearchControllerTest < ActionController::TestCase | @@ -472,7 +474,7 @@ class SearchControllerTest < ActionController::TestCase | ||
472 | 474 | ||
473 | should 'show events of specific day' do | 475 | should 'show events of specific day' do |
474 | person = create_user('anotheruser').person | 476 | person = create_user('anotheruser').person |
475 | - event = create_event(person, :name => 'Joao Birthday', :start_date => Date.new(2009, 10, 28)) | 477 | + event = create_event(person, :name => 'Joao Birthday', :start_date => DateTime.new(2009, 10, 28)) |
476 | 478 | ||
477 | get :events_by_day, :year => 2009, :month => 10, :day => 28 | 479 | get :events_by_day, :year => 2009, :month => 10, :day => 28 |
478 | 480 | ||
@@ -481,8 +483,8 @@ class SearchControllerTest < ActionController::TestCase | @@ -481,8 +483,8 @@ class SearchControllerTest < ActionController::TestCase | ||
481 | 483 | ||
482 | should 'ignore filter of events if category not exists' do | 484 | should 'ignore filter of events if category not exists' do |
483 | person = create_user('anotheruser').person | 485 | person = create_user('anotheruser').person |
484 | - create_event(person, :name => 'Joao Birthday', :start_date => Date.new(2009, 10, 28), :category_ids => [@category.id]) | ||
485 | - create_event(person, :name => 'Maria Birthday', :start_date => Date.new(2009, 10, 28)) | 486 | + create_event(person, :name => 'Joao Birthday', :start_date => DateTime.new(2009, 10, 28), :category_ids => [@category.id]) |
487 | + create_event(person, :name => 'Maria Birthday', :start_date => DateTime.new(2009, 10, 28)) | ||
486 | 488 | ||
487 | id_of_unexistent_category = Category.last.id + 10 | 489 | id_of_unexistent_category = Category.last.id + 10 |
488 | 490 | ||
@@ -769,7 +771,7 @@ class SearchControllerTest < ActionController::TestCase | @@ -769,7 +771,7 @@ class SearchControllerTest < ActionController::TestCase | ||
769 | protected | 771 | protected |
770 | 772 | ||
771 | def create_event(profile, options) | 773 | def create_event(profile, options) |
772 | - ev = build(Event, { :name => 'some event', :start_date => Date.new(2008,1,1) }.merge(options)) | 774 | + ev = build(Event, { :name => 'some event', :start_date => DateTime.new(2008,1,1) }.merge(options)) |
773 | ev.profile = profile | 775 | ev.profile = profile |
774 | ev.save! | 776 | ev.save! |
775 | ev | 777 | ev |
test/unit/application_helper_test.rb
@@ -1022,6 +1022,27 @@ class ApplicationHelperTest < ActionView::TestCase | @@ -1022,6 +1022,27 @@ class ApplicationHelperTest < ActionView::TestCase | ||
1022 | assert_equal "Clone Article", label_for_clone_article(TinyMceArticle.new) | 1022 | assert_equal "Clone Article", label_for_clone_article(TinyMceArticle.new) |
1023 | end | 1023 | end |
1024 | 1024 | ||
1025 | + should "return top url of environment" do | ||
1026 | + env = Environment.default | ||
1027 | + request = mock() | ||
1028 | + request.expects(:scheme).returns('http') | ||
1029 | + stubs(:request).returns(request) | ||
1030 | + stubs(:environment).returns(env) | ||
1031 | + stubs(:profile).returns(nil) | ||
1032 | + assert_equal env.top_url('http'), top_url | ||
1033 | + end | ||
1034 | + | ||
1035 | + should "return top url considering profile" do | ||
1036 | + env = Environment.default | ||
1037 | + c = fast_create(Community) | ||
1038 | + request = mock() | ||
1039 | + request.stubs(:scheme).returns('http') | ||
1040 | + stubs(:request).returns(request) | ||
1041 | + stubs(:environment).returns(env) | ||
1042 | + stubs(:profile).returns(c) | ||
1043 | + assert_equal c.top_url, top_url | ||
1044 | + end | ||
1045 | + | ||
1025 | protected | 1046 | protected |
1026 | include NoosferoTestHelper | 1047 | include NoosferoTestHelper |
1027 | 1048 |
test/unit/article_test.rb
@@ -1497,6 +1497,17 @@ class ArticleTest < ActiveSupport::TestCase | @@ -1497,6 +1497,17 @@ class ArticleTest < ActiveSupport::TestCase | ||
1497 | assert_includes a.body_images_paths, 'http://test.com/noosfero.png' | 1497 | assert_includes a.body_images_paths, 'http://test.com/noosfero.png' |
1498 | end | 1498 | end |
1499 | 1499 | ||
1500 | + should 'escape utf8 characters correctly' do | ||
1501 | + Environment.any_instance.stubs(:default_hostname).returns('noosfero.org') | ||
1502 | + a = build TinyMceArticle, profile: @profile | ||
1503 | + a.body = 'Noosfero <img src="http://noosfero.com/cabeça.png" /> ' | ||
1504 | + assert_includes a.body_images_paths, 'http://noosfero.com/cabe%C3%A7a.png' | ||
1505 | + | ||
1506 | + # check if after save (that is, after xss_terminate run) | ||
1507 | + a.save! | ||
1508 | + assert_includes a.body_images_paths, 'http://noosfero.com/cabe%C3%A7a.png' | ||
1509 | + end | ||
1510 | + | ||
1500 | should 'get absolute images paths in article body' do | 1511 | should 'get absolute images paths in article body' do |
1501 | Environment.any_instance.stubs(:default_hostname).returns('noosfero.org') | 1512 | Environment.any_instance.stubs(:default_hostname).returns('noosfero.org') |
1502 | a = build TinyMceArticle, :profile => @profile | 1513 | a = build TinyMceArticle, :profile => @profile |
@@ -2181,4 +2192,32 @@ class ArticleTest < ActiveSupport::TestCase | @@ -2181,4 +2192,32 @@ class ArticleTest < ActiveSupport::TestCase | ||
2181 | article.destroy | 2192 | article.destroy |
2182 | end | 2193 | end |
2183 | 2194 | ||
2195 | + should 'have can_display_media_panel with default false' do | ||
2196 | + a = Article.new | ||
2197 | + assert !a.can_display_media_panel? | ||
2198 | + end | ||
2199 | + | ||
2200 | + should 'display media panel when allowed by the environment' do | ||
2201 | + a = Article.new | ||
2202 | + a.expects(:can_display_media_panel?).returns(true) | ||
2203 | + environment = mock | ||
2204 | + a.expects(:environment).returns(environment) | ||
2205 | + environment.expects(:enabled?).with('media_panel').returns(true) | ||
2206 | + assert a.display_media_panel? | ||
2207 | + end | ||
2208 | + | ||
2209 | + should 'not display media panel when not allowed by the environment' do | ||
2210 | + a = Article.new | ||
2211 | + a.expects(:can_display_media_panel?).returns(true) | ||
2212 | + environment = mock | ||
2213 | + a.expects(:environment).returns(environment) | ||
2214 | + environment.expects(:enabled?).with('media_panel').returns(false) | ||
2215 | + assert !a.display_media_panel? | ||
2216 | + end | ||
2217 | + | ||
2218 | + should 'have display_preview' do | ||
2219 | + a = Article.new(:display_preview => false) | ||
2220 | + assert !a.display_preview? | ||
2221 | + end | ||
2222 | + | ||
2184 | end | 2223 | end |
test/unit/change_password_test.rb
@@ -29,7 +29,8 @@ class ChangePasswordTest < ActiveSupport::TestCase | @@ -29,7 +29,8 @@ class ChangePasswordTest < ActiveSupport::TestCase | ||
29 | change.password_confirmation = 'newpass' | 29 | change.password_confirmation = 'newpass' |
30 | change.finish | 30 | change.finish |
31 | 31 | ||
32 | - assert User.find(person.user.id).authenticated?('newpass') | 32 | + person.user.activate |
33 | + assert person.user.authenticated?('newpass') | ||
33 | end | 34 | end |
34 | 35 | ||
35 | should 'not require password and password confirmation when cancelling' do | 36 | should 'not require password and password confirmation when cancelling' do |
test/unit/content_viewer_helper_test.rb
@@ -16,14 +16,14 @@ class ContentViewerHelperTest < ActionView::TestCase | @@ -16,14 +16,14 @@ class ContentViewerHelperTest < ActionView::TestCase | ||
16 | blog = fast_create(Blog, :name => 'Blog test', :profile_id => profile.id) | 16 | blog = fast_create(Blog, :name => 'Blog test', :profile_id => profile.id) |
17 | post = create(TextileArticle, :name => 'post test', :profile => profile, :parent => blog) | 17 | post = create(TextileArticle, :name => 'post test', :profile => profile, :parent => blog) |
18 | result = article_title(post) | 18 | result = article_title(post) |
19 | - assert_tag_in_string result, :tag => 'span', :content => show_date(post.published_at) | 19 | + assert_tag_in_string result, :tag => 'span', :content => show_time(post.published_at) |
20 | end | 20 | end |
21 | 21 | ||
22 | should 'display published-at for forum posts' do | 22 | should 'display published-at for forum posts' do |
23 | forum = fast_create(Forum, :name => 'Forum test', :profile_id => profile.id) | 23 | forum = fast_create(Forum, :name => 'Forum test', :profile_id => profile.id) |
24 | post = TextileArticle.create!(:name => 'post test', :profile => profile, :parent => forum) | 24 | post = TextileArticle.create!(:name => 'post test', :profile => profile, :parent => forum) |
25 | result = article_title(post) | 25 | result = article_title(post) |
26 | - assert_tag_in_string result, :tag => 'span', :content => show_date(post.published_at) | 26 | + assert_tag_in_string result, :tag => 'span', :content => show_time(post.published_at) |
27 | end | 27 | end |
28 | 28 | ||
29 | should 'not display published-at for non-blog and non-forum posts' do | 29 | should 'not display published-at for non-blog and non-forum posts' do |
test/unit/dates_helper_test.rb
@@ -21,23 +21,23 @@ class DatesHelperTest < ActiveSupport::TestCase | @@ -21,23 +21,23 @@ class DatesHelperTest < ActiveSupport::TestCase | ||
21 | should 'generate period with two dates' do | 21 | should 'generate period with two dates' do |
22 | date1 = mock | 22 | date1 = mock |
23 | date1.stubs(:year).returns('A') | 23 | date1.stubs(:year).returns('A') |
24 | - expects(:show_date).with(date1, anything).returns('XXX') | 24 | + expects(:show_time).with(date1, anything).returns('XXX') |
25 | date2 = mock | 25 | date2 = mock |
26 | date2.stubs(:year).returns('B') | 26 | date2.stubs(:year).returns('B') |
27 | - expects(:show_date).with(date2, anything).returns('YYY') | 27 | + expects(:show_time).with(date2, anything).returns('YYY') |
28 | expects(:_).with('from %{date1} to %{date2}').returns('from %{date1} to %{date2}') | 28 | expects(:_).with('from %{date1} to %{date2}').returns('from %{date1} to %{date2}') |
29 | assert_equal 'from XXX to YYY', show_period(date1, date2) | 29 | assert_equal 'from XXX to YYY', show_period(date1, date2) |
30 | end | 30 | end |
31 | 31 | ||
32 | should 'generate period with in two diferent years' do | 32 | should 'generate period with in two diferent years' do |
33 | - date1 = Date.new(1920, 1, 2) | ||
34 | - date2 = Date.new(1992, 4, 6) | ||
35 | - assert_equal 'from January 2, 1920 to April 6, 1992', show_period(date1, date2) | 33 | + date1 = DateTime.new(1920, 1, 2) |
34 | + date2 = DateTime.new(1992, 4, 6) | ||
35 | + assert_equal 'from January 2, 1920 0:00 to April 6, 1992 0:00', show_period(date1, date2) | ||
36 | end | 36 | end |
37 | 37 | ||
38 | should 'generate period with in two diferent months of the same year' do | 38 | should 'generate period with in two diferent months of the same year' do |
39 | - date1 = Date.new(2013, 2, 1) | ||
40 | - date2 = Date.new(2013, 3, 1) | 39 | + date1 = DateTime.new(2013, 2, 1) |
40 | + date2 = DateTime.new(2013, 3, 1) | ||
41 | assert_equal 'from February 1 to March 1, 2013', show_period(date1, date2) | 41 | assert_equal 'from February 1 to March 1, 2013', show_period(date1, date2) |
42 | end | 42 | end |
43 | 43 | ||
@@ -49,13 +49,13 @@ class DatesHelperTest < ActiveSupport::TestCase | @@ -49,13 +49,13 @@ class DatesHelperTest < ActiveSupport::TestCase | ||
49 | 49 | ||
50 | should 'generate period with two equal dates' do | 50 | should 'generate period with two equal dates' do |
51 | date1 = mock | 51 | date1 = mock |
52 | - expects(:show_date).with(date1, anything).returns('XXX') | 52 | + expects(:show_time).with(date1, anything).returns('XXX') |
53 | assert_equal 'XXX', show_period(date1, date1) | 53 | assert_equal 'XXX', show_period(date1, date1) |
54 | end | 54 | end |
55 | 55 | ||
56 | should 'generate period with one date only' do | 56 | should 'generate period with one date only' do |
57 | date1 = mock | 57 | date1 = mock |
58 | - expects(:show_date).with(date1, anything).returns('XXX') | 58 | + expects(:show_time).with(date1, anything).returns('XXX') |
59 | assert_equal 'XXX', show_period(date1) | 59 | assert_equal 'XXX', show_period(date1) |
60 | end | 60 | end |
61 | 61 | ||
@@ -84,7 +84,7 @@ class DatesHelperTest < ActiveSupport::TestCase | @@ -84,7 +84,7 @@ class DatesHelperTest < ActiveSupport::TestCase | ||
84 | end | 84 | end |
85 | 85 | ||
86 | should 'fallback to current year/month in show_month' do | 86 | should 'fallback to current year/month in show_month' do |
87 | - Date.expects(:today).returns(Date.new(2008,11,1)).at_least_once | 87 | + DateTime.expects(:now).returns(DateTime.new(2008,11,1)).at_least_once |
88 | assert_equal 'November 2008', show_month(nil, nil) | 88 | assert_equal 'November 2008', show_month(nil, nil) |
89 | assert_equal 'November 2008', show_month('', '') | 89 | assert_equal 'November 2008', show_month('', '') |
90 | end | 90 | end |
@@ -118,16 +118,16 @@ class DatesHelperTest < ActiveSupport::TestCase | @@ -118,16 +118,16 @@ class DatesHelperTest < ActiveSupport::TestCase | ||
118 | end | 118 | end |
119 | 119 | ||
120 | should 'format time' do | 120 | should 'format time' do |
121 | - assert_equal '22 November 2008, 15:34', show_time(Time.mktime(2008, 11, 22, 15, 34, 0, 0)) | 121 | + assert_equal 'November 22, 2008 15:34', show_time(Time.mktime(2008, 11, 22, 15, 34, 0, 0)) |
122 | end | 122 | end |
123 | 123 | ||
124 | should 'format time with 2 digits minutes' do | 124 | should 'format time with 2 digits minutes' do |
125 | - assert_equal '22 November 2008, 15:04', show_time(Time.mktime(2008, 11, 22, 15, 04, 0, 0)) | 125 | + assert_equal 'November 22, 2008 15:04', show_time(Time.mktime(2008, 11, 22, 15, 04, 0, 0)) |
126 | end | 126 | end |
127 | 127 | ||
128 | should 'translate time' do | 128 | should 'translate time' do |
129 | time = Time.parse('25 May 2009, 12:47') | 129 | time = Time.parse('25 May 2009, 12:47') |
130 | - assert_equal '25 May 2009, 12:47', show_time(time) | 130 | + assert_equal 'May 25, 2009 12:47', show_time(time) |
131 | end | 131 | end |
132 | 132 | ||
133 | should 'handle nil time' do | 133 | should 'handle nil time' do |
test/unit/enterprise_homepage_test.rb
@@ -26,4 +26,9 @@ class EnterpriseHomepageTest < ActiveSupport::TestCase | @@ -26,4 +26,9 @@ class EnterpriseHomepageTest < ActiveSupport::TestCase | ||
26 | assert_equal false, a.can_display_hits? | 26 | assert_equal false, a.can_display_hits? |
27 | end | 27 | end |
28 | 28 | ||
29 | + should 'have can_display_media_panel with default true' do | ||
30 | + a = EnterpriseHomepage.new | ||
31 | + assert a.can_display_media_panel? | ||
32 | + end | ||
33 | + | ||
29 | end | 34 | end |
test/unit/event_test.rb
@@ -29,15 +29,9 @@ class EventTest < ActiveSupport::TestCase | @@ -29,15 +29,9 @@ class EventTest < ActiveSupport::TestCase | ||
29 | assert_equal 'South Noosfero street, 88', e.address | 29 | assert_equal 'South Noosfero street, 88', e.address |
30 | end | 30 | end |
31 | 31 | ||
32 | - should 'have a start date' do | ||
33 | - e = Event.new | ||
34 | - e.start_date = Date.today | ||
35 | - assert_kind_of Date, e.start_date | ||
36 | - end | ||
37 | - | ||
38 | should 'set start date default value as today' do | 32 | should 'set start date default value as today' do |
39 | e = Event.new | 33 | e = Event.new |
40 | - assert_equal Date.today, e.start_date | 34 | + assert_in_delta DateTime.now.to_i, e.start_date.to_i, 1 |
41 | end | 35 | end |
42 | 36 | ||
43 | should 'require start date' do | 37 | should 'require start date' do |
@@ -45,38 +39,32 @@ class EventTest < ActiveSupport::TestCase | @@ -45,38 +39,32 @@ class EventTest < ActiveSupport::TestCase | ||
45 | e.start_date = nil | 39 | e.start_date = nil |
46 | e.valid? | 40 | e.valid? |
47 | assert e.errors[:start_date.to_s].present? | 41 | assert e.errors[:start_date.to_s].present? |
48 | - e.start_date = Date.today | 42 | + e.start_date = DateTime.now |
49 | e.valid? | 43 | e.valid? |
50 | refute e.errors[:start_date.to_s].present? | 44 | refute e.errors[:start_date.to_s].present? |
51 | end | 45 | end |
52 | 46 | ||
53 | - should 'have a end date' do | ||
54 | - e = Event.new | ||
55 | - e.end_date = Date.today | ||
56 | - assert_kind_of Date, e.end_date | ||
57 | - end | ||
58 | - | ||
59 | should 'use its own icon' do | 47 | should 'use its own icon' do |
60 | assert_equal 'event', Event.icon_name | 48 | assert_equal 'event', Event.icon_name |
61 | end | 49 | end |
62 | 50 | ||
63 | should 'not allow end date before start date' do | 51 | should 'not allow end date before start date' do |
64 | - e = build(Event, :start_date => Date.new(2008, 01, 01), :end_date => Date.new(2007,01,01)) | 52 | + e = build(Event, :start_date => DateTime.new(2008, 01, 01), :end_date => DateTime.new(2007,01,01)) |
65 | e.valid? | 53 | e.valid? |
66 | assert e.errors[:start_date.to_s].present? | 54 | assert e.errors[:start_date.to_s].present? |
67 | 55 | ||
68 | - e.end_date = Date.new(2008,01,05) | 56 | + e.end_date = DateTime.new(2008,01,05) |
69 | e.valid? | 57 | e.valid? |
70 | refute e.errors[:start_date.to_s].present? | 58 | refute e.errors[:start_date.to_s].present? |
71 | end | 59 | end |
72 | 60 | ||
73 | should 'find by range of dates' do | 61 | should 'find by range of dates' do |
74 | profile = create_user('testuser').person | 62 | profile = create_user('testuser').person |
75 | - e1 = create(Event, :name => 'e1', :start_date => Date.new(2008,1,1), :profile => profile) | ||
76 | - e2 = create(Event, :name => 'e2', :start_date => Date.new(2008,2,1), :profile => profile) | ||
77 | - e3 = create(Event, :name => 'e3', :start_date => Date.new(2008,3,1), :profile => profile) | 63 | + e1 = create(Event, :name => 'e1', :start_date => DateTime.new(2008,1,1), :profile => profile) |
64 | + e2 = create(Event, :name => 'e2', :start_date => DateTime.new(2008,2,1), :profile => profile) | ||
65 | + e3 = create(Event, :name => 'e3', :start_date => DateTime.new(2008,3,1), :profile => profile) | ||
78 | 66 | ||
79 | - found = Event.by_range(Date.new(2008, 1, 1)..Date.new(2008, 2, 28)) | 67 | + found = Event.by_range(DateTime.new(2008, 1, 1)..DateTime.new(2008, 2, 28)) |
80 | assert_includes found, e1 | 68 | assert_includes found, e1 |
81 | assert_includes found, e2 | 69 | assert_includes found, e2 |
82 | assert_not_includes found, e3 | 70 | assert_not_includes found, e3 |
@@ -84,32 +72,33 @@ class EventTest < ActiveSupport::TestCase | @@ -84,32 +72,33 @@ class EventTest < ActiveSupport::TestCase | ||
84 | 72 | ||
85 | should 'filter events by range' do | 73 | should 'filter events by range' do |
86 | profile = create_user('testuser').person | 74 | profile = create_user('testuser').person |
87 | - e1 = create(Event, :name => 'e1', :start_date => Date.new(2008,1,15), :profile => profile) | ||
88 | - assert_includes profile.events.by_range(Date.new(2008, 1, 10)..Date.new(2008, 1, 20)), e1 | 75 | + e1 = create(Event, :name => 'e1', :start_date => DateTime.new(2008,1,15), :profile => profile) |
76 | + assert_includes profile.events.by_range(DateTime.new(2008, 1, 10)..DateTime.new(2008, 1, 20)), e1 | ||
89 | end | 77 | end |
90 | 78 | ||
91 | should 'provide period for searching in month' do | 79 | should 'provide period for searching in month' do |
92 | - assert_equal Date.new(2008, 1, 1)..Date.new(2008,1,31), Event.date_range(2008, 1) | ||
93 | - assert_equal Date.new(2008, 2, 1)..Date.new(2008,2,29), Event.date_range(2008, 2) | ||
94 | - assert_equal Date.new(2007, 2, 1)..Date.new(2007,2,28), Event.date_range(2007, 2) | 80 | + assert_equal DateTime.new(2008, 1, 1)..DateTime.new(2008,1,31), Event.date_range(2008, 1) |
81 | + assert_equal DateTime.new(2008, 2, 1)..DateTime.new(2008,2,29), Event.date_range(2008, 2) | ||
82 | + assert_equal DateTime.new(2007, 2, 1)..DateTime.new(2007,2,28), Event.date_range(2007, 2) | ||
95 | end | 83 | end |
96 | 84 | ||
97 | should 'support string arguments to Event#date_range' do | 85 | should 'support string arguments to Event#date_range' do |
98 | - assert_equal Date.new(2008,1,1)..Date.new(2008,1,31), Event.date_range('2008', '1') | 86 | + assert_equal DateTime.new(2008,1,1)..DateTime.new(2008,1,31), Event.date_range('2008', '1') |
99 | end | 87 | end |
100 | 88 | ||
101 | should 'provide range of dates for event with both dates filled' do | 89 | should 'provide range of dates for event with both dates filled' do |
102 | - e = build(Event, :start_date => Date.new(2008, 1, 1), :end_date => Date.new(2008, 1, 5)) | ||
103 | - assert_equal (Date.new(2008,1,1)..Date.new(2008,1,5)), e.date_range | 90 | + e = build(Event, :start_date => DateTime.new(2008, 1, 1), :end_date => DateTime.new(2008, 1, 5)) |
91 | + assert_equal (DateTime.new(2008,1,1)..DateTime.new(2008,1,5)), e.date_range | ||
104 | end | 92 | end |
105 | 93 | ||
106 | should 'provide range of dates for event with only start date' do | 94 | should 'provide range of dates for event with only start date' do |
107 | - e = build(Event, :start_date => Date.new(2008, 1, 1)) | ||
108 | - assert_equal (Date.new(2008,1,1)..Date.new(2008,1,1)), e.date_range | 95 | + e = build(Event, :start_date => DateTime.new(2008, 1, 1)) |
96 | + assert_equal (DateTime.new(2008,1,1)..DateTime.new(2008,1,1)), e.date_range | ||
109 | end | 97 | end |
110 | 98 | ||
111 | should 'provide nice display format' do | 99 | should 'provide nice display format' do |
112 | - event = build(Event, :start_date => Date.new(2008,1,1), :end_date => Date.new(2008,1,1), :link => 'http://www.myevent.org', :body => '<p>my somewhat short description</p>') | 100 | + date = Time.zone.local(2008, 1, 1, 0, 0, 0) |
101 | + event = build(Event, :start_date => date, :end_date => date, :link => 'http://www.myevent.org', :body => '<p>my somewhat short description</p>') | ||
113 | display = instance_eval(&event.to_html) | 102 | display = instance_eval(&event.to_html) |
114 | 103 | ||
115 | assert_tag_in_string display, :content => Regexp.new("January 1, 2008") | 104 | assert_tag_in_string display, :content => Regexp.new("January 1, 2008") |
@@ -148,7 +137,7 @@ class EventTest < ActiveSupport::TestCase | @@ -148,7 +137,7 @@ class EventTest < ActiveSupport::TestCase | ||
148 | profile = create_user('testuser').person | 137 | profile = create_user('testuser').person |
149 | event = create(Event, :profile => profile, :name => 'test', | 138 | event = create(Event, :profile => profile, :name => 'test', |
150 | :body => '<p>first paragraph </p><p>second paragraph </p>', | 139 | :body => '<p>first paragraph </p><p>second paragraph </p>', |
151 | - :link => 'www.colivre.coop.br', :start_date => Date.today) | 140 | + :link => 'www.colivre.coop.br', :start_date => DateTime.now) |
152 | 141 | ||
153 | assert_match '<p>first paragraph </p>', event.first_paragraph | 142 | assert_match '<p>first paragraph </p>', event.first_paragraph |
154 | end | 143 | end |
@@ -161,7 +150,7 @@ class EventTest < ActiveSupport::TestCase | @@ -161,7 +150,7 @@ class EventTest < ActiveSupport::TestCase | ||
161 | 150 | ||
162 | should 'filter HTML in body' do | 151 | should 'filter HTML in body' do |
163 | profile = create_user('testuser').person | 152 | profile = create_user('testuser').person |
164 | - e = create(Event, :profile => profile, :name => 'test', :body => '<p>a paragraph (valid)</p><script type="text/javascript">/* this is invalid */</script>"', :link => 'www.colivre.coop.br', :start_date => Date.today) | 153 | + e = create(Event, :profile => profile, :name => 'test', :body => '<p>a paragraph (valid)</p><script type="text/javascript">/* this is invalid */</script>"', :link => 'www.colivre.coop.br', :start_date => DateTime.now) |
165 | 154 | ||
166 | assert_tag_in_string e.body, :tag => 'p', :content => 'a paragraph (valid)' | 155 | assert_tag_in_string e.body, :tag => 'p', :content => 'a paragraph (valid)' |
167 | assert_no_tag_in_string e.body, :tag => 'script' | 156 | assert_no_tag_in_string e.body, :tag => 'script' |
@@ -169,7 +158,7 @@ class EventTest < ActiveSupport::TestCase | @@ -169,7 +158,7 @@ class EventTest < ActiveSupport::TestCase | ||
169 | 158 | ||
170 | should 'filter HTML in name' do | 159 | should 'filter HTML in name' do |
171 | profile = create_user('testuser').person | 160 | profile = create_user('testuser').person |
172 | - e = create(Event, :profile => profile, :name => '<p>a paragraph (valid)</p><script type="text/javascript">/* this is invalid */</script>"', :link => 'www.colivre.coop.br', :start_date => Date.today) | 161 | + e = create(Event, :profile => profile, :name => '<p>a paragraph (valid)</p><script type="text/javascript">/* this is invalid */</script>"', :link => 'www.colivre.coop.br', :start_date => DateTime.now) |
173 | 162 | ||
174 | assert_tag_in_string e.name, :tag => 'p', :content => 'a paragraph (valid)' | 163 | assert_tag_in_string e.name, :tag => 'p', :content => 'a paragraph (valid)' |
175 | assert_no_tag_in_string e.name, :tag => 'script' | 164 | assert_no_tag_in_string e.name, :tag => 'script' |
@@ -184,8 +173,8 @@ class EventTest < ActiveSupport::TestCase | @@ -184,8 +173,8 @@ class EventTest < ActiveSupport::TestCase | ||
184 | 173 | ||
185 | should 'list all events' do | 174 | should 'list all events' do |
186 | profile = fast_create(Profile) | 175 | profile = fast_create(Profile) |
187 | - event1 = build(Event, :name => 'Ze Birthday', :start_date => Date.today) | ||
188 | - event2 = build(Event, :name => 'Mane Birthday', :start_date => Date.today >> 1) | 176 | + event1 = build(Event, :name => 'Ze Birthday', :start_date => DateTime.now) |
177 | + event2 = build(Event, :name => 'Mane Birthday', :start_date => DateTime.now >> 1) | ||
189 | profile.events << [event1, event2] | 178 | profile.events << [event1, event2] |
190 | assert_includes profile.events, event1 | 179 | assert_includes profile.events, event1 |
191 | assert_includes profile.events, event2 | 180 | assert_includes profile.events, event2 |
@@ -194,7 +183,7 @@ class EventTest < ActiveSupport::TestCase | @@ -194,7 +183,7 @@ class EventTest < ActiveSupport::TestCase | ||
194 | should 'list events by day' do | 183 | should 'list events by day' do |
195 | profile = fast_create(Profile) | 184 | profile = fast_create(Profile) |
196 | 185 | ||
197 | - today = Date.today | 186 | + today = DateTime.now |
198 | yesterday_event = build(Event, :name => 'Joao Birthday', :start_date => today - 1.day) | 187 | yesterday_event = build(Event, :name => 'Joao Birthday', :start_date => today - 1.day) |
199 | today_event = build(Event, :name => 'Ze Birthday', :start_date => today) | 188 | today_event = build(Event, :name => 'Ze Birthday', :start_date => today) |
200 | tomorrow_event = build(Event, :name => 'Mane Birthday', :start_date => today + 1.day) | 189 | tomorrow_event = build(Event, :name => 'Mane Birthday', :start_date => today + 1.day) |
@@ -207,7 +196,7 @@ class EventTest < ActiveSupport::TestCase | @@ -207,7 +196,7 @@ class EventTest < ActiveSupport::TestCase | ||
207 | should 'list events by month' do | 196 | should 'list events by month' do |
208 | profile = fast_create(Profile) | 197 | profile = fast_create(Profile) |
209 | 198 | ||
210 | - today = Date.new(2013, 10, 6) | 199 | + today = DateTime.new(2013, 10, 6) |
211 | 200 | ||
212 | last_month_event = Event.new(:name => 'Joao Birthday', :start_date => today - 1.month) | 201 | last_month_event = Event.new(:name => 'Joao Birthday', :start_date => today - 1.month) |
213 | 202 | ||
@@ -230,7 +219,7 @@ class EventTest < ActiveSupport::TestCase | @@ -230,7 +219,7 @@ class EventTest < ActiveSupport::TestCase | ||
230 | should 'event by month ordered by start date'do | 219 | should 'event by month ordered by start date'do |
231 | profile = fast_create(Profile) | 220 | profile = fast_create(Profile) |
232 | 221 | ||
233 | - today = Date.new(2013, 10, 6) | 222 | + today = DateTime.new(2013, 10, 6) |
234 | 223 | ||
235 | event_1 = Event.new(:name => 'Maria Birthday', :start_date => today + 1.day) | 224 | event_1 = Event.new(:name => 'Maria Birthday', :start_date => today + 1.day) |
236 | event_2 = Event.new(:name => 'Joana Birthday', :start_date => today - 1.day) | 225 | event_2 = Event.new(:name => 'Joana Birthday', :start_date => today - 1.day) |
@@ -248,7 +237,7 @@ class EventTest < ActiveSupport::TestCase | @@ -248,7 +237,7 @@ class EventTest < ActiveSupport::TestCase | ||
248 | should 'list events in a range' do | 237 | should 'list events in a range' do |
249 | profile = fast_create(Profile) | 238 | profile = fast_create(Profile) |
250 | 239 | ||
251 | - today = Date.today | 240 | + today = DateTime.now |
252 | event_in_range = build(Event, :name => 'Noosfero Conference', :start_date => today - 2.day, :end_date => today + 2.day) | 241 | event_in_range = build(Event, :name => 'Noosfero Conference', :start_date => today - 2.day, :end_date => today + 2.day) |
253 | event_in_day = build(Event, :name => 'Ze Birthday', :start_date => today) | 242 | event_in_day = build(Event, :name => 'Ze Birthday', :start_date => today) |
254 | 243 | ||
@@ -262,7 +251,7 @@ class EventTest < ActiveSupport::TestCase | @@ -262,7 +251,7 @@ class EventTest < ActiveSupport::TestCase | ||
262 | should 'not list events out of range' do | 251 | should 'not list events out of range' do |
263 | profile = fast_create(Profile) | 252 | profile = fast_create(Profile) |
264 | 253 | ||
265 | - today = Date.today | 254 | + today = DateTime.now |
266 | event_in_range1 = build(Event, :name => 'Foswiki Conference', :start_date => today - 2.day, :end_date => today + 2.day) | 255 | event_in_range1 = build(Event, :name => 'Foswiki Conference', :start_date => today - 2.day, :end_date => today + 2.day) |
267 | event_in_range2 = build(Event, :name => 'Debian Conference', :start_date => today - 2.day, :end_date => today + 3.day) | 256 | event_in_range2 = build(Event, :name => 'Debian Conference', :start_date => today - 2.day, :end_date => today + 3.day) |
268 | event_out_of_range = build(Event, :name => 'Ze Birthday', :start_date => today - 5.day, :end_date => today - 3.day) | 257 | event_out_of_range = build(Event, :name => 'Ze Birthday', :start_date => today - 5.day, :end_date => today - 3.day) |
@@ -357,4 +346,9 @@ class EventTest < ActiveSupport::TestCase | @@ -357,4 +346,9 @@ class EventTest < ActiveSupport::TestCase | ||
357 | assert event.translatable? | 346 | assert event.translatable? |
358 | end | 347 | end |
359 | 348 | ||
349 | + should 'have can_display_media_panel with default true' do | ||
350 | + a = Event.new | ||
351 | + assert a.can_display_media_panel? | ||
352 | + end | ||
353 | + | ||
360 | end | 354 | end |
test/unit/profile_test.rb
@@ -256,6 +256,20 @@ class ProfileTest < ActiveSupport::TestCase | @@ -256,6 +256,20 @@ class ProfileTest < ActiveSupport::TestCase | ||
256 | assert_equal({:host => 'micojones.net', :profile => nil, :controller => 'content_viewer', :action => 'view_page', :page => []}, profile.url) | 256 | assert_equal({:host => 'micojones.net', :profile => nil, :controller => 'content_viewer', :action => 'view_page', :page => []}, profile.url) |
257 | end | 257 | end |
258 | 258 | ||
259 | + should 'provide environment top URL when profile has not a domain' do | ||
260 | + env = Environment.default | ||
261 | + profile = fast_create(Profile, :environment_id => env.id) | ||
262 | + assert_equal env.top_url, profile.top_url | ||
263 | + end | ||
264 | + | ||
265 | + should 'provide top URL to profile with domain' do | ||
266 | + env = Environment.default | ||
267 | + profile = fast_create(Profile, :environment_id => env.id) | ||
268 | + domain = fast_create(Domain, :name => 'example.net') | ||
269 | + profile.domains << domain | ||
270 | + assert_equal 'http://example.net', profile.top_url | ||
271 | + end | ||
272 | + | ||
259 | should 'help developers by adding a suitable port to url' do | 273 | should 'help developers by adding a suitable port to url' do |
260 | profile = build(Profile) | 274 | profile = build(Profile) |
261 | 275 | ||
@@ -1566,8 +1580,8 @@ class ProfileTest < ActiveSupport::TestCase | @@ -1566,8 +1580,8 @@ class ProfileTest < ActiveSupport::TestCase | ||
1566 | 1580 | ||
1567 | should 'list all events' do | 1581 | should 'list all events' do |
1568 | profile = fast_create(Profile) | 1582 | profile = fast_create(Profile) |
1569 | - event1 = Event.new(:name => 'Ze Birthday', :start_date => Date.today) | ||
1570 | - event2 = Event.new(:name => 'Mane Birthday', :start_date => Date.today >> 1) | 1583 | + event1 = Event.new(:name => 'Ze Birthday', :start_date => DateTime.now) |
1584 | + event2 = Event.new(:name => 'Mane Birthday', :start_date => DateTime.now >> 1) | ||
1571 | profile.events << [event1, event2] | 1585 | profile.events << [event1, event2] |
1572 | assert_includes profile.events, event1 | 1586 | assert_includes profile.events, event1 |
1573 | assert_includes profile.events, event2 | 1587 | assert_includes profile.events, event2 |
@@ -1576,7 +1590,7 @@ class ProfileTest < ActiveSupport::TestCase | @@ -1576,7 +1590,7 @@ class ProfileTest < ActiveSupport::TestCase | ||
1576 | should 'list events by day' do | 1590 | should 'list events by day' do |
1577 | profile = fast_create(Profile) | 1591 | profile = fast_create(Profile) |
1578 | 1592 | ||
1579 | - today = Date.today | 1593 | + today = DateTime.now |
1580 | yesterday_event = Event.new(:name => 'Joao Birthday', :start_date => today - 1.day) | 1594 | yesterday_event = Event.new(:name => 'Joao Birthday', :start_date => today - 1.day) |
1581 | today_event = Event.new(:name => 'Ze Birthday', :start_date => today) | 1595 | today_event = Event.new(:name => 'Ze Birthday', :start_date => today) |
1582 | tomorrow_event = Event.new(:name => 'Mane Birthday', :start_date => today + 1.day) | 1596 | tomorrow_event = Event.new(:name => 'Mane Birthday', :start_date => today + 1.day) |
@@ -1589,7 +1603,7 @@ class ProfileTest < ActiveSupport::TestCase | @@ -1589,7 +1603,7 @@ class ProfileTest < ActiveSupport::TestCase | ||
1589 | should 'list events by month' do | 1603 | should 'list events by month' do |
1590 | profile = fast_create(Profile) | 1604 | profile = fast_create(Profile) |
1591 | 1605 | ||
1592 | - today = Date.new(2014, 03, 2) | 1606 | + today = DateTime.new(2014, 03, 2) |
1593 | yesterday_event = Event.new(:name => 'Joao Birthday', :start_date => today - 1.day) | 1607 | yesterday_event = Event.new(:name => 'Joao Birthday', :start_date => today - 1.day) |
1594 | today_event = Event.new(:name => 'Ze Birthday', :start_date => today) | 1608 | today_event = Event.new(:name => 'Ze Birthday', :start_date => today) |
1595 | tomorrow_event = Event.new(:name => 'Mane Birthday', :start_date => today + 1.day) | 1609 | tomorrow_event = Event.new(:name => 'Mane Birthday', :start_date => today + 1.day) |
@@ -1602,7 +1616,7 @@ class ProfileTest < ActiveSupport::TestCase | @@ -1602,7 +1616,7 @@ class ProfileTest < ActiveSupport::TestCase | ||
1602 | should 'list events in a range' do | 1616 | should 'list events in a range' do |
1603 | profile = fast_create(Profile) | 1617 | profile = fast_create(Profile) |
1604 | 1618 | ||
1605 | - today = Date.today | 1619 | + today = DateTime.now |
1606 | event_in_range = Event.new(:name => 'Noosfero Conference', :start_date => today - 2.day, :end_date => today + 2.day) | 1620 | event_in_range = Event.new(:name => 'Noosfero Conference', :start_date => today - 2.day, :end_date => today + 2.day) |
1607 | event_in_day = Event.new(:name => 'Ze Birthday', :start_date => today) | 1621 | event_in_day = Event.new(:name => 'Ze Birthday', :start_date => today) |
1608 | 1622 | ||
@@ -1616,7 +1630,7 @@ class ProfileTest < ActiveSupport::TestCase | @@ -1616,7 +1630,7 @@ class ProfileTest < ActiveSupport::TestCase | ||
1616 | should 'not list events out of range' do | 1630 | should 'not list events out of range' do |
1617 | profile = fast_create(Profile) | 1631 | profile = fast_create(Profile) |
1618 | 1632 | ||
1619 | - today = Date.today | 1633 | + today = DateTime.now |
1620 | event_in_range1 = Event.new(:name => 'Foswiki Conference', :start_date => today - 2.day, :end_date => today + 2.day) | 1634 | event_in_range1 = Event.new(:name => 'Foswiki Conference', :start_date => today - 2.day, :end_date => today + 2.day) |
1621 | event_in_range2 = Event.new(:name => 'Debian Conference', :start_date => today - 2.day, :end_date => today + 3.day) | 1635 | event_in_range2 = Event.new(:name => 'Debian Conference', :start_date => today - 2.day, :end_date => today + 3.day) |
1622 | event_out_of_range = Event.new(:name => 'Ze Birthday', :start_date => today - 5.day, :end_date => today - 3.day) | 1636 | event_out_of_range = Event.new(:name => 'Ze Birthday', :start_date => today - 5.day, :end_date => today - 3.day) |
@@ -1630,9 +1644,9 @@ class ProfileTest < ActiveSupport::TestCase | @@ -1630,9 +1644,9 @@ class ProfileTest < ActiveSupport::TestCase | ||
1630 | 1644 | ||
1631 | should 'sort events by date' do | 1645 | should 'sort events by date' do |
1632 | profile = fast_create(Profile) | 1646 | profile = fast_create(Profile) |
1633 | - event1 = Event.new(:name => 'Noosfero Hackaton', :start_date => Date.today) | ||
1634 | - event2 = Event.new(:name => 'Debian Day', :start_date => Date.today - 1) | ||
1635 | - event3 = Event.new(:name => 'Fisl 10', :start_date => Date.today + 1) | 1647 | + event1 = Event.new(:name => 'Noosfero Hackaton', :start_date => DateTime.now) |
1648 | + event2 = Event.new(:name => 'Debian Day', :start_date => DateTime.now - 1) | ||
1649 | + event3 = Event.new(:name => 'Fisl 10', :start_date => DateTime.now + 1) | ||
1636 | profile.events << [event1, event2, event3] | 1650 | profile.events << [event1, event2, event3] |
1637 | assert_equal [event2, event1, event3], profile.events | 1651 | assert_equal [event2, event1, event3], profile.events |
1638 | end | 1652 | end |
test/unit/text_article_test.rb
@@ -85,6 +85,17 @@ class TextArticleTest < ActiveSupport::TestCase | @@ -85,6 +85,17 @@ class TextArticleTest < ActiveSupport::TestCase | ||
85 | assert_equal "<img src=\"/test.png\">", article.body | 85 | assert_equal "<img src=\"/test.png\">", article.body |
86 | end | 86 | end |
87 | 87 | ||
88 | + should 'change image path to relative for profile with own domain' do | ||
89 | + person = create_user('testuser').person | ||
90 | + person.domains << build(Domain) | ||
91 | + | ||
92 | + article = TextArticle.new(:profile => person, :name => 'test') | ||
93 | + env = Environment.default | ||
94 | + article.body = "<img src=\"http://#{person.default_hostname}:3000/link-profile.png\">" | ||
95 | + article.save! | ||
96 | + assert_equal "<img src=\"/link-profile.png\">", article.body | ||
97 | + end | ||
98 | + | ||
88 | should 'not be translatable if there is no language available on environment' do | 99 | should 'not be translatable if there is no language available on environment' do |
89 | environment = fast_create(Environment) | 100 | environment = fast_create(Environment) |
90 | environment.languages = nil | 101 | environment.languages = nil |
@@ -109,4 +120,12 @@ class TextArticleTest < ActiveSupport::TestCase | @@ -109,4 +120,12 @@ class TextArticleTest < ActiveSupport::TestCase | ||
109 | assert text.translatable? | 120 | assert text.translatable? |
110 | end | 121 | end |
111 | 122 | ||
123 | + should 'display preview when configured on parent that is a blog' do | ||
124 | + person = fast_create(Person) | ||
125 | + post = fast_create(TextArticle, :profile_id => person.id) | ||
126 | + blog = Blog.new(:display_preview => true) | ||
127 | + post.parent = blog | ||
128 | + assert post.display_preview? | ||
129 | + end | ||
130 | + | ||
112 | end | 131 | end |
test/unit/textile_article_test.rb
@@ -174,6 +174,11 @@ class TextileArticleTest < ActiveSupport::TestCase | @@ -174,6 +174,11 @@ class TextileArticleTest < ActiveSupport::TestCase | ||
174 | assert_equal "<p>one\nparagraph</p>", build_article("one\nparagraph").to_html | 174 | assert_equal "<p>one\nparagraph</p>", build_article("one\nparagraph").to_html |
175 | end | 175 | end |
176 | 176 | ||
177 | + should 'have can_display_media_panel with default true' do | ||
178 | + a = TextileArticle.new | ||
179 | + assert a.can_display_media_panel? | ||
180 | + end | ||
181 | + | ||
177 | protected | 182 | protected |
178 | 183 | ||
179 | def build_article(input = nil, options = {}) | 184 | def build_article(input = nil, options = {}) |
test/unit/tiny_mce_article_test.rb
@@ -235,4 +235,9 @@ end | @@ -235,4 +235,9 @@ end | ||
235 | :attributes => { :colspan => '2', :rowspan => '3' } | 235 | :attributes => { :colspan => '2', :rowspan => '3' } |
236 | end | 236 | end |
237 | 237 | ||
238 | + should 'have can_display_media_panel with default true' do | ||
239 | + a = TinyMceArticle.new | ||
240 | + assert a.can_display_media_panel? | ||
241 | + end | ||
242 | + | ||
238 | end | 243 | end |
test/unit/user_test.rb
@@ -123,6 +123,7 @@ class UserTest < ActiveSupport::TestCase | @@ -123,6 +123,7 @@ class UserTest < ActiveSupport::TestCase | ||
123 | 123 | ||
124 | def test_should_change_password | 124 | def test_should_change_password |
125 | user = create_user('changetest', :password => 'test', :password_confirmation => 'test', :email => 'changetest@example.com') | 125 | user = create_user('changetest', :password => 'test', :password_confirmation => 'test', :email => 'changetest@example.com') |
126 | + user.activate | ||
126 | assert_nothing_raised do | 127 | assert_nothing_raised do |
127 | user.change_password!('test', 'newpass', 'newpass') | 128 | user.change_password!('test', 'newpass', 'newpass') |
128 | end | 129 | end |
@@ -132,6 +133,7 @@ class UserTest < ActiveSupport::TestCase | @@ -132,6 +133,7 @@ class UserTest < ActiveSupport::TestCase | ||
132 | 133 | ||
133 | def test_should_give_correct_current_password_for_changing_password | 134 | def test_should_give_correct_current_password_for_changing_password |
134 | user = create_user('changetest', :password => 'test', :password_confirmation => 'test', :email => 'changetest@example.com') | 135 | user = create_user('changetest', :password => 'test', :password_confirmation => 'test', :email => 'changetest@example.com') |
136 | + user.activate | ||
135 | assert_raise User::IncorrectPassword do | 137 | assert_raise User::IncorrectPassword do |
136 | user.change_password!('wrong', 'newpass', 'newpass') | 138 | user.change_password!('wrong', 'newpass', 'newpass') |
137 | end | 139 | end |
@@ -141,6 +143,8 @@ class UserTest < ActiveSupport::TestCase | @@ -141,6 +143,8 @@ class UserTest < ActiveSupport::TestCase | ||
141 | 143 | ||
142 | should 'require matching confirmation when changing password by force' do | 144 | should 'require matching confirmation when changing password by force' do |
143 | user = create_user('changetest', :password => 'test', :password_confirmation => 'test', :email => 'changetest@example.com') | 145 | user = create_user('changetest', :password => 'test', :password_confirmation => 'test', :email => 'changetest@example.com') |
146 | + user.activate | ||
147 | + | ||
144 | assert_raise ActiveRecord::RecordInvalid do | 148 | assert_raise ActiveRecord::RecordInvalid do |
145 | user.force_change_password!('newpass', 'newpasswrong') | 149 | user.force_change_password!('newpass', 'newpasswrong') |
146 | end | 150 | end |
@@ -153,6 +157,8 @@ class UserTest < ActiveSupport::TestCase | @@ -153,6 +157,8 @@ class UserTest < ActiveSupport::TestCase | ||
153 | assert_nothing_raised do | 157 | assert_nothing_raised do |
154 | user.force_change_password!('newpass', 'newpass') | 158 | user.force_change_password!('newpass', 'newpass') |
155 | end | 159 | end |
160 | + | ||
161 | + user.activate | ||
156 | assert user.authenticated?('newpass') | 162 | assert user.authenticated?('newpass') |
157 | end | 163 | end |
158 | 164 | ||
@@ -256,6 +262,7 @@ class UserTest < ActiveSupport::TestCase | @@ -256,6 +262,7 @@ class UserTest < ActiveSupport::TestCase | ||
256 | 262 | ||
257 | # when the user logs in, her password must be reencrypted with the new | 263 | # when the user logs in, her password must be reencrypted with the new |
258 | # method | 264 | # method |
265 | + user.activate | ||
259 | user.authenticated?('test') | 266 | user.authenticated?('test') |
260 | 267 | ||
261 | # and the new password must be saved back to the database | 268 | # and the new password must be saved back to the database |
@@ -273,6 +280,7 @@ class UserTest < ActiveSupport::TestCase | @@ -273,6 +280,7 @@ class UserTest < ActiveSupport::TestCase | ||
273 | User.expects(:system_encryption_method).returns(:md5).at_least_once | 280 | User.expects(:system_encryption_method).returns(:md5).at_least_once |
274 | 281 | ||
275 | # but the user provided the wrong password | 282 | # but the user provided the wrong password |
283 | + user.activate | ||
276 | user.authenticated?('WRONG_PASSWORD') | 284 | user.authenticated?('WRONG_PASSWORD') |
277 | 285 | ||
278 | # and then her password is not updated | 286 | # and then her password is not updated |
@@ -520,7 +528,9 @@ class UserTest < ActiveSupport::TestCase | @@ -520,7 +528,9 @@ class UserTest < ActiveSupport::TestCase | ||
520 | 528 | ||
521 | should 'not authenticate a not activated user' do | 529 | should 'not authenticate a not activated user' do |
522 | user = new_user :login => 'testuser', :password => 'test123', :password_confirmation => 'test123' | 530 | user = new_user :login => 'testuser', :password => 'test123', :password_confirmation => 'test123' |
523 | - assert_nil User.authenticate('testuser', 'test123') | 531 | + assert_raises User::UserNotActivated do |
532 | + User.authenticate('testuser', 'test123') | ||
533 | + end | ||
524 | end | 534 | end |
525 | 535 | ||
526 | should 'have activation code but no activated at when created' do | 536 | should 'have activation code but no activated at when created' do |