village 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/Gemfile +29 -0
  2. data/Guardfile +9 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +255 -0
  5. data/Rakefile +29 -0
  6. data/app/controllers/village/articles_controller.rb +27 -0
  7. data/app/controllers/village/pages_controller.rb +16 -0
  8. data/app/views/layouts/village.html.haml +22 -0
  9. data/app/views/village/articles/_addthis.html.haml +8 -0
  10. data/app/views/village/articles/_analytics.html.haml +13 -0
  11. data/app/views/village/articles/_article.html.haml +25 -0
  12. data/app/views/village/articles/_comments.html.haml +8 -0
  13. data/app/views/village/articles/_feed_link.html.haml +2 -0
  14. data/app/views/village/articles/_sidebar.html.haml +13 -0
  15. data/app/views/village/articles/index.atom.builder +26 -0
  16. data/app/views/village/articles/index.html.haml +11 -0
  17. data/app/views/village/articles/index.rss.builder +31 -0
  18. data/app/views/village/articles/show.html.haml +9 -0
  19. data/app/views/village/pages/show.html.haml +1 -0
  20. data/lib/generators/base.rb +178 -0
  21. data/lib/generators/village/articles/USAGE +6 -0
  22. data/lib/generators/village/articles/articles_generator.rb +50 -0
  23. data/lib/generators/village/setup/USAGE +5 -0
  24. data/lib/generators/village/setup/setup_generator.rb +67 -0
  25. data/lib/generators/village/setup/templates/2001-01-01-example-article.markdown +8 -0
  26. data/lib/generators/village/setup/templates/example-page.markdown +10 -0
  27. data/lib/generators/village/setup/templates/views/articles/_addthis.html.haml +8 -0
  28. data/lib/generators/village/setup/templates/views/articles/_analytics.html.haml +13 -0
  29. data/lib/generators/village/setup/templates/views/articles/_article.html.haml +25 -0
  30. data/lib/generators/village/setup/templates/views/articles/_comments.html.haml +8 -0
  31. data/lib/generators/village/setup/templates/views/articles/_feed_link.html.haml +2 -0
  32. data/lib/generators/village/setup/templates/views/articles/_sidebar.html.haml +13 -0
  33. data/lib/generators/village/setup/templates/views/articles/index.atom.builder +26 -0
  34. data/lib/generators/village/setup/templates/views/articles/index.html.haml +11 -0
  35. data/lib/generators/village/setup/templates/views/articles/index.rss.builder +31 -0
  36. data/lib/generators/village/setup/templates/views/articles/show.html.haml +9 -0
  37. data/lib/generators/village/setup/templates/views/pages/show.html.haml +1 -0
  38. data/lib/generators/village/setup/templates/views/village.css +72 -0
  39. data/lib/generators/village/setup/templates/views/village.html.haml +22 -0
  40. data/lib/generators/village/setup/templates/village_config.yml +81 -0
  41. data/lib/village.rb +4 -0
  42. data/lib/village/article.rb +125 -0
  43. data/lib/village/attributes.rb +21 -0
  44. data/lib/village/config.rb +53 -0
  45. data/lib/village/engine.rb +11 -0
  46. data/lib/village/file_model.rb +83 -0
  47. data/lib/village/page.rb +10 -0
  48. data/lib/village/routes.rb +16 -0
  49. metadata +158 -0
