spree_simple_blog 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+