Commit 48c34b8c997297ae26c177aa1d744686deabbcb3
Exists in
theme-brasil-digital-from-staging
and in
9 other branches
Merge branch 'master' into stable
Conflicts: public/javascripts/application.js
Showing
49 changed files
with
1784 additions
and
239 deletions
Show diff stats
DEVELOPMENT.md
| @@ -19,6 +19,26 @@ | @@ -19,6 +19,26 @@ | ||
| 19 | * In the case the original author is a committer, he/she should feel free to | 19 | * In the case the original author is a committer, he/she should feel free to |
| 20 | commit directly if after 1 week nobody has provided any kind of feedback. | 20 | commit directly if after 1 week nobody has provided any kind of feedback. |
| 21 | 21 | ||
| 22 | + * Developers who are not committers should feel free to ping committers if | ||
| 23 | + they do not get feedback on their contributions after 1 week. | ||
| 24 | + | ||
| 25 | + * On GitLab, one can just add a comment to the merge request; one can also | ||
| 26 | + @-mention specific committers or other developers who have expertise on | ||
| 27 | + the area of the contribution. | ||
| 28 | + | ||
| 29 | + * Committers should follow the activity of the project, and try to help | ||
| 30 | + reviewing contributions from others as much as possible. | ||
| 31 | + | ||
| 32 | + * On GitLab one can get emails for all activity on a project by setting the | ||
| 33 | + [notification settings](https://gitlab.com/profile/notifications) to | ||
| 34 | + "watch". | ||
| 35 | + | ||
| 36 | + * Anyone can help by reviewing contributions. Committers are the only ones | ||
| 37 | + who can give the final approval to a contribution, but everyone is welcome | ||
| 38 | + to help with code review, testing, etc. | ||
| 39 | + | ||
| 40 | + * See note above about setting up notification on GitLab. | ||
| 41 | + | ||
| 22 | * Committers should feel free to push trivial (or urgent) changes directly. | 42 | * Committers should feel free to push trivial (or urgent) changes directly. |
| 23 | There are no strict rule on what makes a change trivial or urgent; committers | 43 | There are no strict rule on what makes a change trivial or urgent; committers |
| 24 | are expected to exercise good judgement on a case by case basis. | 44 | are expected to exercise good judgement on a case by case basis. |
app/helpers/application_helper.rb
| @@ -433,19 +433,19 @@ module ApplicationHelper | @@ -433,19 +433,19 @@ module ApplicationHelper | ||
| 433 | end | 433 | end |
| 434 | 434 | ||
| 435 | def theme_site_title | 435 | def theme_site_title |
| 436 | - theme_include('site_title') | 436 | + @theme_site_title ||= theme_include 'site_title' |
| 437 | end | 437 | end |
| 438 | 438 | ||
| 439 | def theme_header | 439 | def theme_header |
| 440 | - theme_include('header') | 440 | + @theme_header ||= theme_include 'header' |
| 441 | end | 441 | end |
| 442 | 442 | ||
| 443 | def theme_footer | 443 | def theme_footer |
| 444 | - theme_include('footer') | 444 | + @theme_footer ||= theme_include 'footer' |
| 445 | end | 445 | end |
| 446 | 446 | ||
| 447 | def theme_extra_navigation | 447 | def theme_extra_navigation |
| 448 | - theme_include('navigation') | 448 | + @theme_extra_navigation ||= theme_include 'navigation' |
| 449 | end | 449 | end |
| 450 | 450 | ||
| 451 | def is_testing_theme | 451 | def is_testing_theme |
| @@ -674,13 +674,14 @@ module ApplicationHelper | @@ -674,13 +674,14 @@ module ApplicationHelper | ||
| 674 | html.join "\n" | 674 | html.join "\n" |
| 675 | end | 675 | end |
| 676 | 676 | ||
| 677 | + def theme_javascript_src | ||
| 678 | + script = File.join theme_path, 'theme.js' | ||
| 679 | + script if File.exists? File.join(Rails.root, 'public', script) | ||
| 680 | + end | ||
| 681 | + | ||
| 677 | def theme_javascript_ng | 682 | def theme_javascript_ng |
| 678 | - script = File.join(theme_path, 'theme.js') | ||
| 679 | - if File.exists?(File.join(Rails.root, 'public', script)) | ||
| 680 | - javascript_include_tag script | ||
| 681 | - else | ||
| 682 | - nil | ||
| 683 | - end | 683 | + script = theme_javascript_src |
| 684 | + javascript_include_tag script if script | ||
| 684 | end | 685 | end |
| 685 | 686 | ||
| 686 | def file_field_or_thumbnail(label, image, i) | 687 | def file_field_or_thumbnail(label, image, i) |
app/helpers/layout_helper.rb
| @@ -18,6 +18,8 @@ module LayoutHelper | @@ -18,6 +18,8 @@ module LayoutHelper | ||
| 18 | unless plugins_javascripts.empty? | 18 | unless plugins_javascripts.empty? |
| 19 | output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}" | 19 | output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}" |
| 20 | end | 20 | end |
| 21 | + output += theme_javascript_ng.to_s | ||
| 22 | + | ||
| 21 | output | 23 | output |
| 22 | end | 24 | end |
| 23 | 25 | ||
| @@ -87,6 +89,10 @@ module LayoutHelper | @@ -87,6 +89,10 @@ module LayoutHelper | ||
| 87 | theme_path + '/style.css' | 89 | theme_path + '/style.css' |
| 88 | end | 90 | end |
| 89 | 91 | ||
| 92 | + def layout_template | ||
| 93 | + if profile then profile.layout_template else environment.layout_template end | ||
| 94 | + end | ||
| 95 | + | ||
| 90 | def addthis_javascript | 96 | def addthis_javascript |
| 91 | if NOOSFERO_CONF['addthis_enabled'] | 97 | if NOOSFERO_CONF['addthis_enabled'] |
| 92 | '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>' | 98 | '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>' |
app/models/person.rb
| @@ -21,6 +21,12 @@ class Person < Profile | @@ -21,6 +21,12 @@ class Person < Profile | ||
| 21 | { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] } | 21 | { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] } |
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | + scope :by_role, lambda { |roles| | ||
| 25 | + roles = [roles] unless roles.kind_of?(Array) | ||
| 26 | + { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)', | ||
| 27 | +roles] } | ||
| 28 | + } | ||
| 29 | + | ||
| 24 | def has_permission_with_plugins?(permission, profile) | 30 | def has_permission_with_plugins?(permission, profile) |
| 25 | permissions = [has_permission_without_plugins?(permission, profile)] | 31 | permissions = [has_permission_without_plugins?(permission, profile)] |
| 26 | permissions += plugins.map do |plugin| | 32 | permissions += plugins.map do |plugin| |
app/models/profile.rb
| @@ -146,8 +146,8 @@ class Profile < ActiveRecord::Base | @@ -146,8 +146,8 @@ class Profile < ActiveRecord::Base | ||
| 146 | alias_method_chain :count, :distinct | 146 | alias_method_chain :count, :distinct |
| 147 | end | 147 | end |
| 148 | 148 | ||
| 149 | - def members_by_role(role) | ||
| 150 | - Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', role.id]) | 149 | + def members_by_role(roles) |
| 150 | + Person.members_of(self).by_role(roles) | ||
| 151 | end | 151 | end |
| 152 | 152 | ||
| 153 | acts_as_having_boxes | 153 | acts_as_having_boxes |
app/views/layouts/application-ng.html.erb
| @@ -72,10 +72,7 @@ | @@ -72,10 +72,7 @@ | ||
| 72 | <div id="navigation-end"></div> | 72 | <div id="navigation-end"></div> |
| 73 | </div><!-- end id="navigation" --> | 73 | </div><!-- end id="navigation" --> |
| 74 | <div id="content"> | 74 | <div id="content"> |
| 75 | - <div id="content-inner"> | ||
| 76 | - <%= insert_boxes(yield) %> | ||
| 77 | - <br style='clear: both'/> | ||
| 78 | - </div><!-- end id="content-inner" --> | 75 | + <%= render 'layouts/content' %> |
| 79 | </div><!-- end id="content" --> | 76 | </div><!-- end id="content" --> |
| 80 | </div><!-- end id="wrap-2" --> | 77 | </div><!-- end id="wrap-2" --> |
| 81 | </div><!-- end id="wrap-1" --> | 78 | </div><!-- end id="wrap-1" --> |
| @@ -84,7 +81,6 @@ | @@ -84,7 +81,6 @@ | ||
| 84 | <%= theme_footer %> | 81 | <%= theme_footer %> |
| 85 | </div><!-- end id="theme-footer" --> | 82 | </div><!-- end id="theme-footer" --> |
| 86 | <%= noosfero_layout_features %> | 83 | <%= noosfero_layout_features %> |
| 87 | - <%= theme_javascript_ng %> | ||
| 88 | <%= addthis_javascript %> | 84 | <%= addthis_javascript %> |
| 89 | <%= | 85 | <%= |
| 90 | @plugins.dispatch(:body_ending).map do |content| | 86 | @plugins.dispatch(:body_ending).map do |content| |
config/application.rb
| @@ -111,9 +111,7 @@ module Noosfero | @@ -111,9 +111,7 @@ module Noosfero | ||
| 111 | # Make sure the secret is at least 30 characters and all random, | 111 | # Make sure the secret is at least 30 characters and all random, |
| 112 | # no regular words or you'll be exposed to dictionary attacks. | 112 | # no regular words or you'll be exposed to dictionary attacks. |
| 113 | config.secret_token = noosfero_session_secret | 113 | config.secret_token = noosfero_session_secret |
| 114 | - config.action_dispatch.session = { | ||
| 115 | - :key => '_noosfero_session', | ||
| 116 | - } | 114 | + config.session_store :cookie_store, :key => '_noosfero_session' |
| 117 | 115 | ||
| 118 | config.time_zone = File.read('/etc/timezone').split("\n").first | 116 | config.time_zone = File.read('/etc/timezone').split("\n").first |
| 119 | config.active_record.default_timezone = :local | 117 | config.active_record.default_timezone = :local |
db/migrate/20140724134600_remove_environment_statistics_block_sooner.rb
0 → 100644
| @@ -0,0 +1,9 @@ | @@ -0,0 +1,9 @@ | ||
| 1 | +class RemoveEnvironmentStatisticsBlockSooner < ActiveRecord::Migration | ||
| 2 | + def self.up | ||
| 3 | + update("UPDATE blocks SET type = 'StatisticsBlock' WHERE type = 'EnvironmentStatisticsBlock'") | ||
| 4 | + end | ||
| 5 | + | ||
| 6 | + def self.down | ||
| 7 | + say("Nothing to undo (cannot recover the data)") | ||
| 8 | + end | ||
| 9 | +end |
debian/control
| @@ -60,6 +60,11 @@ Depends: | @@ -60,6 +60,11 @@ Depends: | ||
| 60 | dbconfig-common, | 60 | dbconfig-common, |
| 61 | adduser, | 61 | adduser, |
| 62 | exim4 | mail-transport-agent, | 62 | exim4 | mail-transport-agent, |
| 63 | +# to minimize upgrade issues: | ||
| 64 | + ruby-feedparser (>= 0.7-3~), | ||
| 65 | + ruby-eventmachine (>= 0.12.10-4~), | ||
| 66 | + ruby-rack (>= 1.4.5-2~), | ||
| 67 | + ruby-tzinfo (>= 1.1.0-2~), | ||
| 63 | ${misc:Depends} | 68 | ${misc:Depends} |
| 64 | Recommends: | 69 | Recommends: |
| 65 | postgresql, | 70 | postgresql, |
etc/noosfero/varnish-noosfero.vcl
| @@ -10,6 +10,13 @@ sub vcl_recv { | @@ -10,6 +10,13 @@ sub vcl_recv { | ||
| 10 | } | 10 | } |
| 11 | } | 11 | } |
| 12 | 12 | ||
| 13 | +sub vcl_deliver { | ||
| 14 | + # Force clients to aways hit the server again for HTML pages | ||
| 15 | + if (resp.http.Content-Type ~ "^text/html") { | ||
| 16 | + set resp.http.Cache-Control = "no-cache"; | ||
| 17 | + } | ||
| 18 | +} | ||
| 19 | + | ||
| 13 | sub vcl_error { | 20 | sub vcl_error { |
| 14 | set obj.http.Content-Type = "text/html; charset=utf-8"; | 21 | set obj.http.Content-Type = "text/html; charset=utf-8"; |
| 15 | 22 |
lib/noosfero.rb
| @@ -70,16 +70,6 @@ module Noosfero | @@ -70,16 +70,6 @@ module Noosfero | ||
| 70 | end | 70 | end |
| 71 | end | 71 | end |
| 72 | 72 | ||
| 73 | - def self.term(t) | ||
| 74 | - self.terminology.get(t) | ||
| 75 | - end | ||
| 76 | - def self.terminology | ||
| 77 | - @terminology ||= Noosfero::Terminology::Default.instance | ||
| 78 | - end | ||
| 79 | - def self.terminology=(term) | ||
| 80 | - @terminology = term | ||
| 81 | - end | ||
| 82 | - | ||
| 83 | def self.url_options | 73 | def self.url_options |
| 84 | case Rails.env | 74 | case Rails.env |
| 85 | when 'development' | 75 | when 'development' |
lib/noosfero/plugin.rb
| @@ -88,18 +88,29 @@ class Noosfero::Plugin | @@ -88,18 +88,29 @@ class Noosfero::Plugin | ||
| 88 | # This is a generic method that initialize any possible filter defined by a | 88 | # This is a generic method that initialize any possible filter defined by a |
| 89 | # plugin to a specific controller | 89 | # plugin to a specific controller |
| 90 | def load_plugin_filters(plugin) | 90 | def load_plugin_filters(plugin) |
| 91 | - plugin_methods = plugin.instance_methods.select {|m| m.to_s.end_with?('_filters')} | ||
| 92 | - plugin_methods.each do |plugin_method| | ||
| 93 | - controller_class = plugin_method.to_s.gsub('_filters', '').camelize.constantize | ||
| 94 | - filters = plugin.new.send(plugin_method) | ||
| 95 | - filters = [filters] if !filters.kind_of?(Array) | ||
| 96 | - | ||
| 97 | - filters.each do |plugin_filter| | ||
| 98 | - filter_method = (plugin.name.underscore.gsub('/','_') + '_' + plugin_filter[:method_name]).to_sym | ||
| 99 | - controller_class.send(plugin_filter[:type], filter_method, (plugin_filter[:options] || {})) | ||
| 100 | - controller_class.send(:define_method, filter_method) do | ||
| 101 | - instance_eval(&plugin_filter[:block]) if environment.plugin_enabled?(plugin) | ||
| 102 | - end | 91 | + Rails.configuration.to_prepare do |
| 92 | + filters = plugin.new.send 'application_controller_filters' rescue [] | ||
| 93 | + Noosfero::Plugin.add_controller_filters ApplicationController, plugin, filters | ||
| 94 | + | ||
| 95 | + plugin_methods = plugin.instance_methods.select {|m| m.to_s.end_with?('_filters')} | ||
| 96 | + plugin_methods.each do |plugin_method| | ||
| 97 | + controller_class = plugin_method.to_s.gsub('_filters', '').camelize.constantize | ||
| 98 | + | ||
| 99 | + filters = plugin.new.send(plugin_method) | ||
| 100 | + Noosfero::Plugin.add_controller_filters controller_class, plugin, filters | ||
| 101 | + end | ||
| 102 | + end | ||
| 103 | + end | ||
| 104 | + | ||
| 105 | + def add_controller_filters(controller_class, plugin, filters) | ||
| 106 | + unless filters.is_a?(Array) | ||
| 107 | + filters = [filters] | ||
| 108 | + end | ||
| 109 | + filters.each do |plugin_filter| | ||
| 110 | + filter_method = (plugin.name.underscore.gsub('/','_') + '_' + plugin_filter[:method_name]).to_sym | ||
| 111 | + controller_class.send(plugin_filter[:type], filter_method, (plugin_filter[:options] || {})) | ||
| 112 | + controller_class.send(:define_method, filter_method) do | ||
| 113 | + instance_exec(&plugin_filter[:block]) if environment.plugin_enabled?(plugin) | ||
| 103 | end | 114 | end |
| 104 | end | 115 | end |
| 105 | end | 116 | end |
lib/tasks/release.rake
| @@ -137,7 +137,17 @@ EOF | @@ -137,7 +137,17 @@ EOF | ||
| 137 | new_version += '~rc1' | 137 | new_version += '~rc1' |
| 138 | end | 138 | end |
| 139 | else | 139 | else |
| 140 | - new_version.sub!(/~rc[0-9]+/, '') | 140 | + if new_version =~ /~rc\d+/ |
| 141 | + new_version.sub!(/~rc[0-9]+/, '') | ||
| 142 | + else | ||
| 143 | + components = new_version.split('.').map(&:to_i) | ||
| 144 | + if components.size < 3 | ||
| 145 | + components << 1 | ||
| 146 | + else | ||
| 147 | + components[-1] += 1 | ||
| 148 | + end | ||
| 149 | + new_version = components.join('.') | ||
| 150 | + end | ||
| 141 | end | 151 | end |
| 142 | 152 | ||
| 143 | puts "Current version: #{$version}" | 153 | puts "Current version: #{$version}" |
lib/unifreire_terminology.rb
| @@ -1,44 +0,0 @@ | @@ -1,44 +0,0 @@ | ||
| 1 | -require 'noosfero/terminology' | ||
| 2 | - | ||
| 3 | -class UnifreireTerminology < Noosfero::Terminology::Custom | ||
| 4 | - | ||
| 5 | - def initialize | ||
| 6 | - # NOTE: the hash values must be marked for translation!! | ||
| 7 | - super({ | ||
| 8 | - 'Enterprises' => N_('Institutions'), | ||
| 9 | - 'enterprises' => N_('institutions'), | ||
| 10 | - 'The enterprises where this user works.' => N_('The institution where this user belongs.'), | ||
| 11 | - 'A block that displays your enterprises' => N_('A block that displays your institutions.'), | ||
| 12 | - 'All enterprises' => N_('All institutions'), | ||
| 13 | - 'Disable search for enterprises' => N_('Disable search for institutions'), | ||
| 14 | - 'One enterprise' => N_('One institution'), | ||
| 15 | - '%{num} enterprises' => N_('%{num} institutions'), | ||
| 16 | - 'Favorite Enterprises' => N_('Favorite Institutions'), | ||
| 17 | - 'This user\'s favorite enterprises.' => N_('This user\'s favorite institutions'), | ||
| 18 | - 'A block that displays your favorite enterprises' => N_('A block that displays your favorite institutions'), | ||
| 19 | - 'All favorite enterprises' => N_('All favorite institutions'), | ||
| 20 | - 'A search for enterprises by products selled and local' => N_('A search for institutions by products selled and local'), | ||
| 21 | - 'Edit message for disabled enterprises' => N_('Edit message for disabled institutions'), | ||
| 22 | - 'Add favorite enterprise' => N_('Add favorite institution'), | ||
| 23 | - 'Validation info is the information the enterprises will see about how your organization processes the enterprises validations it receives: validation methodology, restrictions to the types of enterprises the organization validates etc.' => N_('Validation info is the information the institutions will see about how your organization processes the institutions validations it receives: validation methodology, restrictions to the types of institutions the organization validates etc.'), | ||
| 24 | - 'Here are all <b>%s</b>\'s enterprises.' => N_('Here are all <b>%s</b>\'s institutions.'), | ||
| 25 | - 'Here are all <b>%s</b>\'s favorite enterprises.' => N_('Here are all <b>%s</b>\'s favorite institutions.'), | ||
| 26 | - 'Favorite Enterprises' => N_('Favorite Institutions'), | ||
| 27 | - 'Enterprises in "%s"' => N_('Institutions in "%s"'), | ||
| 28 | - 'Register a new Enterprise' => N_('Register a new Institution'), | ||
| 29 | - 'Events' => N_('Schedule'), | ||
| 30 | - 'Manage enterprise fields' => N_('Manage institutions fields'), | ||
| 31 | - "%s's enterprises" => N_("%s's institutions"), | ||
| 32 | - 'Activate your enterprise' => N_('Activate your institution'), | ||
| 33 | - 'Enterprise activation code' => N_('Institution activation code'), | ||
| 34 | - 'Disable activation of enterprises' => N_('Disable activation of institutions'), | ||
| 35 | - "%s's favorite enterprises" => N_("%s's favorite institutions"), | ||
| 36 | - 'Disable Enterprise' => N_('Disable Institution'), | ||
| 37 | - 'Enable Enterprise' => N_('Enable Institution'), | ||
| 38 | - 'Enterprise Validation' => N_('Institution Validation'), | ||
| 39 | - 'Enterprise Info and settings' => N_('Institution Info and settings'), | ||
| 40 | - 'Enterprises are disabled when created' => N_('Institutions are disabled when created'), | ||
| 41 | - }) | ||
| 42 | - end | ||
| 43 | - | ||
| 44 | -end |
lib/zen3_terminology.rb
| @@ -1,88 +0,0 @@ | @@ -1,88 +0,0 @@ | ||
| 1 | -require 'noosfero/terminology' | ||
| 2 | - | ||
| 3 | -class Zen3Terminology < Noosfero::Terminology::Custom | ||
| 4 | - | ||
| 5 | - def initialize | ||
| 6 | - # NOTE: the hash values must be marked for translation!! | ||
| 7 | - super({ | ||
| 8 | - 'My Home Page' => N_('My ePortfolio'), | ||
| 9 | - 'Homepage' => N_('ePortfolio'), | ||
| 10 | - 'Communities' => N_('Groups'), | ||
| 11 | - 'communities' => N_('groups'), | ||
| 12 | - 'A block that displays your communities' => N_('A block that displays your groups'), | ||
| 13 | - 'A block that displays your friends' => N_('A block that displays your contacts'), | ||
| 14 | - 'The communities in which the user is a member' => N_('The groups in which the user is a member'), | ||
| 15 | - 'All communities' => N_('All groups'), | ||
| 16 | - 'Community' => N_('Group'), | ||
| 17 | - 'One community' => N_('One group'), | ||
| 18 | - '%{num} communities' => N_('%{num} groups'), | ||
| 19 | - 'Disable search for communities' => N_('Disable search for groups'), | ||
| 20 | - 'Enterprises' => N_('Organizations'), | ||
| 21 | - 'enterprises' => N_('organizations'), | ||
| 22 | - 'The enterprises where this user works.' => N_('The organizations where this user works.'), | ||
| 23 | - 'A block that displays your enterprises' => N_('A block that displays your organizations.'), | ||
| 24 | - 'All enterprises' => N_('All organizations'), | ||
| 25 | - 'Disable search for enterprises' => N_('Disable search for organizations'), | ||
| 26 | - 'One enterprise' => N_('One organization'), | ||
| 27 | - '%{num} enterprises' => N_('%{num} organizations'), | ||
| 28 | - 'Favorite Enterprises' => N_('Favorite Organizations'), | ||
| 29 | - 'This user\'s favorite enterprises.' => N_('This user\'s favorite organizations'), | ||
| 30 | - 'A block that displays your favorite enterprises' => N_('A block that displays your favorite organizations'), | ||
| 31 | - 'All favorite enterprises' => N_('All favorite organizations'), | ||
| 32 | - 'A search for enterprises by products selled and local' => N_('A search for organizations by products selled and local'), | ||
| 33 | - 'Edit message for disabled enterprises' => N_('Edit message for disabled organizations'), | ||
| 34 | - 'Add enterprise as favorite' => N_('Add organization as favorite'), | ||
| 35 | - 'Validation info is the information the enterprises will see about how your organization processes the enterprises validations it receives: validation methodology, restrictions to the types of enterprises the organization validates etc.' => N_('Validation info is the information the organizations will see about how your organization processes the organizations validations it receives: validation methodology, restrictions to the types of organizations the organization validates etc.'), | ||
| 36 | - 'Here are all <b>%s</b>\'s enterprises.' => N_('Here all all <b>%s</b>\'s organizations.'), | ||
| 37 | - 'Here are all <b>%s</b>\'s favorite enterprises.' => N_('Here are all <b>%s</b>\'s favorite organizations.'), | ||
| 38 | - 'Favorite Enterprises' => N_('Favorite Organizations'), | ||
| 39 | - 'Enterprises in "%s"' => N_('Organizations in "%s"'), | ||
| 40 | - 'Register a new Enterprise' => N_('Register a new organization'), | ||
| 41 | - 'One friend' => N_('One contact'), | ||
| 42 | - '%s friends' => N_('%s contacts'), | ||
| 43 | - '%s communities' => N_('%s groups'), | ||
| 44 | - 'Are you sure you want to remove %s from your friends list?' => N_('Are you sure you want to remove %s from your contacts list?'), | ||
| 45 | - 'Note that %s will still have you as a friend, unless he/she also wants to remove you from his/her friend list.' => N_('Note that %s will still have you as a contact, unless he/she also wants to remove you from his/her contact list.'), | ||
| 46 | - 'Yes, I want to remove %s from my friend list' => N_('Yes, I want to remove %s from my contact list'), | ||
| 47 | - 'Adding %s as a friend' => N_('Adding %s as a contact'), | ||
| 48 | - 'Are you sure you want to add %s as your friend?' => N_('Are you sure you want to add %s as your contact?'), | ||
| 49 | - 'Note that %s will need to accept being added as your friend.' => N_('Note that %s will need to accept being added as your contact.'), | ||
| 50 | - 'Classify your new friend %s: ' => N_('Classify your new contact %s: '), | ||
| 51 | - 'Yes, I want to add %s as my friend' => N_('Yes, I want to add %s as my contact'), | ||
| 52 | - 'Manage friends' => N_('Manage contacts'), | ||
| 53 | - 'Add friend' => N_('Add contact'), | ||
| 54 | - 'Removing friend: %s' => N_('Removing friend: %s'), | ||
| 55 | - 'Clicking on this button will remove your friend relation with %s.' => N_('Clicking on this button will remove your contact relation with %s.'), | ||
| 56 | - 'You have no friends yet.' => N_('You have no contacts yet.'), | ||
| 57 | - '%s\'s friends' => N_('%s\'s contacts'), | ||
| 58 | - 'Here are all <b>%s</b>\'s friends.' => N_('Here are all <b>%s</b>\'s contacts.'), | ||
| 59 | - 'Friends' => N_('Contacts'), | ||
| 60 | - 'Creating new community' => N_('Creating new group'), | ||
| 61 | - 'Do you want to join this community?' => N_('Do you want to join this group?'), | ||
| 62 | - 'Activate your enterprise' => N_('Activate your organization'), | ||
| 63 | - 'Enterprise activation code' => N_('Organization activation code'), | ||
| 64 | - 'Disable activation of enterprises' => N_('Disable activation of organizations'), | ||
| 65 | - 'Manage community fields' => N_('Manage group fields'), | ||
| 66 | - 'Create a new community' => N_('Create a new group'), | ||
| 67 | - 'Preferred domain name:' => N_('Choose your host community:'), | ||
| 68 | - 'My communities' => N_('My groups'), | ||
| 69 | - 'Community Info and settings' => N_('Group Info and Settings'), | ||
| 70 | - '{#} community' => N_('{#} group'), | ||
| 71 | - '{#} communities' => N_('{#} groups'), | ||
| 72 | - '{#} enterprise' => N_('{#} organization'), | ||
| 73 | - '{#} enterprises' => N_('{#} organizations'), | ||
| 74 | - '{#} friend' => N_('{#} contact'), | ||
| 75 | - '{#} friends' => N_('{#} contacts'), | ||
| 76 | - "%s's favorite enterprises" => N_("%s's favorite organizations"), | ||
| 77 | - 'Disable Enterprise' => N_('Disable Organization'), | ||
| 78 | - 'Enable Enterprise' => N_('Enable Organization'), | ||
| 79 | - 'Enterprise Validation' => N_('Organization Validation'), | ||
| 80 | - 'Enterprise Info and settings' => N_('Organization Info and settings'), | ||
| 81 | - 'Choose the communities you want to join and/or create your own.' => N_('Choose the groups you want to join and/or create your own.'), | ||
| 82 | - 'New community' => N_('New group'), | ||
| 83 | - "Tags are important to new users, they'll be able to find your new community more easily." => N_("Tags are important to new users, they'll be able to find your new group more easily."), | ||
| 84 | - 'Enterprises are disabled when created' => N_('Organizations are disabled when created'), | ||
| 85 | - }) | ||
| 86 | - end | ||
| 87 | - | ||
| 88 | -end |
| @@ -0,0 +1,52 @@ | @@ -0,0 +1,52 @@ | ||
| 1 | +class PjaxPlugin < Noosfero::Plugin | ||
| 2 | + | ||
| 3 | + def self.plugin_name | ||
| 4 | + I18n.t('pjax_plugin.lib.plugin.name') | ||
| 5 | + end | ||
| 6 | + | ||
| 7 | + def self.plugin_description | ||
| 8 | + I18n.t('pjax_plugin.lib.plugin.description') | ||
| 9 | + end | ||
| 10 | + | ||
| 11 | + def stylesheet? | ||
| 12 | + true | ||
| 13 | + end | ||
| 14 | + | ||
| 15 | + def js_files | ||
| 16 | + ['jquery.pjax.js', 'patchwork.js', 'loading-overlay', 'pjax', ].map{ |j| "javascripts/#{j}" } | ||
| 17 | + end | ||
| 18 | + | ||
| 19 | + def head_ending | ||
| 20 | + #TODO: add pjax meta | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | + def body_beginning | ||
| 24 | + lambda{ render 'pjax_layouts/load_state_script' } | ||
| 25 | + end | ||
| 26 | + | ||
| 27 | + PjaxCheck = lambda do | ||
| 28 | + return unless request.headers['X-PJAX'] | ||
| 29 | + # raise makes pjax fallback to a regular request | ||
| 30 | + raise "Pjax can't be used here" if params[:controller] == 'account' | ||
| 31 | + | ||
| 32 | + @pjax = true | ||
| 33 | + @pjax_loaded_themes = request.headers['X-PJAX-Themes'].to_s.split(',') || [] | ||
| 34 | + | ||
| 35 | + unless self.respond_to? :get_layout_with_pjax | ||
| 36 | + self.class.send :define_method, :get_layout_with_pjax do | ||
| 37 | + if @pjax then 'pjax' else get_layout_without_pjax end | ||
| 38 | + end | ||
| 39 | + self.class.alias_method_chain :get_layout, :pjax | ||
| 40 | + end | ||
| 41 | + end | ||
| 42 | + | ||
| 43 | + def application_controller_filters | ||
| 44 | + [{ | ||
| 45 | + :type => 'before_filter', :method_name => 'pjax_check', | ||
| 46 | + :options => {}, :block => PjaxCheck, | ||
| 47 | + }] | ||
| 48 | + end | ||
| 49 | + | ||
| 50 | + protected | ||
| 51 | + | ||
| 52 | +end |
617 KB
3.4 KB
| @@ -0,0 +1,838 @@ | @@ -0,0 +1,838 @@ | ||
| 1 | +// jquery.pjax.js | ||
| 2 | +// copyright chris wanstrath | ||
| 3 | +// https://github.com/defunkt/jquery-pjax | ||
| 4 | + | ||
| 5 | +(function($){ | ||
| 6 | + | ||
| 7 | +// When called on a container with a selector, fetches the href with | ||
| 8 | +// ajax into the container or with the data-pjax attribute on the link | ||
| 9 | +// itself. | ||
| 10 | +// | ||
| 11 | +// Tries to make sure the back button and ctrl+click work the way | ||
| 12 | +// you'd expect. | ||
| 13 | +// | ||
| 14 | +// Exported as $.fn.pjax | ||
| 15 | +// | ||
| 16 | +// Accepts a jQuery ajax options object that may include these | ||
| 17 | +// pjax specific options: | ||
| 18 | +// | ||
| 19 | +// | ||
| 20 | +// container - Where to stick the response body. Usually a String selector. | ||
| 21 | +// $(container).html(xhr.responseBody) | ||
| 22 | +// (default: current jquery context) | ||
| 23 | +// push - Whether to pushState the URL. Defaults to true (of course). | ||
| 24 | +// replace - Want to use replaceState instead? That's cool. | ||
| 25 | +// | ||
| 26 | +// For convenience the second parameter can be either the container or | ||
| 27 | +// the options object. | ||
| 28 | +// | ||
| 29 | +// Returns the jQuery object | ||
| 30 | +function fnPjax(selector, container, options) { | ||
| 31 | + var context = this | ||
| 32 | + return this.on('click.pjax', selector, function(event) { | ||
| 33 | + var opts = $.extend({}, optionsFor(container, options)) | ||
| 34 | + if (!opts.container) | ||
| 35 | + opts.container = $(this).attr('data-pjax') || context | ||
| 36 | + handleClick(event, opts) | ||
| 37 | + }) | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +// Public: pjax on click handler | ||
| 41 | +// | ||
| 42 | +// Exported as $.pjax.click. | ||
| 43 | +// | ||
| 44 | +// event - "click" jQuery.Event | ||
| 45 | +// options - pjax options | ||
| 46 | +// | ||
| 47 | +// Examples | ||
| 48 | +// | ||
| 49 | +// $(document).on('click', 'a', $.pjax.click) | ||
| 50 | +// // is the same as | ||
| 51 | +// $(document).pjax('a') | ||
| 52 | +// | ||
| 53 | +// $(document).on('click', 'a', function(event) { | ||
| 54 | +// var container = $(this).closest('[data-pjax-container]') | ||
| 55 | +// $.pjax.click(event, container) | ||
| 56 | +// }) | ||
| 57 | +// | ||
| 58 | +// Returns nothing. | ||
| 59 | +function handleClick(event, container, options) { | ||
| 60 | + options = optionsFor(container, options) | ||
| 61 | + | ||
| 62 | + var link = event.currentTarget | ||
| 63 | + | ||
| 64 | + if (link.tagName.toUpperCase() !== 'A') | ||
| 65 | + throw "$.fn.pjax or $.pjax.click requires an anchor element" | ||
| 66 | + | ||
| 67 | + // Middle click, cmd click, and ctrl click should open | ||
| 68 | + // links in a new tab as normal. | ||
| 69 | + if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) | ||
| 70 | + return | ||
| 71 | + | ||
| 72 | + // Ignore cross origin links | ||
| 73 | + if ( location.protocol !== link.protocol || location.hostname !== link.hostname ) | ||
| 74 | + return | ||
| 75 | + | ||
| 76 | + // Ignore anchors on the same page | ||
| 77 | + if (link.hash && link.href.replace(link.hash, '') === | ||
| 78 | + location.href.replace(location.hash, '')) | ||
| 79 | + return | ||
| 80 | + | ||
| 81 | + // Ignore empty anchor "foo.html#" | ||
| 82 | + if (link.href === location.href + '#') | ||
| 83 | + return | ||
| 84 | + | ||
| 85 | + var defaults = { | ||
| 86 | + url: link.href, | ||
| 87 | + container: $(link).attr('data-pjax'), | ||
| 88 | + target: link | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + var opts = $.extend({}, defaults, options) | ||
| 92 | + var clickEvent = $.Event('pjax:click') | ||
| 93 | + $(link).trigger(clickEvent, [opts]) | ||
| 94 | + | ||
| 95 | + if (!clickEvent.isDefaultPrevented()) { | ||
| 96 | + pjax(opts) | ||
| 97 | + event.preventDefault() | ||
| 98 | + } | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +// Public: pjax on form submit handler | ||
| 102 | +// | ||
| 103 | +// Exported as $.pjax.submit | ||
| 104 | +// | ||
| 105 | +// event - "click" jQuery.Event | ||
| 106 | +// options - pjax options | ||
| 107 | +// | ||
| 108 | +// Examples | ||
| 109 | +// | ||
| 110 | +// $(document).on('submit', 'form', function(event) { | ||
| 111 | +// var container = $(this).closest('[data-pjax-container]') | ||
| 112 | +// $.pjax.submit(event, container) | ||
| 113 | +// }) | ||
| 114 | +// | ||
| 115 | +// Returns nothing. | ||
| 116 | +function handleSubmit(event, container, options) { | ||
| 117 | + options = optionsFor(container, options) | ||
| 118 | + | ||
| 119 | + var form = event.currentTarget | ||
| 120 | + | ||
| 121 | + if (form.tagName.toUpperCase() !== 'FORM') | ||
| 122 | + throw "$.pjax.submit requires a form element" | ||
| 123 | + | ||
| 124 | + var defaults = { | ||
| 125 | + type: form.method.toUpperCase(), | ||
| 126 | + url: form.action, | ||
| 127 | + data: $(form).serializeArray(), | ||
| 128 | + container: $(form).attr('data-pjax'), | ||
| 129 | + target: form | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + pjax($.extend({}, defaults, options)) | ||
| 133 | + | ||
| 134 | + event.preventDefault() | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +// Loads a URL with ajax, puts the response body inside a container, | ||
| 138 | +// then pushState()'s the loaded URL. | ||
| 139 | +// | ||
| 140 | +// Works just like $.ajax in that it accepts a jQuery ajax | ||
| 141 | +// settings object (with keys like url, type, data, etc). | ||
| 142 | +// | ||
| 143 | +// Accepts these extra keys: | ||
| 144 | +// | ||
| 145 | +// container - Where to stick the response body. | ||
| 146 | +// $(container).html(xhr.responseBody) | ||
| 147 | +// push - Whether to pushState the URL. Defaults to true (of course). | ||
| 148 | +// replace - Want to use replaceState instead? That's cool. | ||
| 149 | +// | ||
| 150 | +// Use it just like $.ajax: | ||
| 151 | +// | ||
| 152 | +// var xhr = $.pjax({ url: this.href, container: '#main' }) | ||
| 153 | +// console.log( xhr.readyState ) | ||
| 154 | +// | ||
| 155 | +// Returns whatever $.ajax returns. | ||
| 156 | +function pjax(options) { | ||
| 157 | + options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options) | ||
| 158 | + | ||
| 159 | + if ($.isFunction(options.url)) { | ||
| 160 | + options.url = options.url() | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + var target = options.target | ||
| 164 | + | ||
| 165 | + var hash = parseURL(options.url).hash | ||
| 166 | + | ||
| 167 | + var context = options.context = findContainerFor(options.container) | ||
| 168 | + | ||
| 169 | + // We want the browser to maintain two separate internal caches: one | ||
| 170 | + // for pjax'd partial page loads and one for normal page loads. | ||
| 171 | + // Without adding this secret parameter, some browsers will often | ||
| 172 | + // confuse the two. | ||
| 173 | + if (!options.data) options.data = {} | ||
| 174 | + options.data._pjax = context.selector | ||
| 175 | + | ||
| 176 | + function fire(type, args) { | ||
| 177 | + var event = $.Event(type, { relatedTarget: target }) | ||
| 178 | + context.trigger(event, args) | ||
| 179 | + return !event.isDefaultPrevented() | ||
| 180 | + } | ||
| 181 | + | ||
| 182 | + var timeoutTimer | ||
| 183 | + | ||
| 184 | + options.beforeSend = function(xhr, settings) { | ||
| 185 | + // No timeout for non-GET requests | ||
| 186 | + // Its not safe to request the resource again with a fallback method. | ||
| 187 | + if (settings.type !== 'GET') { | ||
| 188 | + settings.timeout = 0 | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + xhr.setRequestHeader('X-PJAX', 'true') | ||
| 192 | + xhr.setRequestHeader('X-PJAX-Container', context.selector) | ||
| 193 | + | ||
| 194 | + if (!fire('pjax:beforeSend', [xhr, settings])) | ||
| 195 | + return false | ||
| 196 | + | ||
| 197 | + if (settings.timeout > 0) { | ||
| 198 | + timeoutTimer = setTimeout(function() { | ||
| 199 | + if (fire('pjax:timeout', [xhr, options])) | ||
| 200 | + xhr.abort('timeout') | ||
| 201 | + }, settings.timeout) | ||
| 202 | + | ||
| 203 | + // Clear timeout setting so jquerys internal timeout isn't invoked | ||
| 204 | + settings.timeout = 0 | ||
| 205 | + } | ||
| 206 | + | ||
| 207 | + options.requestUrl = parseURL(settings.url).href | ||
| 208 | + } | ||
| 209 | + | ||
| 210 | + options.complete = function(xhr, textStatus) { | ||
| 211 | + if (timeoutTimer) | ||
| 212 | + clearTimeout(timeoutTimer) | ||
| 213 | + | ||
| 214 | + fire('pjax:complete', [xhr, textStatus, options]) | ||
| 215 | + | ||
| 216 | + fire('pjax:end', [xhr, options]) | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + options.error = function(xhr, textStatus, errorThrown) { | ||
| 220 | + var container = extractContainer("", xhr, options) | ||
| 221 | + | ||
| 222 | + var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options]) | ||
| 223 | + if (options.type == 'GET' && textStatus !== 'abort' && allowed) { | ||
| 224 | + locationReplace(container.url) | ||
| 225 | + } | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + options.success = function(data, status, xhr) { | ||
| 229 | + // If $.pjax.defaults.version is a function, invoke it first. | ||
| 230 | + // Otherwise it can be a static string. | ||
| 231 | + var currentVersion = (typeof $.pjax.defaults.version === 'function') ? | ||
| 232 | + $.pjax.defaults.version() : | ||
| 233 | + $.pjax.defaults.version | ||
| 234 | + | ||
| 235 | + var latestVersion = xhr.getResponseHeader('X-PJAX-Version') | ||
| 236 | + | ||
| 237 | + var container = extractContainer(data, xhr, options) | ||
| 238 | + | ||
| 239 | + // If there is a layout version mismatch, hard load the new url | ||
| 240 | + if (currentVersion && latestVersion && currentVersion !== latestVersion) { | ||
| 241 | + locationReplace(container.url) | ||
| 242 | + return | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + // If the new response is missing a body, hard load the page | ||
| 246 | + if (!container.contents) { | ||
| 247 | + locationReplace(container.url) | ||
| 248 | + return | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + pjax.state = { | ||
| 252 | + id: options.id || uniqueId(), | ||
| 253 | + url: container.url, | ||
| 254 | + title: container.title, | ||
| 255 | + container: context.selector, | ||
| 256 | + fragment: options.fragment, | ||
| 257 | + timeout: options.timeout | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + if (options.push || options.replace) { | ||
| 261 | + window.history.replaceState(pjax.state, container.title, container.url) | ||
| 262 | + } | ||
| 263 | + | ||
| 264 | + // Clear out any focused controls before inserting new page contents. | ||
| 265 | + document.activeElement.blur() | ||
| 266 | + | ||
| 267 | + if (container.title) document.title = container.title | ||
| 268 | + context.html(container.contents) | ||
| 269 | + | ||
| 270 | + // FF bug: Won't autofocus fields that are inserted via JS. | ||
| 271 | + // This behavior is incorrect. So if theres no current focus, autofocus | ||
| 272 | + // the last field. | ||
| 273 | + // | ||
| 274 | + // http://www.w3.org/html/wg/drafts/html/master/forms.html | ||
| 275 | + var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0] | ||
| 276 | + if (autofocusEl && document.activeElement !== autofocusEl) { | ||
| 277 | + autofocusEl.focus(); | ||
| 278 | + } | ||
| 279 | + | ||
| 280 | + executeScriptTags(container.scripts) | ||
| 281 | + | ||
| 282 | + // Scroll to top by default | ||
| 283 | + if (typeof options.scrollTo === 'number') | ||
| 284 | + $(window).scrollTop(options.scrollTo) | ||
| 285 | + | ||
| 286 | + // If the URL has a hash in it, make sure the browser | ||
| 287 | + // knows to navigate to the hash. | ||
| 288 | + if ( hash !== '' ) { | ||
| 289 | + // Avoid using simple hash set here. Will add another history | ||
| 290 | + // entry. Replace the url with replaceState and scroll to target | ||
| 291 | + // by hand. | ||
| 292 | + // | ||
| 293 | + // window.location.hash = hash | ||
| 294 | + var url = parseURL(container.url) | ||
| 295 | + url.hash = hash | ||
| 296 | + | ||
| 297 | + pjax.state.url = url.href | ||
| 298 | + window.history.replaceState(pjax.state, container.title, url.href) | ||
| 299 | + | ||
| 300 | + var target = $(url.hash) | ||
| 301 | + if (target.length) $(window).scrollTop(target.offset().top) | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + fire('pjax:success', [data, status, xhr, options]) | ||
| 305 | + } | ||
| 306 | + | ||
| 307 | + | ||
| 308 | + // Initialize pjax.state for the initial page load. Assume we're | ||
| 309 | + // using the container and options of the link we're loading for the | ||
| 310 | + // back button to the initial page. This ensures good back button | ||
| 311 | + // behavior. | ||
| 312 | + if (!pjax.state) { | ||
| 313 | + pjax.state = { | ||
| 314 | + id: uniqueId(), | ||
| 315 | + url: window.location.href, | ||
| 316 | + title: document.title, | ||
| 317 | + container: context.selector, | ||
| 318 | + fragment: options.fragment, | ||
| 319 | + timeout: options.timeout | ||
| 320 | + } | ||
| 321 | + window.history.replaceState(pjax.state, document.title) | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + // Cancel the current request if we're already pjaxing | ||
| 325 | + var xhr = pjax.xhr | ||
| 326 | + if ( xhr && xhr.readyState < 4) { | ||
| 327 | + xhr.onreadystatechange = $.noop | ||
| 328 | + xhr.abort() | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + pjax.options = options | ||
| 332 | + var xhr = pjax.xhr = $.ajax(options) | ||
| 333 | + | ||
| 334 | + if (xhr.readyState > 0) { | ||
| 335 | + if (options.push && !options.replace) { | ||
| 336 | + // Cache current container element before replacing it | ||
| 337 | + cachePush(pjax.state.id, context.clone().contents()) | ||
| 338 | + | ||
| 339 | + window.history.pushState(null, "", stripPjaxParam(options.requestUrl)) | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + fire('pjax:start', [xhr, options]) | ||
| 343 | + fire('pjax:send', [xhr, options]) | ||
| 344 | + } | ||
| 345 | + | ||
| 346 | + return pjax.xhr | ||
| 347 | +} | ||
| 348 | + | ||
| 349 | +// Public: Reload current page with pjax. | ||
| 350 | +// | ||
| 351 | +// Returns whatever $.pjax returns. | ||
| 352 | +function pjaxReload(container, options) { | ||
| 353 | + var defaults = { | ||
| 354 | + url: window.location.href, | ||
| 355 | + push: false, | ||
| 356 | + replace: true, | ||
| 357 | + scrollTo: false | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + return pjax($.extend(defaults, optionsFor(container, options))) | ||
| 361 | +} | ||
| 362 | + | ||
| 363 | +// Internal: Hard replace current state with url. | ||
| 364 | +// | ||
| 365 | +// Work for around WebKit | ||
| 366 | +// https://bugs.webkit.org/show_bug.cgi?id=93506 | ||
| 367 | +// | ||
| 368 | +// Returns nothing. | ||
| 369 | +function locationReplace(url) { | ||
| 370 | + window.history.replaceState(null, "", "#") | ||
| 371 | + window.location.replace(url) | ||
| 372 | +} | ||
| 373 | + | ||
| 374 | + | ||
| 375 | +var initialPop = true | ||
| 376 | +var initialURL = window.location.href | ||
| 377 | +var initialState = window.history.state | ||
| 378 | + | ||
| 379 | +// Initialize $.pjax.state if possible | ||
| 380 | +// Happens when reloading a page and coming forward from a different | ||
| 381 | +// session history. | ||
| 382 | +if (initialState && initialState.container) { | ||
| 383 | + pjax.state = initialState | ||
| 384 | +} | ||
| 385 | + | ||
| 386 | +// Non-webkit browsers don't fire an initial popstate event | ||
| 387 | +if ('state' in window.history) { | ||
| 388 | + initialPop = false | ||
| 389 | +} | ||
| 390 | + | ||
| 391 | +// popstate handler takes care of the back and forward buttons | ||
| 392 | +// | ||
| 393 | +// You probably shouldn't use pjax on pages with other pushState | ||
| 394 | +// stuff yet. | ||
| 395 | +function onPjaxPopstate(event) { | ||
| 396 | + var state = event.state | ||
| 397 | + | ||
| 398 | + if (state && state.container) { | ||
| 399 | + // When coming forward from a separate history session, will get an | ||
| 400 | + // initial pop with a state we are already at. Skip reloading the current | ||
| 401 | + // page. | ||
| 402 | + if (initialPop && initialURL == state.url) return | ||
| 403 | + | ||
| 404 | + // If popping back to the same state, just skip. | ||
| 405 | + // Could be clicking back from hashchange rather than a pushState. | ||
| 406 | + if (pjax.state.id === state.id) return | ||
| 407 | + | ||
| 408 | + var container = $(state.container) | ||
| 409 | + if (container.length) { | ||
| 410 | + var direction, contents = cacheMapping[state.id] | ||
| 411 | + | ||
| 412 | + if (pjax.state) { | ||
| 413 | + // Since state ids always increase, we can deduce the history | ||
| 414 | + // direction from the previous state. | ||
| 415 | + direction = pjax.state.id < state.id ? 'forward' : 'back' | ||
| 416 | + | ||
| 417 | + // Cache current container before replacement and inform the | ||
| 418 | + // cache which direction the history shifted. | ||
| 419 | + cachePop(direction, pjax.state.id, container.clone().contents()) | ||
| 420 | + } | ||
| 421 | + | ||
| 422 | + var popstateEvent = $.Event('pjax:popstate', { | ||
| 423 | + state: state, | ||
| 424 | + direction: direction | ||
| 425 | + }) | ||
| 426 | + container.trigger(popstateEvent) | ||
| 427 | + | ||
| 428 | + var options = { | ||
| 429 | + id: state.id, | ||
| 430 | + url: state.url, | ||
| 431 | + container: container, | ||
| 432 | + push: false, | ||
| 433 | + fragment: state.fragment, | ||
| 434 | + timeout: state.timeout, | ||
| 435 | + scrollTo: false | ||
| 436 | + } | ||
| 437 | + | ||
| 438 | + if (contents) { | ||
| 439 | + container.trigger('pjax:start', [null, options]) | ||
| 440 | + | ||
| 441 | + if (state.title) document.title = state.title | ||
| 442 | + container.html(contents) | ||
| 443 | + pjax.state = state | ||
| 444 | + | ||
| 445 | + container.trigger('pjax:end', [null, options]) | ||
| 446 | + } else { | ||
| 447 | + pjax(options) | ||
| 448 | + } | ||
| 449 | + | ||
| 450 | + // Force reflow/relayout before the browser tries to restore the | ||
| 451 | + // scroll position. | ||
| 452 | + container[0].offsetHeight | ||
| 453 | + } else { | ||
| 454 | + locationReplace(location.href) | ||
| 455 | + } | ||
| 456 | + } | ||
| 457 | + initialPop = false | ||
| 458 | +} | ||
| 459 | + | ||
| 460 | +// Fallback version of main pjax function for browsers that don't | ||
| 461 | +// support pushState. | ||
| 462 | +// | ||
| 463 | +// Returns nothing since it retriggers a hard form submission. | ||
| 464 | +function fallbackPjax(options) { | ||
| 465 | + var url = $.isFunction(options.url) ? options.url() : options.url, | ||
| 466 | + method = options.type ? options.type.toUpperCase() : 'GET' | ||
| 467 | + | ||
| 468 | + var form = $('<form>', { | ||
| 469 | + method: method === 'GET' ? 'GET' : 'POST', | ||
| 470 | + action: url, | ||
| 471 | + style: 'display:none' | ||
| 472 | + }) | ||
| 473 | + | ||
| 474 | + if (method !== 'GET' && method !== 'POST') { | ||
| 475 | + form.append($('<input>', { | ||
| 476 | + type: 'hidden', | ||
| 477 | + name: '_method', | ||
| 478 | + value: method.toLowerCase() | ||
| 479 | + })) | ||
| 480 | + } | ||
| 481 | + | ||
| 482 | + var data = options.data | ||
| 483 | + if (typeof data === 'string') { | ||
| 484 | + $.each(data.split('&'), function(index, value) { | ||
| 485 | + var pair = value.split('=') | ||
| 486 | + form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]})) | ||
| 487 | + }) | ||
| 488 | + } else if (typeof data === 'object') { | ||
| 489 | + for (key in data) | ||
| 490 | + form.append($('<input>', {type: 'hidden', name: key, value: data[key]})) | ||
| 491 | + } | ||
| 492 | + | ||
| 493 | + $(document.body).append(form) | ||
| 494 | + form.submit() | ||
| 495 | +} | ||
| 496 | + | ||
| 497 | +// Internal: Generate unique id for state object. | ||
| 498 | +// | ||
| 499 | +// Use a timestamp instead of a counter since ids should still be | ||
| 500 | +// unique across page loads. | ||
| 501 | +// | ||
| 502 | +// Returns Number. | ||
| 503 | +function uniqueId() { | ||
| 504 | + return (new Date).getTime() | ||
| 505 | +} | ||
| 506 | + | ||
| 507 | +// Internal: Strips _pjax param from url | ||
| 508 | +// | ||
| 509 | +// url - String | ||
| 510 | +// | ||
| 511 | +// Returns String. | ||
| 512 | +function stripPjaxParam(url) { | ||
| 513 | + return url | ||
| 514 | + .replace(/\?_pjax=[^&]+&?/, '?') | ||
| 515 | + .replace(/_pjax=[^&]+&?/, '') | ||
| 516 | + .replace(/[\?&]$/, '') | ||
| 517 | +} | ||
| 518 | + | ||
| 519 | +// Internal: Parse URL components and returns a Locationish object. | ||
| 520 | +// | ||
| 521 | +// url - String URL | ||
| 522 | +// | ||
| 523 | +// Returns HTMLAnchorElement that acts like Location. | ||
| 524 | +function parseURL(url) { | ||
| 525 | + var a = document.createElement('a') | ||
| 526 | + a.href = url | ||
| 527 | + return a | ||
| 528 | +} | ||
| 529 | + | ||
| 530 | +// Internal: Build options Object for arguments. | ||
| 531 | +// | ||
| 532 | +// For convenience the first parameter can be either the container or | ||
| 533 | +// the options object. | ||
| 534 | +// | ||
| 535 | +// Examples | ||
| 536 | +// | ||
| 537 | +// optionsFor('#container') | ||
| 538 | +// // => {container: '#container'} | ||
| 539 | +// | ||
| 540 | +// optionsFor('#container', {push: true}) | ||
| 541 | +// // => {container: '#container', push: true} | ||
| 542 | +// | ||
| 543 | +// optionsFor({container: '#container', push: true}) | ||
| 544 | +// // => {container: '#container', push: true} | ||
| 545 | +// | ||
| 546 | +// Returns options Object. | ||
| 547 | +function optionsFor(container, options) { | ||
| 548 | + // Both container and options | ||
| 549 | + if ( container && options ) | ||
| 550 | + options.container = container | ||
| 551 | + | ||
| 552 | + // First argument is options Object | ||
| 553 | + else if ( $.isPlainObject(container) ) | ||
| 554 | + options = container | ||
| 555 | + | ||
| 556 | + // Only container | ||
| 557 | + else | ||
| 558 | + options = {container: container} | ||
| 559 | + | ||
| 560 | + // Find and validate container | ||
| 561 | + if (options.container) | ||
| 562 | + options.container = findContainerFor(options.container) | ||
| 563 | + | ||
| 564 | + return options | ||
| 565 | +} | ||
| 566 | + | ||
| 567 | +// Internal: Find container element for a variety of inputs. | ||
| 568 | +// | ||
| 569 | +// Because we can't persist elements using the history API, we must be | ||
| 570 | +// able to find a String selector that will consistently find the Element. | ||
| 571 | +// | ||
| 572 | +// container - A selector String, jQuery object, or DOM Element. | ||
| 573 | +// | ||
| 574 | +// Returns a jQuery object whose context is `document` and has a selector. | ||
| 575 | +function findContainerFor(container) { | ||
| 576 | + container = $(container) | ||
| 577 | + | ||
| 578 | + if ( !container.length ) { | ||
| 579 | + throw "no pjax container for " + container.selector | ||
| 580 | + } else if ( container.selector !== '' && container.context === document ) { | ||
| 581 | + return container | ||
| 582 | + } else if ( container.attr('id') ) { | ||
| 583 | + return $('#' + container.attr('id')) | ||
| 584 | + } else { | ||
| 585 | + throw "cant get selector for pjax container!" | ||
| 586 | + } | ||
| 587 | +} | ||
| 588 | + | ||
| 589 | +// Internal: Filter and find all elements matching the selector. | ||
| 590 | +// | ||
| 591 | +// Where $.fn.find only matches descendants, findAll will test all the | ||
| 592 | +// top level elements in the jQuery object as well. | ||
| 593 | +// | ||
| 594 | +// elems - jQuery object of Elements | ||
| 595 | +// selector - String selector to match | ||
| 596 | +// | ||
| 597 | +// Returns a jQuery object. | ||
| 598 | +function findAll(elems, selector) { | ||
| 599 | + return elems.filter(selector).add(elems.find(selector)); | ||
| 600 | +} | ||
| 601 | + | ||
| 602 | +function parseHTML(html) { | ||
| 603 | + return $.parseHTML(html, document, true) | ||
| 604 | +} | ||
| 605 | + | ||
| 606 | +// Internal: Extracts container and metadata from response. | ||
| 607 | +// | ||
| 608 | +// 1. Extracts X-PJAX-URL header if set | ||
| 609 | +// 2. Extracts inline <title> tags | ||
| 610 | +// 3. Builds response Element and extracts fragment if set | ||
| 611 | +// | ||
| 612 | +// data - String response data | ||
| 613 | +// xhr - XHR response | ||
| 614 | +// options - pjax options Object | ||
| 615 | +// | ||
| 616 | +// Returns an Object with url, title, and contents keys. | ||
| 617 | +function extractContainer(data, xhr, options) { | ||
| 618 | + var obj = {} | ||
| 619 | + | ||
| 620 | + // Prefer X-PJAX-URL header if it was set, otherwise fallback to | ||
| 621 | + // using the original requested url. | ||
| 622 | + obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl) | ||
| 623 | + | ||
| 624 | + // Attempt to parse response html into elements | ||
| 625 | + if (/<html/i.test(data)) { | ||
| 626 | + var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0])) | ||
| 627 | + var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) | ||
| 628 | + } else { | ||
| 629 | + var $head = $body = $(parseHTML(data)) | ||
| 630 | + } | ||
| 631 | + | ||
| 632 | + // If response data is empty, return fast | ||
| 633 | + if ($body.length === 0) | ||
| 634 | + return obj | ||
| 635 | + | ||
| 636 | + // If there's a <title> tag in the header, use it as | ||
| 637 | + // the page's title. | ||
| 638 | + obj.title = findAll($head, 'title').last().text() | ||
| 639 | + | ||
| 640 | + if (options.fragment) { | ||
| 641 | + // If they specified a fragment, look for it in the response | ||
| 642 | + // and pull it out. | ||
| 643 | + if (options.fragment === 'body') { | ||
| 644 | + var $fragment = $body | ||
| 645 | + } else { | ||
| 646 | + var $fragment = findAll($body, options.fragment).first() | ||
| 647 | + } | ||
| 648 | + | ||
| 649 | + if ($fragment.length) { | ||
| 650 | + obj.contents = $fragment.contents() | ||
| 651 | + | ||
| 652 | + // If there's no title, look for data-title and title attributes | ||
| 653 | + // on the fragment | ||
| 654 | + if (!obj.title) | ||
| 655 | + obj.title = $fragment.attr('title') || $fragment.data('title') | ||
| 656 | + } | ||
| 657 | + | ||
| 658 | + } else if (!/<html/i.test(data)) { | ||
| 659 | + obj.contents = $body | ||
| 660 | + } | ||
| 661 | + | ||
| 662 | + // Clean up any <title> tags | ||
| 663 | + if (obj.contents) { | ||
| 664 | + // Remove any parent title elements | ||
| 665 | + obj.contents = obj.contents.not(function() { return $(this).is('title') }) | ||
| 666 | + | ||
| 667 | + // Then scrub any titles from their descendants | ||
| 668 | + obj.contents.find('title').remove() | ||
| 669 | + | ||
| 670 | + // Gather all script[src] elements | ||
| 671 | + //obj.scripts = findAll(obj.contents, 'script[src]').remove() | ||
| 672 | + obj.contents = obj.contents.not(obj.scripts) | ||
| 673 | + } | ||
| 674 | + | ||
| 675 | + // Trim any whitespace off the title | ||
| 676 | + if (obj.title) obj.title = $.trim(obj.title) | ||
| 677 | + | ||
| 678 | + return obj | ||
| 679 | +} | ||
| 680 | + | ||
| 681 | +// Load an execute scripts using standard script request. | ||
| 682 | +// | ||
| 683 | +// Avoids jQuery's traditional $.getScript which does a XHR request and | ||
| 684 | +// globalEval. | ||
| 685 | +// | ||
| 686 | +// scripts - jQuery object of script Elements | ||
| 687 | +// | ||
| 688 | +// Returns nothing. | ||
| 689 | +function executeScriptTags(scripts) { | ||
| 690 | + if (!scripts) return | ||
| 691 | + | ||
| 692 | + var existingScripts = $('script[src]') | ||
| 693 | + | ||
| 694 | + scripts.each(function() { | ||
| 695 | + var src = this.src | ||
| 696 | + var matchedScripts = existingScripts.filter(function() { | ||
| 697 | + return this.src === src | ||
| 698 | + }) | ||
| 699 | + if (matchedScripts.length) return | ||
| 700 | + | ||
| 701 | + var script = document.createElement('script') | ||
| 702 | + script.type = $(this).attr('type') | ||
| 703 | + script.src = $(this).attr('src') | ||
| 704 | + document.head.appendChild(script) | ||
| 705 | + }) | ||
| 706 | +} | ||
| 707 | + | ||
| 708 | +// Internal: History DOM caching class. | ||
| 709 | +var cacheMapping = {} | ||
| 710 | +var cacheForwardStack = [] | ||
| 711 | +var cacheBackStack = [] | ||
| 712 | + | ||
| 713 | +// Push previous state id and container contents into the history | ||
| 714 | +// cache. Should be called in conjunction with `pushState` to save the | ||
| 715 | +// previous container contents. | ||
| 716 | +// | ||
| 717 | +// id - State ID Number | ||
| 718 | +// value - DOM Element to cache | ||
| 719 | +// | ||
| 720 | +// Returns nothing. | ||
| 721 | +function cachePush(id, value) { | ||
| 722 | + cacheMapping[id] = value | ||
| 723 | + cacheBackStack.push(id) | ||
| 724 | + | ||
| 725 | + // Remove all entires in forward history stack after pushing | ||
| 726 | + // a new page. | ||
| 727 | + while (cacheForwardStack.length) | ||
| 728 | + delete cacheMapping[cacheForwardStack.shift()] | ||
| 729 | + | ||
| 730 | + // Trim back history stack to max cache length. | ||
| 731 | + while (cacheBackStack.length > pjax.defaults.maxCacheLength) | ||
| 732 | + delete cacheMapping[cacheBackStack.shift()] | ||
| 733 | +} | ||
| 734 | + | ||
| 735 | +// Shifts cache from directional history cache. Should be | ||
| 736 | +// called on `popstate` with the previous state id and container | ||
| 737 | +// contents. | ||
| 738 | +// | ||
| 739 | +// direction - "forward" or "back" String | ||
| 740 | +// id - State ID Number | ||
| 741 | +// value - DOM Element to cache | ||
| 742 | +// | ||
| 743 | +// Returns nothing. | ||
| 744 | +function cachePop(direction, id, value) { | ||
| 745 | + var pushStack, popStack | ||
| 746 | + cacheMapping[id] = value | ||
| 747 | + | ||
| 748 | + if (direction === 'forward') { | ||
| 749 | + pushStack = cacheBackStack | ||
| 750 | + popStack = cacheForwardStack | ||
| 751 | + } else { | ||
| 752 | + pushStack = cacheForwardStack | ||
| 753 | + popStack = cacheBackStack | ||
| 754 | + } | ||
| 755 | + | ||
| 756 | + pushStack.push(id) | ||
| 757 | + if (id = popStack.pop()) | ||
| 758 | + delete cacheMapping[id] | ||
| 759 | +} | ||
| 760 | + | ||
| 761 | +// Public: Find version identifier for the initial page load. | ||
| 762 | +// | ||
| 763 | +// Returns String version or undefined. | ||
| 764 | +function findVersion() { | ||
| 765 | + return $('meta').filter(function() { | ||
| 766 | + var name = $(this).attr('http-equiv') | ||
| 767 | + return name && name.toUpperCase() === 'X-PJAX-VERSION' | ||
| 768 | + }).attr('content') | ||
| 769 | +} | ||
| 770 | + | ||
| 771 | +// Install pjax functions on $.pjax to enable pushState behavior. | ||
| 772 | +// | ||
| 773 | +// Does nothing if already enabled. | ||
| 774 | +// | ||
| 775 | +// Examples | ||
| 776 | +// | ||
| 777 | +// $.pjax.enable() | ||
| 778 | +// | ||
| 779 | +// Returns nothing. | ||
| 780 | +function enable() { | ||
| 781 | + $.fn.pjax = fnPjax | ||
| 782 | + $.pjax = pjax | ||
| 783 | + $.pjax.enable = $.noop | ||
| 784 | + $.pjax.disable = disable | ||
| 785 | + $.pjax.click = handleClick | ||
| 786 | + $.pjax.submit = handleSubmit | ||
| 787 | + $.pjax.reload = pjaxReload | ||
| 788 | + $.pjax.defaults = { | ||
| 789 | + timeout: 650, | ||
| 790 | + push: true, | ||
| 791 | + replace: false, | ||
| 792 | + type: 'GET', | ||
| 793 | + dataType: 'html', | ||
| 794 | + scrollTo: 0, | ||
| 795 | + maxCacheLength: 20, | ||
| 796 | + version: findVersion | ||
| 797 | + } | ||
| 798 | + $(window).on('popstate.pjax', onPjaxPopstate) | ||
| 799 | +} | ||
| 800 | + | ||
| 801 | +// Disable pushState behavior. | ||
| 802 | +// | ||
| 803 | +// This is the case when a browser doesn't support pushState. It is | ||
| 804 | +// sometimes useful to disable pushState for debugging on a modern | ||
| 805 | +// browser. | ||
| 806 | +// | ||
| 807 | +// Examples | ||
| 808 | +// | ||
| 809 | +// $.pjax.disable() | ||
| 810 | +// | ||
| 811 | +// Returns nothing. | ||
| 812 | +function disable() { | ||
| 813 | + $.fn.pjax = function() { return this } | ||
| 814 | + $.pjax = fallbackPjax | ||
| 815 | + $.pjax.enable = enable | ||
| 816 | + $.pjax.disable = $.noop | ||
| 817 | + $.pjax.click = $.noop | ||
| 818 | + $.pjax.submit = $.noop | ||
| 819 | + $.pjax.reload = function() { window.location.reload() } | ||
| 820 | + | ||
| 821 | + $(window).off('popstate.pjax', onPjaxPopstate) | ||
| 822 | +} | ||
| 823 | + | ||
| 824 | + | ||
| 825 | +// Add the state property to jQuery's event object so we can use it in | ||
| 826 | +// $(window).bind('popstate') | ||
| 827 | +if ( $.inArray('state', $.event.props) < 0 ) | ||
| 828 | + $.event.props.push('state') | ||
| 829 | + | ||
| 830 | +// Is pjax supported by this browser? | ||
| 831 | +$.support.pjax = | ||
| 832 | + window.history && window.history.pushState && window.history.replaceState && | ||
| 833 | + // pushState isn't reliable on iOS until 5. | ||
| 834 | + !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/) | ||
| 835 | + | ||
| 836 | +$.support.pjax ? enable() : disable() | ||
| 837 | + | ||
| 838 | +})(jQuery); |
| @@ -0,0 +1,34 @@ | @@ -0,0 +1,34 @@ | ||
| 1 | +if (typeof loading_overlay === 'undefined') { | ||
| 2 | + | ||
| 3 | +// block user actions while making a post. Also indicate the network transaction | ||
| 4 | +loading_overlay = { | ||
| 5 | + | ||
| 6 | + show: function (selector) { | ||
| 7 | + var element = jQuery(selector); | ||
| 8 | + var overlay = jQuery('<div>', { | ||
| 9 | + class: 'loading-overlay', | ||
| 10 | + css: { | ||
| 11 | + width: element.outerWidth(), | ||
| 12 | + height: element.outerHeight(), | ||
| 13 | + left: element.position().left, | ||
| 14 | + top: element.position().top, | ||
| 15 | + marginLeft: parseFloat(element.css('margin-left')), | ||
| 16 | + marginTop: parseFloat(element.css('margin-top')), | ||
| 17 | + marginRight: parseFloat(element.css('margin-right')), | ||
| 18 | + marginBottom: parseFloat(element.css('margin-bottom')), | ||
| 19 | + }, | ||
| 20 | + }).appendTo(element).get(0); | ||
| 21 | + | ||
| 22 | + overlay.dest = element; | ||
| 23 | + element.loading_overlay = overlay; | ||
| 24 | + }, | ||
| 25 | + | ||
| 26 | + hide: function (selector) { | ||
| 27 | + var element = jQuery(selector); | ||
| 28 | + var overlay = element.find('.loading-overlay'); | ||
| 29 | + overlay.remove(); | ||
| 30 | + }, | ||
| 31 | + | ||
| 32 | +}; | ||
| 33 | + | ||
| 34 | +} |
| @@ -0,0 +1,41 @@ | @@ -0,0 +1,41 @@ | ||
| 1 | +var patch = (function () { | ||
| 2 | + /*jshint evil: true */ | ||
| 3 | + | ||
| 4 | + "use strict"; | ||
| 5 | + | ||
| 6 | + var global = new Function("return this;")(), // Get a reference to the global object | ||
| 7 | + fnProps = Object.getOwnPropertyNames(Function); // Get the own ("static") properties of the Function constructor | ||
| 8 | + | ||
| 9 | + return function (original, originalRef, patches) { | ||
| 10 | + | ||
| 11 | + var ref = global[originalRef] = original, // Maintain a reference to the original constructor as a new property on the global object | ||
| 12 | + args = [], | ||
| 13 | + newRef, // This will be the new patched constructor | ||
| 14 | + i; | ||
| 15 | + | ||
| 16 | + patches.called = patches.called || originalRef; // If we are not patching static calls just pass them through to the original function | ||
| 17 | + | ||
| 18 | + for (i = 0; i < original.length; i++) { // Match the arity of the original constructor | ||
| 19 | + args[i] = "a" + i; // Give the arguments a name (native constructors don't care, but user-defined ones will break otherwise) | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + if (patches.constructed) { // This string is evaluated to create the patched constructor body in the case that we are patching newed calls | ||
| 23 | + args.push("'use strict'; return (!!this ? " + patches.constructed + " : " + patches.called + ").apply(null, arguments);"); | ||
| 24 | + } else { // This string is evaluated to create the patched constructor body in the case that we are only patching static calls | ||
| 25 | + args.push("'use strict'; return (!!this ? new (Function.prototype.bind.apply(" + originalRef + ", [{}].concat([].slice.call(arguments))))() : " + patches.called + ".apply(null, arguments));"); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + newRef = new (Function.prototype.bind.apply(Function, [{}].concat(args)))(); // Create a new function to wrap the patched constructor | ||
| 29 | + newRef.prototype = original.prototype; // Keep a reference to the original prototype to ensure instances of the patch appear as instances of the original | ||
| 30 | + newRef.prototype.constructor = newRef; // Ensure the constructor of patched instances is the patched constructor | ||
| 31 | + | ||
| 32 | + Object.getOwnPropertyNames(ref).forEach(function (property) { // Add any "static" properties of the original constructor to the patched one | ||
| 33 | + if (fnProps.indexOf(property) < 0) { // Don't include static properties of Function since the patched constructor will already have them | ||
| 34 | + newRef[property] = ref[property]; | ||
| 35 | + } | ||
| 36 | + }); | ||
| 37 | + | ||
| 38 | + return newRef; // Return the patched constructor | ||
| 39 | + }; | ||
| 40 | + | ||
| 41 | +}()); | ||
| 0 | \ No newline at end of file | 42 | \ No newline at end of file |
| @@ -0,0 +1,179 @@ | @@ -0,0 +1,179 @@ | ||
| 1 | + | ||
| 2 | +pjax = { | ||
| 3 | + | ||
| 4 | + states: {}, | ||
| 5 | + current_state: null, | ||
| 6 | + initial_state: null, | ||
| 7 | + | ||
| 8 | + themes: {}, | ||
| 9 | + | ||
| 10 | + load: function() { | ||
| 11 | + var target = jQuery('#content'); | ||
| 12 | + var content = jQuery('#content-inner'); | ||
| 13 | + var loadingTarget = jQuery('#content'); | ||
| 14 | + | ||
| 15 | + var container = '.pjax-container'; | ||
| 16 | + target.addClass('pjax-container'); | ||
| 17 | + | ||
| 18 | + jQuery(document).pjax('a', container); | ||
| 19 | + | ||
| 20 | + jQuery(document).on('pjax:beforeSend', function(event, xhr, settings) { | ||
| 21 | + var themes = jQuery.map(pjax.themes, function(theme) { return theme.id }).join(','); | ||
| 22 | + xhr.setRequestHeader('X-PJAX-Themes', themes); | ||
| 23 | + }); | ||
| 24 | + | ||
| 25 | + jQuery(document).on('pjax:send', function(event) { | ||
| 26 | + /* initial state is only initialized after the first navigation, | ||
| 27 | + * so we do associate it here */ | ||
| 28 | + if (!pjax.states[jQuery.pjax.state.id]) | ||
| 29 | + pjax.states[jQuery.pjax.state.id] = pjax.initial_state; | ||
| 30 | + | ||
| 31 | + loading_overlay.show(loadingTarget); | ||
| 32 | + }); | ||
| 33 | + jQuery(document).on('pjax:complete', function(event) { | ||
| 34 | + loading_overlay.hide(loadingTarget); | ||
| 35 | + }); | ||
| 36 | + | ||
| 37 | + jQuery(document).on('pjax:popstate', function(event) { | ||
| 38 | + pjax.popstate(event.state, event.direction); | ||
| 39 | + }); | ||
| 40 | + | ||
| 41 | + jQuery(document).on('pjax:timeout', function(event) { | ||
| 42 | + // Prevent default timeout redirection behavior | ||
| 43 | + event.preventDefault(); | ||
| 44 | + }); | ||
| 45 | + | ||
| 46 | + pjax.patch.document_write(); | ||
| 47 | + //pjax.patch.xhr(); | ||
| 48 | + }, | ||
| 49 | + | ||
| 50 | + update: function(state, from_state) { | ||
| 51 | + if (!from_state) | ||
| 52 | + from_state = this.current_state || this.initial_state; | ||
| 53 | + | ||
| 54 | + if (state.layout_template != from_state.layout_template) { | ||
| 55 | + var lt_css = jQuery('head link[href^="/designs/templates"]'); | ||
| 56 | + lt_css.attr('href', lt_css.attr('href').replace(/templates\/.+\/stylesheets/, 'templates/'+state.layout_template+'/stylesheets')); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + if (state.theme.id != from_state.theme.id) | ||
| 60 | + this.update_theme(state, from_state); | ||
| 61 | + | ||
| 62 | + document.body.className = state.body_classes; | ||
| 63 | + | ||
| 64 | + render_all_jquery_ui_widgets(); | ||
| 65 | + | ||
| 66 | + userDataCallback(noosfero.user_data); | ||
| 67 | + | ||
| 68 | + // theme's update dependent on content. must be last thing to run | ||
| 69 | + if (state.theme_update_js) | ||
| 70 | + jQuery.globalEval(state.theme_update_js); | ||
| 71 | + | ||
| 72 | + pjax.current_state = state; | ||
| 73 | + }, | ||
| 74 | + | ||
| 75 | + update_theme: function(state, from_state) { | ||
| 76 | + // wait for the new theme css load | ||
| 77 | + this.loading.show(function() { | ||
| 78 | + return !pjax.css_loaded('/designs/themes/'+state.theme.id+'/style.css'); | ||
| 79 | + }); | ||
| 80 | + | ||
| 81 | + var css = jQuery('head link[href^="/designs/themes/'+from_state.theme.id+'/style"]'); | ||
| 82 | + css.attr('href', css.attr('href').replace(/themes\/.+\/style/, 'themes/'+state.theme.id+'/style')); | ||
| 83 | + | ||
| 84 | + jQuery('head link[rel="shortcut icon"]').attr('href', state.theme.favicon); | ||
| 85 | + | ||
| 86 | + jQuery('#theme-header').html(state.theme.header); | ||
| 87 | + jQuery('#site-title').html(state.theme.site_title); | ||
| 88 | + jQuery('#navigation ul').html(state.theme.extra_navigation); | ||
| 89 | + jQuery('#theme-footer').html(state.theme.footer); | ||
| 90 | + | ||
| 91 | + jQuery('head script[src^="/designs/themes/'+from_state.theme.id+'/theme.js"]').remove(); | ||
| 92 | + if (state.theme.js_src) { | ||
| 93 | + var script = document.createElement('script'); | ||
| 94 | + script.type = 'text/javascript', script.src = state.theme.js_src; | ||
| 95 | + document.head.appendChild(script); | ||
| 96 | + } | ||
| 97 | + }, | ||
| 98 | + | ||
| 99 | + popstate: function(state, direction) { | ||
| 100 | + state = pjax.states[state.id]; | ||
| 101 | + var from_state = pjax.states[jQuery.pjax.state.id]; | ||
| 102 | + pjax.update(state, from_state); | ||
| 103 | + }, | ||
| 104 | + | ||
| 105 | + loading: { | ||
| 106 | + repeatCallback: null, | ||
| 107 | + | ||
| 108 | + show: function(repeatCallback) { | ||
| 109 | + this.repeatCallback = repeatCallback; | ||
| 110 | + this.gears().show(); | ||
| 111 | + this.pool(); | ||
| 112 | + }, | ||
| 113 | + | ||
| 114 | + pool: function() { | ||
| 115 | + setTimeout(this.timeout, 50); | ||
| 116 | + }, | ||
| 117 | + | ||
| 118 | + timeout: function() { | ||
| 119 | + var repeat = pjax.loading.repeatCallback(); | ||
| 120 | + if (repeat) | ||
| 121 | + pjax.loading.pool(); | ||
| 122 | + else | ||
| 123 | + pjax.loading.gears().hide(); | ||
| 124 | + }, | ||
| 125 | + | ||
| 126 | + gears: function() { | ||
| 127 | + var gears = jQuery('#pjax-loading-gears'); | ||
| 128 | + if (!gears.length) { | ||
| 129 | + gears = jQuery('<div>', { | ||
| 130 | + id: 'pjax-loading-gears', | ||
| 131 | + }); | ||
| 132 | + gears.appendTo(document.body); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + return gears; | ||
| 136 | + }, | ||
| 137 | + }, | ||
| 138 | + | ||
| 139 | + css_loaded: function(path) { | ||
| 140 | + var found = false; | ||
| 141 | + for (index in document.styleSheets) { | ||
| 142 | + var stylesheet = document.styleSheets[index]; | ||
| 143 | + if (!stylesheet.href) | ||
| 144 | + continue; | ||
| 145 | + | ||
| 146 | + found = stylesheet.href.indexOf(path) != -1; | ||
| 147 | + if (found) | ||
| 148 | + break; | ||
| 149 | + } | ||
| 150 | + return found; | ||
| 151 | + }, | ||
| 152 | + | ||
| 153 | + patch: { | ||
| 154 | + | ||
| 155 | + document_write: function () { | ||
| 156 | + // document.write doesn't work after ready state | ||
| 157 | + document._write = document.write; | ||
| 158 | + document.write = function (data) { | ||
| 159 | + if (document.readyState != 'loading') | ||
| 160 | + content.append(data); | ||
| 161 | + else | ||
| 162 | + document._write(data); | ||
| 163 | + }; | ||
| 164 | + }, | ||
| 165 | + | ||
| 166 | + xhr: function () { | ||
| 167 | + XMLHttpRequest = patch(XMLHttpRequest, '_XMLHttpRequest', { | ||
| 168 | + constructed: function() { | ||
| 169 | + console.log('here') | ||
| 170 | + | ||
| 171 | + var args = [].slice.call(arguments); | ||
| 172 | + return new (Function.prototype.bind.apply(_XMLHttpRequest, [{}].concat(args))); | ||
| 173 | + }, | ||
| 174 | + }); | ||
| 175 | + }, | ||
| 176 | + | ||
| 177 | + }, | ||
| 178 | +}; | ||
| 179 | + |
| @@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
| 1 | +.loading-overlay { | ||
| 2 | + position: absolute; | ||
| 3 | + background-image: url(/plugins/pjax/images/loading-overlay.gif); | ||
| 4 | + opacity: 0.1; | ||
| 5 | + z-index: 10; } | ||
| 6 | + | ||
| 7 | +#pjax-loading-gears { | ||
| 8 | + display: none; | ||
| 9 | + position: fixed; | ||
| 10 | + top: 0; | ||
| 11 | + left: 0; | ||
| 12 | + right: 0; | ||
| 13 | + bottom: 0; | ||
| 14 | + z-index: 9999; | ||
| 15 | + background: white url(/plugins/pjax/images/loading-gears.gif) no-repeat center center; | ||
| 16 | + background-size: 5%; } |
| @@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
| 1 | +@import 'loading-overlay'; | ||
| 2 | + | ||
| 3 | +#pjax-loading-gears { | ||
| 4 | + display: none; //default | ||
| 5 | + position: fixed; | ||
| 6 | + top: 0; | ||
| 7 | + left: 0; | ||
| 8 | + right: 0; | ||
| 9 | + bottom: 0; | ||
| 10 | + z-index: 9999; | ||
| 11 | + background: white url(/plugins/pjax/images/loading-gears.gif) no-repeat center center; | ||
| 12 | + background-size: 5%; | ||
| 13 | +} | ||
| 14 | + |
plugins/pjax/vendor/plugins/xhr_status_except_pjax/init.rb
0 → 100644
| @@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
| 1 | +class ActionDispatch::Request | ||
| 2 | + | ||
| 3 | + def xml_http_request_with_pjax? | ||
| 4 | + xml_http_request_without_pjax? and @env['HTTP_X_PJAX'].blank? | ||
| 5 | + end | ||
| 6 | + | ||
| 7 | +end | ||
| 8 | + | ||
| 9 | + ActionDispatch::Request.send :alias_method_chain, :xml_http_request?, :pjax | ||
| 10 | + ActionDispatch::Request.send :alias_method, :xhr?, :xml_http_request? | ||
| 11 | + |
| @@ -0,0 +1,19 @@ | @@ -0,0 +1,19 @@ | ||
| 1 | +<% | ||
| 2 | + update_js = render(:file => "#{Rails.root}/public/designs/themes/#{current_theme}/pjax_update.js.erb").to_json rescue nil | ||
| 3 | +%> | ||
| 4 | + | ||
| 5 | +<title><%= h page_title %></title> | ||
| 6 | + | ||
| 7 | +<%= render :file => "#{Rails.root}/public/designs/themes/#{current_theme}/layouts/_content.html.erb" rescue | ||
| 8 | + render "layouts/content" %> | ||
| 9 | + | ||
| 10 | +<%= javascript_tag do %> | ||
| 11 | + <%= render 'pjax_shared/load_state.js' %> | ||
| 12 | + | ||
| 13 | + <% if update_js %> | ||
| 14 | + state.theme_update_js = <%= update_js %>; | ||
| 15 | + <% end %> | ||
| 16 | + | ||
| 17 | + pjax.update(state); | ||
| 18 | +<% end %> | ||
| 19 | + |
plugins/pjax/views/pjax_layouts/_load_state_script.html.erb
0 → 100644
| @@ -0,0 +1,26 @@ | @@ -0,0 +1,26 @@ | ||
| 1 | +var theme_id = <%= current_theme.to_json %>; | ||
| 2 | + | ||
| 3 | +<% if @pjax_loaded_themes.blank? or not @pjax_loaded_themes.include? current_theme %> | ||
| 4 | + pjax.themes[theme_id] = { | ||
| 5 | + id: theme_id, | ||
| 6 | + favicon: '<%= image_path theme_favicon %>', | ||
| 7 | + header: '<%= escape_javascript theme_header %>', | ||
| 8 | + site_title: '<%= escape_javascript theme_site_title %>', | ||
| 9 | + extra_navigation: '<%= escape_javascript theme_extra_navigation %>', | ||
| 10 | + footer: '<%= escape_javascript theme_footer %>', | ||
| 11 | + js_src: <%= theme_javascript_src.to_json %>, | ||
| 12 | + }; | ||
| 13 | +<% end %> | ||
| 14 | + | ||
| 15 | +var state = { | ||
| 16 | + body_classes: <%= body_classes.to_json %>, | ||
| 17 | + layout_template: <%= layout_template.to_json %>, | ||
| 18 | + theme: pjax.themes[theme_id], | ||
| 19 | +}; | ||
| 20 | + | ||
| 21 | +if (jQuery.pjax.state) | ||
| 22 | + pjax.states[jQuery.pjax.state.id] = state; | ||
| 23 | + | ||
| 24 | +if (!pjax.initial_state) | ||
| 25 | + pjax.initial_state = state; | ||
| 26 | + |
| @@ -0,0 +1,38 @@ | @@ -0,0 +1,38 @@ | ||
| 1 | +README - Profile Members Headlines (ProfileMembersHeadlines Plugin) | ||
| 2 | +=================================================================== | ||
| 3 | + | ||
| 4 | +ProfileMembersHeadlines is a plugin that allow users to add a block that | ||
| 5 | +displays the most recent post from members with the defined roles. | ||
| 6 | + | ||
| 7 | +The user also can configure the limit of headlines. | ||
| 8 | + | ||
| 9 | +This block will be available for all layout columns of communities and enterprises. | ||
| 10 | + | ||
| 11 | +INSTALL | ||
| 12 | +======= | ||
| 13 | + | ||
| 14 | +Enable Plugin | ||
| 15 | +------------- | ||
| 16 | + | ||
| 17 | +Also, you need to enable ProfileMembersHeadlines Plugin at you Noosfero: | ||
| 18 | + | ||
| 19 | +cd <your_noosfero_dir> | ||
| 20 | +./script/noosfero-plugins enable profile_members_headlines | ||
| 21 | + | ||
| 22 | +Active Plugin | ||
| 23 | +------------- | ||
| 24 | + | ||
| 25 | +As a Noosfero administrator user, go to administrator panel: | ||
| 26 | + | ||
| 27 | +- Click on "Enable/disable plugins" option | ||
| 28 | +- Click on "Profile Members Headlines Plugin" checkbox | ||
| 29 | + | ||
| 30 | +Running DisplayContent tests | ||
| 31 | +-------------------- | ||
| 32 | + | ||
| 33 | +$ rake test:noosfero_plugins:profile_members_headlines_plugin | ||
| 34 | + | ||
| 35 | +LICENSE | ||
| 36 | +======= | ||
| 37 | + | ||
| 38 | +See Noosfero license. |
plugins/profile_members_headlines/lib/profile_members_headlines_block.rb
0 → 100644
| @@ -0,0 +1,43 @@ | @@ -0,0 +1,43 @@ | ||
| 1 | +class ProfileMembersHeadlinesBlock < Block | ||
| 2 | + | ||
| 3 | + settings_items :interval, :type => 'integer', :default => 10 | ||
| 4 | + settings_items :limit, :type => 'integer', :default => 10 | ||
| 5 | + settings_items :navigation, :type => 'boolean', :default => true | ||
| 6 | + settings_items :filtered_roles, :type => Array, :default => [] | ||
| 7 | + | ||
| 8 | + attr_accessible :interval, :limit, :navigation, :filtered_roles | ||
| 9 | + | ||
| 10 | + def self.description | ||
| 11 | + _('Display headlines from members of a community') | ||
| 12 | + end | ||
| 13 | + | ||
| 14 | + def help | ||
| 15 | + _('This block displays one post from members of a community.') | ||
| 16 | + end | ||
| 17 | + | ||
| 18 | + include Noosfero::Plugin::HotSpot | ||
| 19 | + | ||
| 20 | + def default_title | ||
| 21 | + _('Profile members headlines') | ||
| 22 | + end | ||
| 23 | + | ||
| 24 | + def filtered_roles=(array) | ||
| 25 | + self.settings[:filtered_roles] = array.map(&:to_i).select { |r| !r.to_i.zero? } | ||
| 26 | + end | ||
| 27 | + | ||
| 28 | + def authors_list | ||
| 29 | + result = owner.members_by_role(filtered_roles).public.includes([:image,:domains,:preferred_domain,:environment]).order('updated_at DESC') | ||
| 30 | + | ||
| 31 | + result.all(:limit => limit * 5).select { |p| p.has_headline? | ||
| 32 | +}.slice(0..limit-1) | ||
| 33 | + end | ||
| 34 | + | ||
| 35 | + def content(args={}) | ||
| 36 | + block = self | ||
| 37 | + members = authors_list | ||
| 38 | + proc do | ||
| 39 | + render :file => 'blocks/headlines', :locals => { :block => block, :members => members } | ||
| 40 | + end | ||
| 41 | + end | ||
| 42 | + | ||
| 43 | +end |
plugins/profile_members_headlines/lib/profile_members_headlines_plugin.rb
0 → 100644
| @@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
| 1 | +require_dependency File.dirname(__FILE__) + '/profile_members_headlines_block' | ||
| 2 | +require 'ext/person' | ||
| 3 | + | ||
| 4 | +class ProfileMembersHeadlinesPlugin < Noosfero::Plugin | ||
| 5 | + | ||
| 6 | + def self.plugin_name | ||
| 7 | + "Profile Members Headlines Plugin" | ||
| 8 | + end | ||
| 9 | + | ||
| 10 | + def self.plugin_description | ||
| 11 | + _("A plugin that adds a block where you can display posts from members.") | ||
| 12 | + end | ||
| 13 | + | ||
| 14 | + def self.extra_blocks | ||
| 15 | + { ProfileMembersHeadlinesBlock => { :type => [Community], :position => | ||
| 16 | +['1'] }} | ||
| 17 | + end | ||
| 18 | + | ||
| 19 | + def stylesheet? | ||
| 20 | + true | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | +end |
379 Bytes
| @@ -0,0 +1,59 @@ | @@ -0,0 +1,59 @@ | ||
| 1 | +.profile-members-headlines-block { | ||
| 2 | + width: 100%; | ||
| 3 | +} | ||
| 4 | + | ||
| 5 | +.profile-members-headlines-block .headlines-container { | ||
| 6 | + height: 180px; | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +.profile-members-headlines-block .headlines-container .author, | ||
| 10 | +.profile-members-headlines-block .headlines-container .post { | ||
| 11 | + vertical-align: top; | ||
| 12 | + min-width: 150px; | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +.profile-members-headlines-block .headlines-container .author { | ||
| 16 | + position: absolute; | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +.profile-members-headlines-block .headlines-container .author p { | ||
| 20 | + text-align: center; | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +.profile-members-headlines-block .headlines-container .post { | ||
| 24 | + margin-left: 160px; | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +.profile-members-headlines-block #content .headlines-container h4 { | ||
| 28 | + margin-top: 15px; | ||
| 29 | + font-weight: bold; | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | +.profile-members-headlines-block .headlines-container a { | ||
| 33 | + text-decoration: none; | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +.profile-members-headlines-block .headlines-container a:hover { | ||
| 37 | + text-decoration: underline; | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +.profile-members-headlines-block .headlines-container .post .date { | ||
| 41 | + margin-top: 15px; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +.profile-members-headlines-block .headlines-block-pager { | ||
| 45 | + text-align: center; | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +.profile-members-headlines-block .headlines-block-pager a { | ||
| 49 | + padding: 0px 5px; | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +.profile-members-headlines-block .headlines-block-pager a.activeSlide { | ||
| 53 | + text-decoration: none; | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +.profile-members-headlines-block .headlines-block-pager a.activeSlide:visited, | ||
| 57 | +.profile-members-headlines-block .headlines-block-pager a.activeSlide { | ||
| 58 | + color: #000; | ||
| 59 | +} |
| @@ -0,0 +1 @@ | @@ -0,0 +1 @@ | ||
| 1 | +require File.dirname(__FILE__) + '/../../../test/test_helper' |
plugins/profile_members_headlines/test/unit/profile_members_headlines_block_test.rb
0 → 100644
| @@ -0,0 +1,81 @@ | @@ -0,0 +1,81 @@ | ||
| 1 | +require 'test_helper' | ||
| 2 | + | ||
| 3 | +class ProfileMembersHeadlinesBlockTest < ActiveSupport::TestCase | ||
| 4 | + | ||
| 5 | + include Noosfero::Plugin::HotSpot | ||
| 6 | + | ||
| 7 | + def setup | ||
| 8 | + @environment = fast_create(Environment) | ||
| 9 | + @environment.enable_plugin(ProfileMembersHeadlinesPlugin) | ||
| 10 | + | ||
| 11 | + @member1 = fast_create(Person) | ||
| 12 | + @member2 = fast_create(Person) | ||
| 13 | + @community = fast_create(Community) | ||
| 14 | + community.add_member member1 | ||
| 15 | + community.add_member member2 | ||
| 16 | + end | ||
| 17 | + attr_accessor :environment, :community, :member1, :member2 | ||
| 18 | + | ||
| 19 | + should 'inherit from Block' do | ||
| 20 | + assert_kind_of Block, ProfileMembersHeadlinesBlock.new | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | + should 'describe itself' do | ||
| 24 | + assert_not_equal Block.description, ProfileMembersHeadlinesBlock.description | ||
| 25 | + end | ||
| 26 | + | ||
| 27 | + should 'provide a default title' do | ||
| 28 | + assert_not_equal Block.new.default_title, ProfileMembersHeadlinesBlock.new.default_title | ||
| 29 | + end | ||
| 30 | + | ||
| 31 | + should 'not have authors if they have no blog' do | ||
| 32 | + block = ProfileMembersHeadlinesBlock.create | ||
| 33 | + block.stubs(:owner).returns(community) | ||
| 34 | + | ||
| 35 | + self.expects(:render).with(:file => 'blocks/headlines', :locals => { :block => block, :members => []}).returns('file-without-authors-and-headlines') | ||
| 36 | + assert_equal 'file-without-authors-and-headlines', instance_eval(&block.content) | ||
| 37 | + end | ||
| 38 | + | ||
| 39 | + should 'display headlines file' do | ||
| 40 | + block = ProfileMembersHeadlinesBlock.create | ||
| 41 | + block.stubs(:owner).returns(community) | ||
| 42 | + blog = fast_create(Blog, :profile_id => member1.id) | ||
| 43 | + post = fast_create(TinyMceArticle, :name => 'headlines', :profile_id => member1.id, :parent_id => blog.id) | ||
| 44 | + self.expects(:render).with(:file => 'blocks/headlines', :locals => { :block => block, :members => []}).returns('file-with-authors-and-headlines') | ||
| 45 | + assert_equal 'file-with-authors-and-headlines', instance_eval(&block.content) | ||
| 46 | + end | ||
| 47 | + | ||
| 48 | + should 'select only authors with articles and selected roles to display' do | ||
| 49 | + role = Role.create!(:name => 'role1') | ||
| 50 | + community.affiliate(member1, role) | ||
| 51 | + block = ProfileMembersHeadlinesBlock.new(:limit => 1, :filtered_roles => [role.id]) | ||
| 52 | + block.expects(:owner).returns(community) | ||
| 53 | + blog = fast_create(Blog, :profile_id => member1.id) | ||
| 54 | + post = fast_create(TinyMceArticle, :name => 'headlines', :profile_id => member1.id, :parent_id => blog.id) | ||
| 55 | + assert_equal [member1], block.authors_list | ||
| 56 | + end | ||
| 57 | + | ||
| 58 | + should 'not select private authors to display' do | ||
| 59 | + block = ProfileMembersHeadlinesBlock.new(:limit => 1) | ||
| 60 | + block.expects(:owner).returns(community) | ||
| 61 | + private_author = fast_create(Person, :public_profile => false) | ||
| 62 | + blog = fast_create(Blog, :profile_id => private_author.id) | ||
| 63 | + post = fast_create(TinyMceArticle, :name => 'headlines', :profile_id => private_author.id, :parent_id => blog.id) | ||
| 64 | + assert_equal [], block.authors_list | ||
| 65 | + end | ||
| 66 | + | ||
| 67 | + should 'filter authors by roles to display' do | ||
| 68 | + role = Role.create!(:name => 'role1') | ||
| 69 | + author = fast_create(Person) | ||
| 70 | + community.affiliate(author, role) | ||
| 71 | + | ||
| 72 | + block = ProfileMembersHeadlinesBlock.new(:limit => 3, :filtered_roles => | ||
| 73 | +[role.id]) | ||
| 74 | + block.stubs(:owner).returns(community) | ||
| 75 | + community.members.each do |member| | ||
| 76 | + blog = fast_create(Blog, :profile_id => member.id) | ||
| 77 | + post = fast_create(TinyMceArticle, :name => 'headlines', :profile_id => member.id, :parent_id => blog.id) | ||
| 78 | + end | ||
| 79 | + assert_equal [author], block.authors_list | ||
| 80 | + end | ||
| 81 | +end |
plugins/profile_members_headlines/test/unit/profile_members_headlines_plugin_test.rb
0 → 100644
| @@ -0,0 +1,41 @@ | @@ -0,0 +1,41 @@ | ||
| 1 | +require 'test_helper' | ||
| 2 | + | ||
| 3 | +class ProfileMembersHeadlinesPluginTest < ActiveSupport::TestCase | ||
| 4 | + | ||
| 5 | + include Noosfero::Plugin::HotSpot | ||
| 6 | + | ||
| 7 | + def setup | ||
| 8 | + @environment = fast_create(Environment) | ||
| 9 | + @environment.enable_plugin(ProfileMembersHeadlinesPlugin) | ||
| 10 | + end | ||
| 11 | + attr_accessor :environment | ||
| 12 | + | ||
| 13 | + should 'has a name' do | ||
| 14 | + assert_not_equal Noosfero::Plugin.plugin_name, ProfileMembersHeadlinesPlugin.plugin_name | ||
| 15 | + end | ||
| 16 | + | ||
| 17 | + should 'describe itself' do | ||
| 18 | + assert_not_equal Noosfero::Plugin.plugin_description, ProfileMembersHeadlinesPlugin.plugin_description | ||
| 19 | + end | ||
| 20 | + | ||
| 21 | + should 'return ProfileMembersHeadlinesBlock in extra_blocks class method' do | ||
| 22 | + assert ProfileMembersHeadlinesPlugin.extra_blocks.keys.include?(ProfileMembersHeadlinesBlock) | ||
| 23 | + end | ||
| 24 | + | ||
| 25 | + should 'ProfileMembersHeadlinesBlock not available for environment' do | ||
| 26 | + assert_not_includes plugins.dispatch(:extra_blocks, :type => Environment), ProfileMembersHeadlinesBlock | ||
| 27 | + end | ||
| 28 | + | ||
| 29 | + should 'ProfileMembersHeadlinesBlock not available for people' do | ||
| 30 | + assert_not_includes plugins.dispatch(:extra_blocks, :type => Person), ProfileMembersHeadlinesBlock | ||
| 31 | + end | ||
| 32 | + | ||
| 33 | + should "ProfileMembersHeadlinesBlock be available for community" do | ||
| 34 | + assert_includes plugins.dispatch(:extra_blocks, :type => Community), ProfileMembersHeadlinesBlock | ||
| 35 | + end | ||
| 36 | + | ||
| 37 | + should 'has stylesheet' do | ||
| 38 | + assert ProfileMembersHeadlinesPlugin.new.stylesheet? | ||
| 39 | + end | ||
| 40 | + | ||
| 41 | +end |
plugins/profile_members_headlines/views/blocks/headlines.html.erb
0 → 100644
| @@ -0,0 +1,39 @@ | @@ -0,0 +1,39 @@ | ||
| 1 | +<%= block_title(block.title) %> | ||
| 2 | + | ||
| 3 | +<% unless members.empty? %> | ||
| 4 | + <div class='headlines-container'> | ||
| 5 | + <% members.each do |member| %> | ||
| 6 | + <div> | ||
| 7 | + <% headline = member.headline %> | ||
| 8 | + <%= link_to_profile(profile_image(member, :big) + content_tag(:p, member.short_name), member.identifier, {:class => 'author'}) %> | ||
| 9 | + <div class='post'> | ||
| 10 | + <h4><%= link_to(headline.title, headline.url, :class => 'title') %></h4> | ||
| 11 | + <div class='lead'> | ||
| 12 | + <%= headline.short_lead %> | ||
| 13 | + </div> | ||
| 14 | + <div class='date'> | ||
| 15 | + <%= show_date(headline.published_at) %> | ||
| 16 | + </div> | ||
| 17 | + <div class='tags'> | ||
| 18 | + <%= headline.tags.map { |t| link_to(t, :controller => 'profile', :profile => member.identifier, :action => 'tags', :id => t.name ) }.join("\n") %> | ||
| 19 | + </div> | ||
| 20 | + </div> | ||
| 21 | + </div> | ||
| 22 | + <% end %> | ||
| 23 | + </div> | ||
| 24 | + <% if block.navigation %> | ||
| 25 | + <div class='headlines-block-pager'> | ||
| 26 | + </div> | ||
| 27 | + <% end %> | ||
| 28 | + | ||
| 29 | + <script> | ||
| 30 | + (function($) { | ||
| 31 | + var options = {fx: 'fade', pause: 1, fastOnEvent: 1, timeout: <%= block.interval * 1000 %>}; | ||
| 32 | + options.pager = '#block-<%= block.id %> .headlines-block-pager'; | ||
| 33 | + $('#block-<%= block.id %> .headlines-container').cycle(options); | ||
| 34 | + })(jQuery); | ||
| 35 | + </script> | ||
| 36 | +<% else %> | ||
| 37 | + <em><%= _('No headlines to be shown.') %></em> | ||
| 38 | +<% end %> | ||
| 39 | + |
plugins/profile_members_headlines/views/box_organizer/_profile_members_headlines_block.html.erb
0 → 100644
| @@ -0,0 +1,13 @@ | @@ -0,0 +1,13 @@ | ||
| 1 | +<div id="profile_members_headlines_plugin"> | ||
| 2 | + | ||
| 3 | + <%= labelled_form_field _('Headlines transition:'), select('block', 'interval', [[_('No automatic transition'), 0]] + [1, 2, 3, 4, 5, 10, 20, 30, 60].map {|item| [n_('Every 1 second', 'Every %d seconds', item) % item, item]}) %> | ||
| 4 | + | ||
| 5 | + <%= labelled_form_field check_box(:block, :navigation) + _('Display navigation buttons'), '' %> | ||
| 6 | + <%= labelled_form_field _('Limit of headlines'), text_field(:block, :limit, :size => 3) %> | ||
| 7 | + | ||
| 8 | + <%= label :block, :filtered_roles, _('Choose which roles should be displayed:'), :class => 'formlabel' %> | ||
| 9 | + | ||
| 10 | + <% Profile::Roles.organization_member_roles(environment).each do |role| %> | ||
| 11 | + <%= labelled_check_box(role.name, 'block[filtered_roles][]', role.id, @block.filtered_roles.include?(role.id)) %> | ||
| 12 | + <% end %> | ||
| 13 | +</div> |
public/500.html
| @@ -105,11 +105,11 @@ | @@ -105,11 +105,11 @@ | ||
| 105 | <div id='pt' style='display: none' class='message'> | 105 | <div id='pt' style='display: none' class='message'> |
| 106 | <h1>Problema temporário no sistema</h1> | 106 | <h1>Problema temporário no sistema</h1> |
| 107 | <p> | 107 | <p> |
| 108 | - Nossa equipe técnica está trabalhando nele, por favor tente mais tarde. Perdoe o incoveniente. | 108 | + Nossa equipe técnica está trabalhando nele, por favor tente mais tarde. Perdoe o inconveniente. |
| 109 | </p> | 109 | </p> |
| 110 | <ul> | 110 | <ul> |
| 111 | <li><a href='javascript: history.back()'>Voltar</a></li> | 111 | <li><a href='javascript: history.back()'>Voltar</a></li> |
| 112 | - <li><a href='/'>Ir para a página inicial do site.</a></li> | 112 | + <li><a href='/'>Ir para a página inicial do site</a></li> |
| 113 | </ul> | 113 | </ul> |
| 114 | </div> | 114 | </div> |
| 115 | 115 |
public/javascripts/application.js
| @@ -518,6 +518,21 @@ function new_qualifier_row(selector, select_qualifiers, delete_button) { | @@ -518,6 +518,21 @@ function new_qualifier_row(selector, select_qualifiers, delete_button) { | ||
| 518 | jQuery(selector).append("<tr><td>" + select_qualifiers + "</td><td id='certifier-area-" + index + "'><select></select>" + delete_button + "</td></tr>"); | 518 | jQuery(selector).append("<tr><td>" + select_qualifiers + "</td><td id='certifier-area-" + index + "'><select></select>" + delete_button + "</td></tr>"); |
| 519 | } | 519 | } |
| 520 | 520 | ||
| 521 | +function userDataCallback(data) { | ||
| 522 | + noosfero.user_data = data; | ||
| 523 | + if (data.login) { | ||
| 524 | + jQuery('head').append('<meta content="authenticity_token" name="csrf-param" />'); | ||
| 525 | + jQuery('head').append('<meta content="'+jQuery.cookie("_noosfero_.XSRF-TOKEN")+'" name="csrf-token" />'); | ||
| 526 | + } | ||
| 527 | + if (data.notice) { | ||
| 528 | + display_notice(data.notice); | ||
| 529 | + // clear notice so that it is not display again in the case this function is called again. | ||
| 530 | + data.notice = null; | ||
| 531 | + } | ||
| 532 | + // Bind this event to do more actions with the user data (for example, inside plugins) | ||
| 533 | + jQuery(window).trigger("userDataLoaded", data); | ||
| 534 | +}; | ||
| 535 | + | ||
| 521 | // controls the display of the login/logout stuff | 536 | // controls the display of the login/logout stuff |
| 522 | jQuery(function($) { | 537 | jQuery(function($) { |
| 523 | $.ajaxSetup({ | 538 | $.ajaxSetup({ |
| @@ -528,18 +543,9 @@ jQuery(function($) { | @@ -528,18 +543,9 @@ jQuery(function($) { | ||
| 528 | }); | 543 | }); |
| 529 | 544 | ||
| 530 | var user_data = noosfero_root() + '/account/user_data'; | 545 | var user_data = noosfero_root() + '/account/user_data'; |
| 531 | - $.getJSON(user_data, function userDataCallBack(data) { | ||
| 532 | - if (data.login) { | ||
| 533 | - $('head').append('<meta content="authenticity_token" name="csrf-param" />'); | ||
| 534 | - $('head').append('<meta content="'+$.cookie("_noosfero_.XSRF-TOKEN")+'" name="csrf-token" />'); | ||
| 535 | - } | ||
| 536 | - if (data.notice) { | ||
| 537 | - display_notice(data.notice); | ||
| 538 | - } | ||
| 539 | - // Bind this event to do more actions with the user data (for example, inside plugins) | ||
| 540 | - $(window).trigger("userDataLoaded", data); | ||
| 541 | - }); | 546 | + $.getJSON(user_data, userDataCallback) |
| 542 | 547 | ||
| 548 | + $.ajaxSetup({ cache: false }); | ||
| 543 | }); | 549 | }); |
| 544 | 550 | ||
| 545 | // controls the display of contact list | 551 | // controls the display of contact list |
| @@ -582,12 +588,9 @@ function display_notice(message) { | @@ -582,12 +588,9 @@ function display_notice(message) { | ||
| 582 | } | 588 | } |
| 583 | 589 | ||
| 584 | function open_chat_window(self_link, anchor) { | 590 | function open_chat_window(self_link, anchor) { |
| 585 | - if(anchor) { | ||
| 586 | - jQuery('#chat').show('fast'); | ||
| 587 | - jQuery("#chat" ).trigger('opengroup', anchor); | ||
| 588 | - } else { | ||
| 589 | - jQuery('#chat').toggle('fast'); | ||
| 590 | - } | 591 | + anchor = anchor || '#'; |
| 592 | + var noosfero_chat_window = window.open(noosfero_root() + '/chat' + anchor,'noosfero_chat','width=900,height=500'); | ||
| 593 | + noosfero_chat_window.focus(); | ||
| 591 | return false; | 594 | return false; |
| 592 | } | 595 | } |
| 593 | 596 | ||
| @@ -1050,7 +1053,7 @@ jQuery(document).ready(function(){ | @@ -1050,7 +1053,7 @@ jQuery(document).ready(function(){ | ||
| 1050 | function apply_zoom_to_images(zoom_text) { | 1053 | function apply_zoom_to_images(zoom_text) { |
| 1051 | jQuery(function($) { | 1054 | jQuery(function($) { |
| 1052 | $(window).load( function() { | 1055 | $(window).load( function() { |
| 1053 | - $('#article .article-body img:not(.disable-zoom)').each( function(index) { | 1056 | + $('#article .article-body img').each( function(index) { |
| 1054 | var original = original_image_dimensions($(this).attr('src')); | 1057 | var original = original_image_dimensions($(this).attr('src')); |
| 1055 | if ($(this).width() < original['width'] || $(this).height() < original['height']) { | 1058 | if ($(this).width() < original['width'] || $(this).height() < original['height']) { |
| 1056 | $(this).wrap('<div class="zoomable-image" />'); | 1059 | $(this).wrap('<div class="zoomable-image" />'); |
| @@ -1069,41 +1072,3 @@ function apply_zoom_to_images(zoom_text) { | @@ -1069,41 +1072,3 @@ function apply_zoom_to_images(zoom_text) { | ||
| 1069 | }); | 1072 | }); |
| 1070 | }); | 1073 | }); |
| 1071 | } | 1074 | } |
| 1072 | - | ||
| 1073 | -function getQueryParams(qs) { | ||
| 1074 | - qs = qs.split("+").join(" "); | ||
| 1075 | - var params = {}, | ||
| 1076 | - tokens, | ||
| 1077 | - re = /[?&]?([^=]+)=([^&]*)/g; | ||
| 1078 | - while (tokens = re.exec(qs)) { | ||
| 1079 | - params[decodeURIComponent(tokens[1])] | ||
| 1080 | - = decodeURIComponent(tokens[2]); | ||
| 1081 | - } | ||
| 1082 | - return params; | ||
| 1083 | -} | ||
| 1084 | - | ||
| 1085 | -var fullwidth=false; | ||
| 1086 | -function toggle_fullwidth(itemId){ | ||
| 1087 | - if(fullwidth){ | ||
| 1088 | - jQuery(itemId).removeClass("fullwidth"); | ||
| 1089 | - jQuery("#fullscreen-btn").show() | ||
| 1090 | - jQuery("#exit-fullscreen-btn").hide() | ||
| 1091 | - fullwidth = false; | ||
| 1092 | - } | ||
| 1093 | - else{ | ||
| 1094 | - jQuery(itemId).addClass("fullwidth"); | ||
| 1095 | - jQuery("#exit-fullscreen-btn").show() | ||
| 1096 | - jQuery("#fullscreen-btn").hide() | ||
| 1097 | - fullwidth = true; | ||
| 1098 | - } | ||
| 1099 | - jQuery(window).trigger("toggleFullwidth", fullwidth); | ||
| 1100 | -} | ||
| 1101 | - | ||
| 1102 | -function fullscreenPageLoad(itemId){ | ||
| 1103 | - jQuery(document).ready(function(){ | ||
| 1104 | - var $_GET = getQueryParams(document.location.search); | ||
| 1105 | - if ($_GET['fullscreen']==1){ | ||
| 1106 | - toggle_fullwidth(itemId); | ||
| 1107 | - } | ||
| 1108 | - }); | ||
| 1109 | -} |
test/unit/organization_test.rb
| @@ -383,6 +383,33 @@ class OrganizationTest < ActiveSupport::TestCase | @@ -383,6 +383,33 @@ class OrganizationTest < ActiveSupport::TestCase | ||
| 383 | assert !organization.errors[:cnpj.to_s].present? | 383 | assert !organization.errors[:cnpj.to_s].present? |
| 384 | end | 384 | end |
| 385 | 385 | ||
| 386 | + should 'get members by role' do | ||
| 387 | + community = fast_create(Community) | ||
| 388 | + role1 = Role.create!(:name => 'role1') | ||
| 389 | + person1 = fast_create(Person) | ||
| 390 | + community.affiliate(person1, role1) | ||
| 391 | + role2 = Role.create!(:name => 'role2') | ||
| 392 | + person2 = fast_create(Person) | ||
| 393 | + community.affiliate(person2, role2) | ||
| 394 | + | ||
| 395 | + assert_equal [person1], community.members_by_role([role1]) | ||
| 396 | + end | ||
| 397 | + | ||
| 398 | + should 'get members by more than one role' do | ||
| 399 | + community = fast_create(Community) | ||
| 400 | + role1 = Role.create!(:name => 'role1') | ||
| 401 | + person1 = fast_create(Person) | ||
| 402 | + community.affiliate(person1, role1) | ||
| 403 | + role2 = Role.create!(:name => 'role2') | ||
| 404 | + person2 = fast_create(Person) | ||
| 405 | + community.affiliate(person2, role2) | ||
| 406 | + role3 = Role.create!(:name => 'role3') | ||
| 407 | + person3 = fast_create(Person) | ||
| 408 | + community.affiliate(person3, role3) | ||
| 409 | + | ||
| 410 | + assert_equal [person2, person3], community.members_by_role([role2, role3]) | ||
| 411 | + end | ||
| 412 | + | ||
| 386 | should 'return members by role in a json format' do | 413 | should 'return members by role in a json format' do |
| 387 | organization = fast_create(Organization) | 414 | organization = fast_create(Organization) |
| 388 | p1 = create_user('person-1').person | 415 | p1 = create_user('person-1').person |
vendor/plugins/noosfero_caching/init.rb
| @@ -49,11 +49,11 @@ module NoosferoHttpCaching | @@ -49,11 +49,11 @@ module NoosferoHttpCaching | ||
| 49 | 49 | ||
| 50 | # filter off all cookies except for plugin-provided ones that are | 50 | # filter off all cookies except for plugin-provided ones that are |
| 51 | # path-specific (i.e path != "/"). | 51 | # path-specific (i.e path != "/"). |
| 52 | - def remove_unwanted_cookies(cookie_list) | ||
| 53 | - return nil if cookie_list.nil? | ||
| 54 | - cookie_list.select do |c| | 52 | + def remove_unwanted_cookies(set_cookie) |
| 53 | + return nil if set_cookie.nil? | ||
| 54 | + set_cookie.split(/\s*,\s*/).select do |c| | ||
| 55 | c =~ /^_noosfero_plugin_\w+=/ && c =~ /path=\/\w+/ | 55 | c =~ /^_noosfero_plugin_\w+=/ && c =~ /path=\/\w+/ |
| 56 | - end | 56 | + end.join(', ') |
| 57 | end | 57 | end |
| 58 | 58 | ||
| 59 | end | 59 | end |
| @@ -61,7 +61,7 @@ module NoosferoHttpCaching | @@ -61,7 +61,7 @@ module NoosferoHttpCaching | ||
| 61 | end | 61 | end |
| 62 | 62 | ||
| 63 | unless Rails.env.development? | 63 | unless Rails.env.development? |
| 64 | - middleware = Rails.application.config.middleware | 64 | + middleware = Noosfero::Application.config.middleware |
| 65 | ActionController::Base.send(:include, NoosferoHttpCaching) | 65 | ActionController::Base.send(:include, NoosferoHttpCaching) |
| 66 | - middleware.use NoosferoHttpCaching::Middleware | 66 | + middleware.insert_before ::ActionDispatch::Cookies, NoosferoHttpCaching::Middleware |
| 67 | end | 67 | end |