@@ -0,0 +1,8 @@
1
+ / AddThis Button BEGIN
2
+ .addthis_toolbox.addthis_default_style
3
+ %a.addthis_button_facebook_like{"fb:like:layout" => "button_count"}
4
+ %a.addthis_button_tweet
5
+ %a.addthis_button_google_plusone{"g:plusone:size" => "medium"}
6
+ %a.addthis_counter.addthis_pill_style
7
+ %script{:src => "http://s7.addthis.com/js/250/addthis_widget.js#pubid=ra-4eade9351b25746b", :type => "text/javascript"}
8
+ / AddThis Button END
@@ -0,0 +1,13 @@
1
+ - content_for :head do
2
+ - if Village::Config.google_analytics_code?
3
+ :plain
4
+ <script type="text/javascript">
5
+ var _gaq = _gaq || [];
6
+ _gaq.push(['_setAccount', '#{Village::Config.google_analytics_code}']);
7
+ _gaq.push(['_trackPageview']);
8
+ (function() {
9
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
10
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
11
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
12
+ })();
13
+ </script>
@@ -0,0 +1,25 @@
1
+ = content_tag_for :article, article do
2
+ %header
3
+ %h1= link_to article.title, article
4
+ = image_tag article.gravatar_url(:size => 32, :default => 'identicon'), :height => 32, :width => 32
5
+ .meta
6
+ %time{:pubdate => 'true', :datetime => article.timestamp.iso8601}
7
+ Posted on #{article.date.strftime '%e %B %Y'}
8
+ %span
9
+ by
10
+ = article.author[:name]
11
+ %p
12
+ - if article.categories?
13
+ Categories on:
14
+ - article.categories.each do |category|
15
+ = link_to category, village_articles_path(:category => category)
16
+ %p
17
+ - if article.tags?
18
+ Tags on:
19
+ - article.tags.each do |tag|
20
+ = link_to tag, village_articles_path(:tag => tag)
21
+ - if summary
22
+ = article.summary_html
23
+ %p.more= link_to 'Read more&hellip;'.html_safe, article
24
+ - else
25
+ ~ article.content_html
@@ -0,0 +1,8 @@
1
+ - if Village::Config.disqus_short_name?
2
+ #disqus_thread
3
+ - if ENV['RAILS_ENV'] == :development
4
+ :javascript
5
+ var disqus_developer = true;
6
+ %script{:type => 'text/javascript', :src => "http://#{Village::Config.disqus_short_name}.disqus.com/embed.js", :async => true}
7
+ %noscript
8
+ %a(href="http://#{Village::Config.disqus_short_name}.disqus.com/embed.js?url=ref") View comments.
@@ -0,0 +1,2 @@
1
+ - content_for :head do
2
+ %link{:href => village_articles_path(:format => :atom), :type => 'application/rss+xml', :rel => :alternate, :title => Village::Config.title}
@@ -0,0 +1,13 @@
1
+ %h4
2
+ Categories
3
+ - unless Village::Article.categories.nil?
4
+ %ul
5
+ - Village::Article.categories.each do |category|
6
+ %li= link_to "#{category[0]} (#{category[1]})", village_articles_path(:category => category[0])
7
+
8
+ %h4
9
+ Tags
10
+ - unless Village::Article.tags.nil?
11
+ %ul
12
+ - Village::Article.tags.each do |tag|
13
+ %li= link_to "#{tag[0]} (#{tag[1]})", village_articles_path(:tag => tag[0])
@@ -0,0 +1,26 @@
1
+ xml.instruct!
2
+ xml.feed :xmlns => 'http://www.w3.org/2005/Atom' do
3
+ xml.title Village::Config.title
4
+ xml.link village_articles_url(:format => :atom)
5
+ xml.id village_articles_url
6
+ xml.updated Village::Article.feed_last_modified.xmlschema
7
+
8
+ Village::Article.feed.each do |article|
9
+ xml.entry do
10
+ xml.title article.title, :type => :text
11
+ xml.link :href => village_article_url(article), :rel => :alternate, :type => 'text/html'
12
+ xml.published article.timestamp.xmlschema
13
+ xml.updated article.last_modified.xmlschema
14
+ if article.author?
15
+ xml.author do
16
+ xml.name article.author[:name]
17
+ xml.email article.author[:email]
18
+ end
19
+ end
20
+ xml.id village_article_url(article)
21
+ xml.content :type => :html, 'xml:base' => village_article_url(article) do
22
+ xml.cdata! article.content_html
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ = render 'analytics'
2
+ = render 'feed_link'
3
+ %section#sidebar
4
+ = render 'sidebar'
5
+
6
+ %section#articles
7
+ - if collection.present?
8
+ = render :partial => 'article', :collection => collection, :locals => { :summary => true }
9
+ = paginate collection
10
+ - else
11
+ No articles found.
@@ -0,0 +1,31 @@
1
+ xml.instruct! :xml, :version => "1.0"
2
+ xml.rss :version => "2.0" do
3
+ xml.channel do
4
+ xml.title Village::Config.title
5
+ xml.description Village::Config.subtitle
6
+ xml.link village_articles_url(:format => :rss)
7
+ xml.generator village_articles_url(:format => :rss)
8
+ xml.lastBuildDate Village::Article.feed_last_modified
9
+
10
+ for article in Village::Article.feed
11
+ xml.item do
12
+ xml.title article.title
13
+ xml.description :type => :html, 'xml:base' => village_article_url(article) do
14
+ xml.cdata! article.summary_html
15
+ end
16
+ xml.pubDate article.timestamp.xmlschema
17
+ xml.link :href => village_article_url(article), :rel => :alternate, :type => 'text/html'
18
+ xml.guid village_article_url(article)
19
+ if article.author.present?
20
+ xml.author do
21
+ xml.name article.author[:name]
22
+ xml.email article.author[:email]
23
+ end
24
+ end
25
+ if article.tags?
26
+ xml.category article.tags.join(",")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ = render 'feed_link'
2
+ %section#sidebar
3
+ = render 'sidebar'
4
+
5
+ = render :partial => 'article', :object => resource, :locals => { :summary => false }
6
+
7
+ = render 'addthis'
8
+
9
+ = render 'comments'
@@ -0,0 +1,72 @@
1
+ /* -- Reset ----------------------------------------------------------------- */
2
+ body,div,dl,dt,dd,ul,ol,li,h1,h2,h4,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0}
3
+ table{border-collapse:collapse;border-spacing:0}
4
+ fieldset,img{border:0}
5
+ address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit}
6
+ del,ins{text-decoration:none}
7
+ li{list-style:none}
8
+ caption,th{text-align:left}
9
+ h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}
10
+ q:before,q:after{content:''}
11
+ abbr,acronym{border:0;font-variant:normal}
12
+ legend{color:#000}
13
+ input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit}
14
+ input,button,textarea,select{font-size:100%}
15
+ sup,sub{vertical-align:baseline}
16
+ header, sidebar, section, article, footer{display:block;}
17
+
18
+ /* -- Global ---------------------------------------------------------------- */
19
+ html{background:#F3F3F3;text-shadow:0px 1px 0px #fff;}
20
+ body{width:760px;margin:40px auto;font:14px/140% 'Helvetica Neue', Arial, Helvetica, Sans-Serif;}
21
+ a{color:#222;}
22
+
23
+ h1, h2, h3, h4, h5, h6{font-weight:bold;}
24
+ h1{font-size:26px;margin-bottom:5px;}
25
+ h1 a{text-decoration:none;}
26
+ h2{font-size:24px;padding-top:10px;margin:30px 0 20px;border-top:2px solid #ccc;}
27
+ h3{font-size:20px;margin-bottom:20px;}
28
+ h4{font-size:18px;margin-bottom:10px;}
29
+ h5, h6{font-size:16px;margin-bottom:5px;}
30
+
31
+ p{margin-bottom:20px;}
32
+ em{font-style:italic;}
33
+ sup{font-size:12px;vertical-align:super;}
34
+ sub{font-size:12px;vertical-align:sub;}
35
+ del{color:#666;font-style:line-through;}
36
+ abbr{border-bottom:1px dotted #bbb;color:#666;text-transform:uppercase;}
37
+ code{background:#eee;padding:2px 5px;font-family:Monospace;}
38
+ small{color:#999;font-size:12px;}
39
+ strong{font-weight:bold;}
40
+ blockquote{color:#666;font-size:16px;margin-left:20px;font-family:Georgia, Serif;}
41
+
42
+ /* code */
43
+ pre{background:#eee;margin-bottom:20px;padding:10px;border:1px solid #ccc;border-width:1px 0;}
44
+
45
+ /* lists */
46
+ ol, ul{margin:0 0 20px 20px;}
47
+ ol li{list-style:decimal inside;}
48
+ ul li{list-style:disc inside;}
49
+
50
+ /* -- Header ---------------------------------------------------------------- */
51
+ header#header {margin-bottom:40px;}
52
+ header#header h1 {font-size:42px;margin-bottom:20px;font-family:Georgia, Serif;font-weight:normal;}
53
+ header#header p {color:#999;}
54
+
55
+ /* -- Content --------------------------------------------------------------- */
56
+ section#content{}
57
+
58
+ section#content section article p{margin-bottom:10px;}
59
+
60
+ section#content article{margin-bottom:40px;}
61
+ section#content article header{overflow:hidden;}
62
+ section#content article header h1{}
63
+ section#content article header img{display:none;}
64
+ section#content article header .meta{color:#999;font-size:12px;}
65
+
66
+ /* -- Sidebar --------------------------------------------------------------- */
67
+ section#sidebar{position:absolute;top:10;right:0;margin-right:100px;}
68
+ section#sidebar ul{margin:0 0 3px 3px}
69
+ section#sidebar ul li{list-style:none inside;}
70
+
71
+ /* -- Footer ---------------------------------------------------------------- */
72
+ footer{margin-bottom:20px;}
@@ -0,0 +1,22 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %title= Village::Config.title
5
+ = stylesheet_link_tag 'village'
6
+ = yield :head
7
+ %body
8
+ %header#header
9
+ %h1= link_to Village::Config.title, '/'
10
+ %p
11
+ = Village::Config.subtitle
12
+ %section#content
13
+ = yield
14
+ %footer
15
+ %small
16
+ - if Village::Config.author?
17
+ A #{link_to 'village', Village::Config.author[:url]} blog
18
+ = "by #{Village::Config.author[:name]}"
19
+ = "Copyright &copy; #{Date.today.year}".html_safe
20
+ = link_to 'rss', village_articles_path(:rss)
21
+ |
22
+ = link_to 'atom', village_articles_path(:atom)
@@ -0,0 +1,81 @@
1
+ # title & subtitle
2
+ # Title and subheading for your site. Used on the layout page and
3
+ # will be overriden in every page titles.
4
+ #
5
+ title: Your Site
6
+ subtitle: (change this text in config/village.yml)
7
+
8
+ # author
9
+ # You should really specify your content's author when generating an
10
+ # Atom feed. Specify at least one of name, uri or email, and Village will
11
+ # include it in your feed. See the Atom spec for more info:
12
+ #
13
+ # http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.feed
14
+ #
15
+ author:
16
+ name: Your Name
17
+ uri: http://yourhomepage.com
18
+ email: your@email.com
19
+
20
+ # layout
21
+ # The name of the layout file to be used by Village specified in "app/views/layouts",
22
+ # village will look for every pages / articles layouts for every request.
23
+ #
24
+ layout: "village"
25
+
26
+ # page_size
27
+ # the page size / PER_PAGE used by Kaminari for paginate your articles,
28
+ # the default is 5
29
+ #
30
+ # page_size: 5
31
+
32
+ # disqus_short_name
33
+ # If you want to use the Disqus service (http://disqus.com) to display
34
+ # comments on your site, register a Disqus account and then specify your
35
+ # site's short name here. A comment form will automatically be added to
36
+ # the bottom of your pages.
37
+ #
38
+ # disqus_short_name: mysite
39
+
40
+ # google_analytics_code
41
+ # Set this if you want Google Analytics to track traffic on your site.
42
+ # Probably best not to set a default value, but to set it in production.
43
+ #
44
+ # The production settings are used if you're deploying to Heroku, so
45
+ # scroll down a bit to set it in production even if you're not deploying
46
+ # to your own server.
47
+ #
48
+ # google_analytics_code: "UA-???????-?"
49
+
50
+ # -------------------------------
51
+ # ======== DEFAULT CONFIGURATIONS
52
+ # -------------------------------
53
+ #
54
+ # permalink_format
55
+ # Set this if you want change the default permalink_format
56
+ # the options are :
57
+ #
58
+ # "day" URL: http://localhost:3000/articles/2011/01/01/test-post
59
+ # "month" URL: http://localhost:3000/articles/2011/01/test-post
60
+ # "year" URL: http://localhost:3000/articles/2011/test-post
61
+ # "slug" URL: http://localhost:3000/articles/test-post
62
+ #
63
+ # the default village:articles will use permalink_format "day"
64
+ # You can also customize this throug route options
65
+ #
66
+ # permalink_format: day
67
+
68
+ # file_extensions
69
+ # Set this file_extensions which engine you want to use
70
+ # Village will accepts these formats :
71
+ #
72
+ # [ "erb", "rhtml", "erubis", "haml", "builder", "liquid",
73
+ # "markdown", "mkd", "md", "textile", "rdoc", "radius",
74
+ # "mab", "wiki", "mediawiki", "mw", "creole", "yajl" ]
75
+ #
76
+ # remember you have to set this settings with String
77
+ # inside an [Array]
78
+ # by default Village will accepts :markdown, :erb and :haml
79
+ #
80
+ # file_extensions: ["markdown", "erb", "haml"]
81
+
@@ -0,0 +1,4 @@
1
+ if defined?(Rails)
2
+ require 'village/engine'
3
+ require 'village/routes'
4
+ end
@@ -0,0 +1,125 @@
1
+ require 'gravtastic'
2
+ require 'nokogiri'
3
+
4
+ module Village
5
+ class Article < FileModel
6
+ include Gravtastic
7
+ is_gravtastic
8
+
9
+ CONTENT_PATH = "app/views/articles"
10
+
11
+ delegate :year, :month, :day, :to => :date
12
+
13
+ class Sanitizer < HTML::WhiteListSanitizer; self.allowed_tags -= %w(img a); end
14
+
15
+ TagHelper = Class.new.extend ActionView::Helpers::TagHelper
16
+
17
+ self.superclass.create_class_methods_on(Village::Article)
18
+
19
+ class << self
20
+ def order
21
+ all.select(&:visible?).sort_by(&:date).reverse
22
+ end
23
+
24
+ def tags
25
+ @tags ||= all.map do |article|
26
+ article.tags if article.attributes[:tags].present?
27
+ end.compact.flatten.inject(Hash.new(0)) do |h,e|
28
+ h[e] += 1; h
29
+ end.inject({}) do |r, e|
30
+ r[e.first] = e.last; r
31
+ end
32
+ end
33
+
34
+ def categories
35
+ @categories ||= all.map do |article|
36
+ article.categories if article.attributes[:categories].present?
37
+ end.compact.flatten.inject(Hash.new(0)) do |h,e|
38
+ h[e] += 1; h
39
+ end.inject({}) do |r, e|
40
+ r[e.first] = e.last; r
41
+ end
42
+ end
43
+
44
+ def where(conditions = {})
45
+ conditions = conditions.symbolize_keys
46
+ conditions.assert_valid_keys :year, :month, :day, :slug, :to_param, :tag, :category
47
+ [:year, :month, :day].each do |key|
48
+ conditions[key] = conditions[key].to_i if conditions[key].present?
49
+ end
50
+ order.select do |article|
51
+ conditions.all? do |key, value|
52
+ case key
53
+ when :tag
54
+ article.tags.include?(value) if article.attributes[:tags].present?
55
+ when :category
56
+ article.categories.include?(value) if article.attributes[:categories].present?
57
+ else
58
+ article.send(key) == value
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def feed
65
+ order.first(Village::Config.page_size)
66
+ end
67
+
68
+ def feed_last_modified
69
+ feed.first.try(:last_modified) || Time.now.utc
70
+ end
71
+
72
+ def reset!
73
+ @files = nil
74
+ @tags = nil
75
+ @categories = nil
76
+ end
77
+ end
78
+
79
+ def initialize(path)
80
+ super path
81
+ attributes[:permalink_format] = Village::Config.permalink_format
82
+ @date_str, @slug = File.basename(path).match(/^(\d+-\d+-\d+)-(.*)(\.[^.]+)$/).captures
83
+ end
84
+
85
+ def date
86
+ @date ||= Time.zone.parse(attributes[:date] || @date_str).to_date
87
+ end
88
+
89
+ def email
90
+ if attributes[:author] && attributes[:author][:email]
91
+ attributes[:author][:email]
92
+ end
93
+ end
94
+
95
+ def timestamp
96
+ date.to_time_in_current_zone
97
+ end
98
+ alias_method :last_modified, :timestamp
99
+
100
+ def visible?
101
+ timestamp <= Time.zone.now
102
+ end
103
+
104
+ def summary_html
105
+ if attributes[:summary].present?
106
+ TagHelper.content_tag :p, summary
107
+ else
108
+ html = Sanitizer.new.sanitize(content_html)
109
+ doc = Nokogiri::HTML.fragment(html)
110
+ para = doc.search('p').detect { |p| p.text.present? }
111
+ para.try(:to_html).try(:html_safe)
112
+ end
113
+ end
114
+
115
+ def to_param
116
+ case attributes[:permalink_format]
117
+ when :day then "%04d/%02d/%02d/%s" % [year, month, day, slug]
118
+ when :month then "%04d/%02d/%s" % [year, month, slug]
119
+ when :year then "%04d/%s" % [year, slug]
120
+ when :slug then slug
121
+ end
122
+ end
123
+
124
+ end
125
+ end