spree_simple_blog 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.markdown ADDED
@@ -0,0 +1,6 @@
1
+ = Spree Simple Blog
2
+
3
+ Adds a simple blog to Spree which works much like Toto (http://github.com/cloudhead/toto/), reading text files in /content/articles. Content files consist of
4
+ yaml metadata followed by Markdown content.
5
+
6
+ By extending FileModel, other types of content can be managed in the same way way.
@@ -0,0 +1,30 @@
1
+ class ArticlesController < Spree::BaseController
2
+ rescue_from Errno::ENOENT, :with => :render_404
3
+
4
+ def index
5
+ @per_page = 10
6
+ @current_page = (params[:page] || 1).to_i
7
+ @total_pages = (Article.count / @per_page.to_f).ceil
8
+ @articles = Article.find_articles(:page => @current_page)
9
+ respond_to do |wants|
10
+ wants.html {}
11
+ wants.xml {}
12
+ end
13
+ end
14
+
15
+ def archive
16
+ @articles = Article.find_articles(:year => params[:year], :month => params[:month])
17
+ render :action => 'index'
18
+ end
19
+
20
+ def tag
21
+ @articles = Article.tagged(params[:tag])
22
+ render :action => 'index'
23
+ end
24
+
25
+ def show
26
+ key = "#{params[:year]}-#{params[:month].rjust(2,'0')}-#{params[:day].rjust(2,'0')}-#{params[:permalink]}"
27
+ @article = Article.find(key)
28
+ end
29
+
30
+ end
@@ -0,0 +1,37 @@
1
+ require 'rdiscount'
2
+
3
+ module ArticlesHelper
4
+
5
+ def html_transform(text)
6
+ RDiscount.new(text.to_s).to_html.html_safe
7
+ end
8
+
9
+ def format_date(time)
10
+ time.strftime("%B %d %Y")
11
+ end
12
+
13
+ def format_time(time)
14
+ time.strftime("%I:%m%p")
15
+ end
16
+
17
+ def link_to_archive(archive_month)
18
+ text = "#{Date::MONTHNAMES[archive_month.last.to_i]} #{archive_month.first.to_i}"
19
+ link_to text, blog_articles_archive_url(:year => archive_month.first, :month => archive_month.last.to_s.rjust(2, '0'))
20
+ end
21
+
22
+ def link_to_article(article, options = {})
23
+ link_to h(article.title), article_url(article), options
24
+ end
25
+
26
+ def article_url(article)
27
+ blog_article_url :permalink => article.slug, :year => article.date.year, :month => article.date.month, :day => article.date.day
28
+ end
29
+
30
+ def url_escape(string)
31
+ string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
32
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
33
+ end.tr(' ', '+')
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,53 @@
1
+ class Article < FileModel
2
+
3
+ def self.find_articles(options = {})
4
+ options = {
5
+ :per_page => 10,
6
+ :page => 1
7
+ }.merge(options)
8
+
9
+ options[:offset] = (options[:page]-1) * options[:per_page]
10
+ options[:limit] = options[:per_page]
11
+
12
+ if options[:year] and options[:month]
13
+ options[:match] = %r(\/#{options[:year]}-#{options[:month].to_s.rjust(2,'0')})
14
+ end
15
+
16
+ all(options)
17
+ end
18
+
19
+ def self.recent(number = 5)
20
+ @@recent ||= all(:limit => number)
21
+ end
22
+
23
+ def self.tagged(tag)
24
+ tag_index[tag.strip].to_a.map do |key|
25
+ Article.find(key)
26
+ end
27
+ end
28
+
29
+ def self.archive_months
30
+ @@archive_months ||= files.map{|f| File.basename(f).split('-')[0..1] }.uniq
31
+ end
32
+
33
+ # Store list of articles for each tag in a hash
34
+ # Fast enough for the small number of articles we're dealing with
35
+ @@tag_index = {}
36
+ def self.tag_index
37
+ return @@tag_index if @@tag_index.present?
38
+ all.each do |a|
39
+ a.tags.split(',').map(&:strip).each do |t|
40
+ @@tag_index[t] ||= []
41
+ @@tag_index[t] = (@@tag_index[t] << a.key).uniq
42
+ end
43
+ end
44
+ @@tag_index
45
+ end
46
+
47
+ %w(title_tag meta_keywords meta_description).each do |attribute|
48
+ define_method(attribute) do
49
+ meta[attribute]
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,98 @@
1
+ # Base class for simple models that load their content from text files formatted like Toto's articles
2
+ class FileModel
3
+ attr_accessor :file, :key, :meta, :body
4
+
5
+ def initialize(file, key, meta, body)
6
+ @file = file
7
+ @key = key
8
+ @meta = meta
9
+ @body = body
10
+ end
11
+
12
+ def method_missing(method, *args)
13
+ if meta.has_key?(method.to_s)
14
+ meta[method.to_s]
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+
21
+ def self.all(options = {})
22
+ options = {
23
+ :offset => 0
24
+ }.merge(options)
25
+
26
+ files = get_filtered_files(options)
27
+
28
+ if options[:limit]
29
+ files = files[options[:offset] .. (options[:offset].to_i + options[:limit].to_i-1)]
30
+ end
31
+
32
+ records = files.to_a.map do |f|
33
+ from_file(f)
34
+ end
35
+
36
+ records
37
+ end
38
+
39
+ def self.find(key)
40
+ from_file("#{content_path}/#{key}.txt")
41
+ end
42
+
43
+ def self.count(options = {})
44
+ get_filtered_files(options).length
45
+ end
46
+
47
+ def self.random(options = {})
48
+ from_file(files[rand(all.size)])
49
+ end
50
+
51
+ def self.files
52
+ Dir["#{content_path}/*.txt"].sort_by {|f| File.basename(f) }.reverse
53
+ end
54
+
55
+ def index
56
+ @index ||= self.class.files.index(file)
57
+ end
58
+
59
+ def next
60
+ return @next if @next.present?
61
+ if next_file = self.class.files[index + 1]
62
+ @next = self.class.from_file(next_file)
63
+ end
64
+ end
65
+
66
+ def previous
67
+ return @previous if @previous.present?
68
+ if index > 0 and previous_file = self.class.files[index - 1]
69
+ @previous = self.class.from_file(previous_file)
70
+ end
71
+ end
72
+
73
+ def self.from_file(file)
74
+ meta, body = File.read(file).split(/\n\n/, 2)
75
+ meta = YAML.load(meta)
76
+ key = File.basename(file).gsub('.txt','')
77
+ new(file, key, meta, body)
78
+ end
79
+
80
+ def self.content_path
81
+ File.expand_path(File.join(Rails.root, "content", relative_path))
82
+ end
83
+
84
+ def self.relative_path
85
+ self.name.underscore
86
+ end
87
+
88
+ private
89
+
90
+ def self.get_filtered_files(options)
91
+ if options[:match]
92
+ files.select{|f| f =~ options[:match]}
93
+ else
94
+ files
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,8 @@
1
+ <div id="archives">
2
+ <h2>Archives</h2>
3
+ <ul>
4
+ <% for archive_month in Article.archive_months %>
5
+ <li><%= link_to_archive archive_month %></li>
6
+ <% end %>
7
+ </ul>
8
+ </div>
@@ -0,0 +1,31 @@
1
+ <div class="post">
2
+
3
+ <div class="heading">
4
+ <h2><%= link_to_article article %></h2>
5
+ <p>
6
+ <span class="author">by <strong><%= article.author %></strong></span>
7
+ &bull;
8
+ <span class="date"><%= format_date article.date %></span>
9
+ </p>
10
+ </div>
11
+
12
+ <div class="entry">
13
+
14
+ <%= html_transform article.body %>
15
+
16
+ </div>
17
+
18
+ <% if @articles %>
19
+ <div class="meta">
20
+ <p class="comments"><a href="<%= article_url article %>#respond" title="Comment on <%= article.title %>">Comments &#187;</a></p>
21
+ <p class="time">Posted @ <%= format_time article.date %></p>
22
+ <br class="clear" />
23
+ </div>
24
+ <% else %>
25
+ <% if @article.tags.present? %>
26
+ <p>Tags:
27
+ <%= @article.tags.split(',').map { |tag| link_to(tag, blog_tag_path(tag), :rel => 'tag')}.join(', ') %>
28
+ <% end %>
29
+ <% end %>
30
+
31
+ </div>
@@ -0,0 +1,18 @@
1
+ <% if Spree::Config[:disqus_id] %>
2
+ <% content_for :extra_javascripts do %>
3
+ <script type="text/javascript">
4
+ //<![CDATA[
5
+ (function() {
6
+ var links = document.getElementsByTagName('a');
7
+ var query = '?';
8
+ for(var i = 0; i < links.length; i++) {
9
+ if(links[i].href.indexOf('#disqus_thread') >= 0) {
10
+ query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&';
11
+ }
12
+ }
13
+ document.write('<script charset="utf-8" type="text/javascript" src="http://disqus.com/forums/<%= Spree::Config[:disqus_id] %>/get_num_replies.js' + query + '"></' + 'script>');
14
+ })();
15
+ //]]>
16
+ </script>
17
+ <% end %>
18
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <div id="recent-posts">
2
+ <h2>Recent Posts</h2>
3
+ <ul>
4
+ <% for article in Article.recent %>
5
+ <li><%= link_to_article article %></li>
6
+ <% end %>
7
+ </ul>
8
+ </div>
@@ -0,0 +1,11 @@
1
+ <% hook :blog_sidebar do %>
2
+
3
+ <% if Spree::Config[:blog_feed_url] %>
4
+ <%= render 'subscribe' %>
5
+ <% end %>
6
+
7
+ <%= render 'recent' %>
8
+
9
+ <%= render 'archives' %>
10
+
11
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <div id="subscribe">
2
+ <h2>Subscribe</h2>
3
+ <ul>
4
+ <li><a id="btnRss" href="<%= Spree::Config[:blog_feed_url] %>" title="Subscribe to the Railsdog RSS Feed">Railsdog RSS Feed</a></li>
5
+ </ul>
6
+ </div>
@@ -0,0 +1,30 @@
1
+ <% self.title = "Blog" %>
2
+ <% @body_class = 'blog' %>
3
+
4
+ <% if params[:tag] %>
5
+ <h2 class="pagetitle">Posts Tagged &lsquo;<%= params[:tag] %>&rsquo;</h2>
6
+ <% elsif params[:year] && params[:month] %>
7
+ <h2 class="pagetitle">Archive for <%= Date::MONTHNAMES[params[:month].to_i] %>, <%= params[:year].to_i %></h2>
8
+ <% end %>
9
+
10
+ <%= render :partial => 'article', :collection => @articles %>
11
+
12
+ <% if @total_pages %>
13
+ <div class="navigation">
14
+ <% if @total_pages > 1 && @current_page < @total_pages %>
15
+ <div class="alignleft"><a href="<%= url_for(:overwrite_params => {:page => @current_page + 1}) %>" >&laquo; Older Entries</a></div>
16
+ <% end %>
17
+ <% if @current_page > 1 %>
18
+ <div class="alignright"><a href="<%= url_for(:overwrite_params => {:page => @current_page - 1}) %>" >Newer Entries &raquo;</a></div>
19
+ <% end %>
20
+ <br class="clear" />
21
+ </div>
22
+ <% end %>
23
+
24
+ <% content_for :sidebar do %>
25
+
26
+ <%= render 'sidebar' %>
27
+
28
+ <% end %>
29
+
30
+ <br class="clear" />
@@ -0,0 +1,20 @@
1
+ xml.instruct!
2
+ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
3
+ xml.title Spree::Config[:blog_feed_title]
4
+ xml.id Spree::Config[:blog_feed_url]
5
+ xml.updated @articles.first.date.xmlschema unless @articles.empty?
6
+ xml.author { xml.name Spree::Config[:blog_feed_author] }
7
+
8
+ @articles.each do |article|
9
+ xml.entry do
10
+ xml.title article.title
11
+ xml.link "rel" => "alternate", "href" => article_url(article)
12
+ xml.id article_url(article)
13
+ xml.published article.date.xmlschema
14
+ xml.updated article.date.xmlschema
15
+ xml.author { xml.name article.author }
16
+ xml.content("type" => "html") { xml.cdata!(html_transform(article.body)) }
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,38 @@
1
+ <% self.title = "Blog - " + @article.title %>
2
+ <% @body_class = 'blog' %>
3
+
4
+ <div class="navigation">
5
+ <% if @article.previous %>
6
+ <div class="alignleft">&laquo; <%= link_to_article @article.previous %></div>
7
+ <% end %>
8
+ <% if @article.next %>
9
+ <div class="alignright"><%= link_to_article @article.next %> &raquo;</div>
10
+ <% end %>
11
+ <br class="clear" />
12
+ </div>
13
+
14
+ <%= render 'article', :article => @article %>
15
+
16
+ <% if Spree::Config[:disqus_id] %>
17
+ <div id="comments">
18
+ <a name="respond"></a>
19
+ <div id="disqus_thread"></div>
20
+ <script type="text/javascript">
21
+ var disqus_developer = <%= RAILS_ENV == 'development' ? '1' : '0' %>;
22
+ </script>
23
+ <noscript><a href="http://spreecommerce.disqus.com/?url=ref">View the discussion thread.</a></noscript>
24
+ <a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
25
+ </div>
26
+ <% end %>
27
+
28
+
29
+
30
+ <% content_for :sidebar do %>
31
+ <%= render 'sidebar' %>
32
+ <% end %>
33
+
34
+ <% if Spree::Config[:disqus_id] %>
35
+ <% content_for :extra_javascripts do %>
36
+ <script type="text/javascript" src="http://disqus.com/forums/<%= Spree::Config[:disqus_id] %>/embed.js"></script>
37
+ <% end %>
38
+ <% end %>
@@ -0,0 +1,22 @@
1
+ module SimpleBlogExtension
2
+ class Engine < Rails::Engine
3
+
4
+ def self.activate
5
+
6
+ Spree::BaseController.class_eval do
7
+ helper ArticlesHelper
8
+ end
9
+
10
+ AppConfiguration.class_eval do
11
+ preference :disqus_id, :default => nil
12
+ preference :blog_feed_url, :default => '/blog.xml'
13
+ end
14
+
15
+ end
16
+
17
+
18
+ config.autoload_paths += %W(#{config.root}/lib)
19
+ config.to_prepare &method(:activate).to_proc
20
+
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ namespace :blog do
2
+
3
+ task :new => :environment do
4
+
5
+ title = ask('Title: ')
6
+ slug = title.empty?? nil : title.strip.parameterize.to_s
7
+
8
+ article = {'slug' => slug, 'title' => title, 'author' => 'Author', 'tags' => '', 'date' => Date.today}.to_yaml
9
+ article << "\n"
10
+ article << "Lorem ipsum dolor sit amet...\n\n"
11
+
12
+ path = "#{RAILS_ROOT}/content/articles/#{Time.now.strftime("%Y-%m-%d")}#{'-' + slug if slug}.txt"
13
+
14
+ unless File.exist? path
15
+ File.open(path, "w") do |file|
16
+ file.write article
17
+ end
18
+ puts "an article was created for you at #{path}."
19
+ else
20
+ puts "I can't create the article, #{path} already exists."
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ def ask message
28
+ print message
29
+ STDIN.gets.chomp
30
+ end
@@ -0,0 +1,17 @@
1
+ namespace :spree do
2
+ namespace :extensions do
3
+ namespace :simple_blog do
4
+ desc "Copies public assets of the Simple Blog to the instance public/ directory."
5
+ task :update => :environment do
6
+ is_svn_git_or_dir = proc {|path| path =~ /\.svn/ || path =~ /\.git/ || File.directory?(path) }
7
+ Dir[SimpleBlogExtension.root + "/public/**/*"].reject(&is_svn_git_or_dir).each do |file|
8
+ path = file.sub(SimpleBlogExtension.root, '')
9
+ directory = File.dirname(path)
10
+ puts "Copying #{path}..."
11
+ mkdir_p RAILS_ROOT + directory
12
+ cp file, RAILS_ROOT + path
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spree_simple_blog
3
+ version: !ruby/object:Gem::Version
4
+ hash: 5
5
+ prerelease: false
6
+ segments:
7
+ - 3
8
+ - 0
9
+ - 1
10
+ version: 3.0.1
11
+ platform: ruby
12
+ authors: []
13
+
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-03 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: spree_core
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - "="
28
+ - !ruby/object:Gem::Version
29
+ hash: -1848229955
30
+ segments:
31
+ - 0
32
+ - 30
33
+ - 0
34
+ - beta1
35
+ version: 0.30.0.beta1
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: rdiscount
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - "="
45
+ - !ruby/object:Gem::Version
46
+ hash: 9
47
+ segments:
48
+ - 1
49
+ - 5
50
+ - 5
51
+ version: 1.5.5
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ description:
55
+ email:
56
+ executables: []
57
+
58
+ extensions: []
59
+
60
+ extra_rdoc_files: []
61
+
62
+ files:
63
+ - README.markdown
64
+ - lib/spree_simple_blog.rb
65
+ - lib/tasks/blog.rake
66
+ - lib/tasks/simple_blog_extension_tasks.rake
67
+ - app/controllers/articles_controller.rb
68
+ - app/helpers/articles_helper.rb
69
+ - app/models/article.rb
70
+ - app/models/file_model.rb
71
+ - app/views/articles/_archives.html.erb
72
+ - app/views/articles/_article.html.erb
73
+ - app/views/articles/_comment_count_js.html.erb
74
+ - app/views/articles/_recent.html.erb
75
+ - app/views/articles/_sidebar.html.erb
76
+ - app/views/articles/_subscribe.html.erb
77
+ - app/views/articles/index.html.erb
78
+ - app/views/articles/index.xml.builder
79
+ - app/views/articles/show.html.erb
80
+ has_rdoc: true
81
+ homepage:
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options: []
86
+
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 57
95
+ segments:
96
+ - 1
97
+ - 8
98
+ - 7
99
+ version: 1.8.7
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ requirements:
110
+ - none
111
+ rubyforge_project:
112
+ rubygems_version: 1.3.7
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Toto style blogging for Spree
116
+ test_files: []
117
+