Commit 12b82c088c310e5100cd5c7861709b019da19b97
1 parent
c892d918
Exists in
master
and in
29 other branches
Added RSS feed per tag
* Modularized RSS link in head code; added add_rss_feed_to_head helper method * Modularized RSS generation in FeedWriter class + Refactored RssFeed to use FeedWriter + removed feed_item_description (this fixes ActionItem1463) (ActionItem1518)
Showing
17 changed files
with
130 additions
and
92 deletions
Show diff stats
app/controllers/public/profile_controller.rb
... | ... | @@ -29,6 +29,19 @@ class ProfileController < PublicController |
29 | 29 | end |
30 | 30 | end |
31 | 31 | |
32 | + def tag_feed | |
33 | + @tag = params[:id] | |
34 | + tagged = profile.find_tagged_with(@tag).paginate(:per_page => 20, :page => 1) | |
35 | + feed_writer = FeedWriter.new | |
36 | + data = feed_writer.write( | |
37 | + tagged, | |
38 | + :title => _("%s's contents tagged with \"%s\"") % [profile.name, @tag], | |
39 | + :description => _("%s's contents tagged with \"%s\"") % [profile.name, @tag], | |
40 | + :link => url_for(:action => 'tag') | |
41 | + ) | |
42 | + render :text => data, :content_type => "text/xml" | |
43 | + end | |
44 | + | |
32 | 45 | def communities |
33 | 46 | if is_cache_expired?(profile.communities_cache_key(params)) |
34 | 47 | @communities = profile.communities.paginate(:per_page => per_page, :page => params[:npage]) | ... | ... |
app/helpers/application_helper.rb
... | ... | @@ -869,12 +869,9 @@ module ApplicationHelper |
869 | 869 | article_helper.cms_label_for_edit |
870 | 870 | end |
871 | 871 | |
872 | - def meta_tags_for_article(article) | |
873 | - if @controller.controller_name == 'content_viewer' | |
874 | - if article and (article.blog? or (article.parent and article.parent.blog?)) | |
875 | - blog = article.blog? ? article : article.parent | |
876 | - "<link rel='alternate' type='application/rss+xml' title='#{blog.feed.title}' href='#{url_for blog.feed.url}' />" | |
877 | - end | |
872 | + def add_rss_feed_to_head(title, url) | |
873 | + content_for :feeds do | |
874 | + "<link rel='alternate' type='application/rss+xml' title='#{h(title)}' href='#{url_for(url)}' />" | |
878 | 875 | end |
879 | 876 | end |
880 | 877 | ... | ... |
app/models/blog.rb
... | ... | @@ -6,7 +6,7 @@ class Blog < Folder |
6 | 6 | attr_accessor :filter |
7 | 7 | |
8 | 8 | after_create do |blog| |
9 | - blog.children << RssFeed.new(:name => 'feed', :profile => blog.profile, :feed_item_description => 'body') | |
9 | + blog.children << RssFeed.new(:name => 'feed', :profile => blog.profile) | |
10 | 10 | blog.feed = blog.feed_attrs |
11 | 11 | end |
12 | 12 | ... | ... |
app/models/rss_feed.rb
... | ... | @@ -41,16 +41,6 @@ class RssFeed < Article |
41 | 41 | end |
42 | 42 | validates_inclusion_of :include, :in => [ 'all', 'parent_and_children' ], :if => :include |
43 | 43 | |
44 | - # determinates what to include in the feed as items' description. Possible | |
45 | - # values are +:body+ (default) and +:abstract+. | |
46 | - def feed_item_description | |
47 | - settings[:feed_item_description] | |
48 | - end | |
49 | - def feed_item_description=(value) | |
50 | - settings[:feed_item_description] = value | |
51 | - end | |
52 | - validates_inclusion_of :feed_item_description, :in => [ 'body', 'abstract' ], :if => :feed_item_description | |
53 | - | |
54 | 44 | # TODO |
55 | 45 | def to_html(options = {}) |
56 | 46 | end |
... | ... | @@ -74,38 +64,13 @@ class RssFeed < Article |
74 | 64 | end |
75 | 65 | end |
76 | 66 | def data |
77 | - articles = fetch_articles | |
78 | - result = "" | |
79 | - xml = Builder::XmlMarkup.new(:target => result) | |
80 | - | |
81 | - xml.instruct! :xml, :version=>"1.0" | |
82 | - xml.rss(:version=>"2.0") do | |
83 | - xml.channel do | |
84 | - xml.title(_("%s's RSS feed") % (self.profile.name)) | |
85 | - xml.link(url_for(self.profile.url)) | |
86 | - xml.description(_("%s's content published at %s") % [self.profile.name, self.profile.environment.name]) | |
87 | - xml.language("pt_BR") | |
88 | - for article in articles | |
89 | - unless self == article | |
90 | - xml.item do | |
91 | - xml.title(article.name) | |
92 | - if self.feed_item_description == 'body' | |
93 | - xml.description(article.to_html) | |
94 | - else | |
95 | - xml.description(article.abstract) | |
96 | - end | |
97 | - # rfc822 | |
98 | - xml.pubDate(article.created_at.rfc2822) | |
99 | - # link to article | |
100 | - xml.link(url_for(article.url)) | |
101 | - xml.guid(url_for(article.url)) | |
102 | - end | |
103 | - end | |
104 | - end | |
105 | - end | |
106 | - end | |
107 | - | |
108 | - result | |
67 | + articles = fetch_articles.select { |a| a != self } | |
68 | + FeedWriter.new.write( | |
69 | + articles, | |
70 | + :title => _("%s's RSS feed") % (self.profile.name), | |
71 | + :description => _("%s's content published at %s") % [self.profile.name, self.profile.environment.name], | |
72 | + :link => url_for(self.profile.url) | |
73 | + ) | |
109 | 74 | end |
110 | 75 | |
111 | 76 | def self.short_description | ... | ... |
app/views/cms/_blog.rhtml
... | ... | @@ -56,7 +56,6 @@ |
56 | 56 | |
57 | 57 | <% f.fields_for 'feed', @article.feed do |feed| %> |
58 | 58 | <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %> |
59 | - <%= labelled_form_field(_('Use as description in RSS Feed:'), feed.select(:feed_item_description, [ [ _('Article body'), 'body'], [ _('Article abstract'), 'abstract'] ])) %> | |
60 | 59 | <% end %> |
61 | 60 | |
62 | 61 | <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %> | ... | ... |
app/views/content_viewer/blog_page.rhtml
1 | +<% add_rss_feed_to_head(article.name, article.feed.url) if article.blog? && article.feed %> | |
2 | + | |
1 | 3 | <%= content_tag('em', _('(external feed was not loaded yet)'), :id => 'external-feed-info', :class => 'metadata') if article.blog? && article.external_feed && article.external_feed.enabled && article.external_feed.fetched_at.nil? %> |
2 | 4 | |
3 | 5 | <div> | ... | ... |
app/views/content_viewer/view_page.rhtml
app/views/layouts/application-ng.rhtml
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%= html_language %>" lang="<%= html_language %>"> |
3 | 3 | <head> |
4 | 4 | <title><%= h page_title %></title> |
5 | - <%= meta_tags_for_article(@page) %> | |
5 | + <%= yield(:feeds) %> | |
6 | 6 | <!--<meta http-equiv="refresh" content="1"/>--> |
7 | 7 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> |
8 | 8 | <meta name="description" content="<%= @environment.name %>" /> | ... | ... |
app/views/layouts/application.rhtml
... | ... | @@ -6,7 +6,7 @@ |
6 | 6 | <meta name="description" content="<%= @environment.name %>" /> |
7 | 7 | <meta name="keywords" content="Noosfero, Community, Open Source" /> |
8 | 8 | <link rel="shortcut icon" href="<%= image_path('/designs/themes/' + current_theme + '/favicon.ico') %>" type="image/x-icon" /> |
9 | - <!-- ok --><%= meta_tags_for_article(@page) %> | |
9 | + <%= yield(:feeds) %> | |
10 | 10 | |
11 | 11 | <%= noosfero_javascript %> |
12 | 12 | <%= javascript_include_tag 'menu', 'auto-open-menu', 'menu-config', :cache => 'cache-menu' %> | ... | ... |
app/views/profile/tag.rhtml
1 | +<% add_rss_feed_to_head(_("%s's contents tagged with \"%s\"") % [profile.name, @tag], tag_feed_path) %> | |
2 | + | |
1 | 3 | <h1><%= _('Content tagged with "%s"') % @tag %></h1> |
2 | 4 | |
5 | +<p> | |
6 | +<%= link_to image_tag('icons-mime/rss-feed.png', :alt => _('Feed for this tag'), :title => _('Feed for this tag')), tag_feed_path, :class => 'blog-feed-link'%> | |
7 | +</p> | |
8 | + | |
3 | 9 | <% cache_timeout(@tag_cache_key, 4.hour.from_now) do %> |
4 | 10 | <div class='search-tagged-items'> |
5 | 11 | <ul> | ... | ... |
config/routes.rb
... | ... | @@ -65,6 +65,9 @@ ActionController::Routing::Routes.draw do |map| |
65 | 65 | # invite |
66 | 66 | map.invite 'profile/:profile/invite/:action', :controller => 'invite', :profile => /#{Noosfero.identifier_format}/ |
67 | 67 | |
68 | + # feeds per tag | |
69 | + map.tag_feed 'profile/:profile/tag/:id/feed', :controller => 'profile', :action =>'tag_feed', :id => /.*/, :profile => /#{Noosfero.identifier_format}/ | |
70 | + | |
68 | 71 | # public profile information |
69 | 72 | map.profile 'profile/:profile/:action/:id', :controller => 'profile', :action => 'index', :id => /.*/, :profile => /#{Noosfero.identifier_format}/ |
70 | 73 | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +Feature: profile tags | |
2 | + As a Noosfero user | |
3 | + I want to to view content tagged | |
4 | + So that I can follow the subjects I care about | |
5 | + | |
6 | + Background: | |
7 | + Given the following users | |
8 | + | login | | |
9 | + | terceiro | | |
10 | + And the following articles | |
11 | + | owner | name | body | tag_list | | |
12 | + | terceiro | text 1 | text 1 content | tag1, tag2 | | |
13 | + | terceiro | text 2 | text 2 content | tag1, tag3 | | |
14 | + | |
15 | + Scenario: tag feed | |
16 | + When I go to terceiro's profile | |
17 | + And I follow "tag1" | |
18 | + And I follow "Feed for this tag" | |
19 | + Then I should see "text 1" | |
20 | + And I should see "text 2" | ... | ... |
features/support/paths.rb
... | ... | @@ -30,6 +30,9 @@ module NavigationHelpers |
30 | 30 | when /^(.*)'s sitemap/ |
31 | 31 | '/profile/%s/sitemap' % Profile.find_by_name($1).identifier |
32 | 32 | |
33 | + when /^(.*)'s profile/ | |
34 | + '/profile/%s' % Profile.find_by_name($1).identifier | |
35 | + | |
33 | 36 | when /^login page$/ |
34 | 37 | '/account/login' |
35 | 38 | ... | ... |
... | ... | @@ -0,0 +1,37 @@ |
1 | +class FeedWriter | |
2 | + | |
3 | + include ActionController::UrlWriter | |
4 | + | |
5 | + def write(articles, options = {}) | |
6 | + result = "" | |
7 | + xml = Builder::XmlMarkup.new(:target => result) | |
8 | + | |
9 | + xml.instruct! :xml, :version=>"1.0" | |
10 | + xml.rss(:version=>"2.0") do | |
11 | + xml.channel do | |
12 | + xml.title(options[:title] || _('Feed')) | |
13 | + if options[:link] | |
14 | + xml.link(options[:link]) | |
15 | + end | |
16 | + if options[:description] | |
17 | + xml.description(options[:description]) | |
18 | + end | |
19 | + for article in articles | |
20 | + xml.item do | |
21 | + xml.title(article.name) | |
22 | + xml.description(article.to_html) | |
23 | + if article.created_at | |
24 | + # rfc822 | |
25 | + xml.pubDate(article.created_at.rfc2822) | |
26 | + end | |
27 | + # link to article | |
28 | + xml.link(url_for(article.url)) | |
29 | + xml.guid(url_for(article.url)) | |
30 | + end | |
31 | + end | |
32 | + end | |
33 | + end | |
34 | + | |
35 | + result | |
36 | + end | |
37 | +end | ... | ... |
test/unit/blog_test.rb
... | ... | @@ -32,12 +32,6 @@ class BlogTest < ActiveSupport::TestCase |
32 | 32 | assert_kind_of RssFeed, b.feed |
33 | 33 | end |
34 | 34 | |
35 | - should 'include articles body in feed by default' do | |
36 | - p = create_user('testuser').person | |
37 | - b = Blog.create!(:profile => p, :name => 'blog_feed_test') | |
38 | - assert_equal 'body', b.feed.feed_item_description | |
39 | - end | |
40 | - | |
41 | 35 | should 'save feed options' do |
42 | 36 | p = create_user('testuser').person |
43 | 37 | p.articles << Blog.new(:profile => p, :name => 'blog_feed_test') | ... | ... |
... | ... | @@ -0,0 +1,26 @@ |
1 | +require File.dirname(__FILE__) + '/../test_helper' | |
2 | + | |
3 | +class FeedWriterTest < Test::Unit::TestCase | |
4 | + | |
5 | + should 'generate feeds' do | |
6 | + articles = [] | |
7 | + profile = fast_create(:profile, :identifier => "tagger") | |
8 | + articles << fast_create(:article, :name => 'text 1', :slug => 'text-1', :path => 'text-1', :profile_id => profile.id) | |
9 | + articles << fast_create(:article, :name => 'text 2', :slug => 'text-2', :path => 'text-2', :profile_id => profile.id) | |
10 | + writer = FeedWriter.new | |
11 | + | |
12 | + feed = writer.write(articles) | |
13 | + assert_match('text 1', feed) | |
14 | + assert_match('/tagger/' + articles.first.slug, feed) | |
15 | + end | |
16 | + | |
17 | + should 'use title, link and description' do | |
18 | + writer = FeedWriter.new | |
19 | + rss = writer.write([], :title => "my title", :description => "my description", :link => "http://example.com/") | |
20 | + assert_match("my title", rss) | |
21 | + assert_match("my description", rss) | |
22 | + assert_match("http://example.com/", rss) | |
23 | + end | |
24 | + | |
25 | +end | |
26 | + | ... | ... |
test/unit/rss_feed_test.rb
... | ... | @@ -62,24 +62,6 @@ class RssFeedTest < Test::Unit::TestCase |
62 | 62 | feed.data |
63 | 63 | end |
64 | 64 | |
65 | - should 'be able to choose to put abstract or entire body on feed' do | |
66 | - profile = create_user('testuser').person | |
67 | - a1 = profile.articles.build(:name => 'article 1', 'abstract' => 'my abstract', 'body' => 'my text'); a1.save! | |
68 | - | |
69 | - feed = RssFeed.new(:name => 'testfeed') | |
70 | - feed.profile = profile | |
71 | - feed.save! | |
72 | - | |
73 | - rss = feed.data | |
74 | - assert_match /<description>my abstract<\/description>/, rss | |
75 | - assert_no_match /<description>my text<\/description>/, rss | |
76 | - | |
77 | - feed.feed_item_description = 'body' | |
78 | - rss = feed.data | |
79 | - assert_match /<description>my text<\/description>/, rss | |
80 | - assert_no_match /<description>my abstract<\/description>/, rss | |
81 | - end | |
82 | - | |
83 | 65 | should "be able to search only children of feed's parent" do |
84 | 66 | profile = create_user('testuser').person |
85 | 67 | a1 = profile.articles.build(:name => 'article 1'); a1.save! |
... | ... | @@ -201,21 +183,6 @@ class RssFeedTest < Test::Unit::TestCase |
201 | 183 | assert !feed.errors.invalid?(:include) |
202 | 184 | end |
203 | 185 | |
204 | - should 'allow only body and abstract as feed_item_description' do | |
205 | - feed = RssFeed.new | |
206 | - feed.feed_item_description = :something_else | |
207 | - feed.valid? | |
208 | - assert feed.errors.invalid?(:feed_item_description) | |
209 | - | |
210 | - feed.feed_item_description = 'body' | |
211 | - feed.valid? | |
212 | - assert !feed.errors.invalid?(:feed_item_description) | |
213 | - | |
214 | - feed.feed_item_description = 'abstract' | |
215 | - feed.valid? | |
216 | - assert !feed.errors.invalid?(:feed_item_description) | |
217 | - end | |
218 | - | |
219 | 186 | should 'provide proper short description' do |
220 | 187 | assert_kind_of String, RssFeed.short_description |
221 | 188 | end |
... | ... | @@ -246,7 +213,7 @@ class RssFeedTest < Test::Unit::TestCase |
246 | 213 | blog = fast_create(Blog, :profile_id => profile.id) |
247 | 214 | published_article = PublishedArticle.create!(:reference_article => article, :profile => profile) |
248 | 215 | blog.posts << published_article |
249 | - feed = RssFeed.new(:parent => blog, :profile => profile, :feed_item_description => 'body') | |
216 | + feed = RssFeed.new(:parent => blog, :profile => profile) | |
250 | 217 | |
251 | 218 | assert_match published_article.to_html, feed.data |
252 | 219 | end | ... | ... |