middleman-blog 3.4.1 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -6
  3. data/CHANGELOG.md +32 -1
  4. data/Gemfile +5 -9
  5. data/README.md +3 -2
  6. data/features/article_cli.feature +10 -1
  7. data/features/custom_collections.feature +11 -0
  8. data/features/language.feature +82 -0
  9. data/features/multiblog.feature +4 -2
  10. data/features/permalink-data.feature +12 -0
  11. data/features/summary.feature +13 -0
  12. data/features/support/env.rb +3 -0
  13. data/features/tags.feature +18 -1
  14. data/features/time_zone.feature +1 -1
  15. data/fixtures/custom-article-template-app/config.rb +3 -0
  16. data/fixtures/custom-article-template-app/my_custom_article.tt +7 -0
  17. data/fixtures/custom-article-template-app/source/index.html.erb +9 -0
  18. data/fixtures/custom-article-template-app/source/layout.erb +30 -0
  19. data/fixtures/custom-collections-sources-app/config.rb +11 -0
  20. data/fixtures/custom-collections-sources-app/source/articles/2011-01-02-another-article.html.markdown +8 -0
  21. data/fixtures/custom-collections-sources-app/source/category.html.erb +7 -0
  22. data/fixtures/custom-collections-sources-app/source/index.html.erb +5 -0
  23. data/fixtures/custom-collections-sources-app/source/layout.erb +13 -0
  24. data/fixtures/custom-collections-sources-app/source/news/2011-01-01-new-article.html.markdown +7 -0
  25. data/fixtures/language-app/config.rb +2 -0
  26. data/fixtures/language-app/locales/en.yml +4 -0
  27. data/fixtures/language-app/locales/ru.yml +4 -0
  28. data/fixtures/language-app/source/2013-09-07-english-article-with-lang-in-frontmatter.html.erb +6 -0
  29. data/fixtures/language-app/source/2013-09-07-russian-article-with-lang-in-frontmatter.html.erb +6 -0
  30. data/fixtures/language-app/source/en/2013-09-07-english-article-with-lang-in-path.html.erb +5 -0
  31. data/fixtures/language-app/source/layouts/layout.erb +8 -0
  32. data/fixtures/language-app/source/localizable/index.html.erb +5 -0
  33. data/fixtures/language-app/source/ru/2013-09-07-russian-article-with-lang-in-path.html.erb +5 -0
  34. data/fixtures/multiblog-app/source/blog1/index.html.erb +7 -0
  35. data/fixtures/permalink-data-app/config.rb +5 -0
  36. data/fixtures/permalink-data-app/source/index.html.erb +3 -0
  37. data/fixtures/permalink-data-app/source/layout.erb +14 -0
  38. data/fixtures/permalink-data-app/source/news/2011-01-01-new-article.html.markdown +7 -0
  39. data/fixtures/time-zone-app/source/blog/2013-06-24-hello.html.erb +1 -0
  40. data/lib/middleman-blog.rb +3 -8
  41. data/lib/middleman-blog/blog_article.rb +96 -60
  42. data/lib/middleman-blog/blog_data.rb +78 -76
  43. data/lib/middleman-blog/calendar_pages.rb +87 -119
  44. data/lib/middleman-blog/commands/article.rb +20 -14
  45. data/lib/middleman-blog/custom_pages.rb +30 -64
  46. data/lib/middleman-blog/extension.rb +175 -0
  47. data/lib/middleman-blog/helpers.rb +152 -0
  48. data/lib/middleman-blog/paginator.rb +127 -123
  49. data/lib/middleman-blog/tag_pages.rb +27 -45
  50. data/lib/middleman-blog/template.rb +17 -15
  51. data/lib/middleman-blog/template/config.tt +30 -33
  52. data/lib/middleman-blog/template/source/layout.erb +1 -0
  53. data/lib/middleman-blog/uri_templates.rb +58 -0
  54. data/lib/middleman-blog/version.rb +1 -1
  55. data/middleman-blog.gemspec +4 -1
  56. metadata +75 -9
  57. data/Gemfile-3.0 +0 -27
  58. data/lib/middleman-blog/extension_3_0.rb +0 -248
  59. data/lib/middleman-blog/extension_3_1.rb +0 -278
@@ -0,0 +1,152 @@
1
+ module Middleman
2
+ module Blog
3
+ # Blog-related helpers that are available to the Middleman application in +config.rb+ and in templates.
4
+ module Helpers
5
+ # All the blog instances known to this Middleman app, keyed by name. A new blog is added
6
+ # every time the blog extension is activated. Name them by setting the +:name+
7
+ # option when activating - otherwise they get an automatic name like 'blog0', 'blog1', etc.
8
+ #
9
+ # @return [Hash<Symbol,BlogExtension>] a hash of all blog instances by name
10
+ def blog_instances
11
+ @blog_instances ||= {}
12
+ end
13
+
14
+ # Retrieve a {BlogExtension} instance.
15
+ # If +blog_name+ is provided, the instance with that name will be returned.
16
+ # Otherwise, an attempt is made to find the appropriate blog controller
17
+ # for the current resource. For articles this is always available, but
18
+ # for other pages it may be necessary to name the blog in frontmatter
19
+ # using the "blog" blog_name. If there is only one blog, this method will
20
+ # always return that blog.
21
+ #
22
+ # @param [Symbol, String] blog_name Optional name of the blog to get a controller for.
23
+ # @return [BlogExtension]
24
+ def blog_controller(blog_name=nil)
25
+ if !blog_name && current_resource
26
+ blog_name = current_resource.metadata[:page]["blog"]
27
+
28
+ if !blog_name
29
+ blog_controller = current_resource.blog_controller if current_resource.respond_to?(:blog_controller)
30
+ return blog_controller if blog_controller
31
+ end
32
+ end
33
+
34
+ # In multiblog situations, force people to specify the blog
35
+ if !blog_name && blog_instances.size > 1
36
+ raise "You must either specify the blog name in calling this method or in your page frontmatter (using the 'blog' blog_name)"
37
+ end
38
+
39
+ blog_name ||= blog_instances.keys.first
40
+ blog_instances[blog_name.to_sym]
41
+ end
42
+
43
+ # Get a {BlogData} instance for the given blog. Follows the
44
+ # same rules as {#blog_controller}.
45
+ #
46
+ # @param [Symbol, String] blog_name Optional name of the blog to get data for.
47
+ # Blogs can be named as an option or will default to 'blog0', 'blog1', etc..
48
+ # @return [BlogData]
49
+ def blog(blog_name=nil)
50
+ blog_controller(blog_name).data
51
+ end
52
+
53
+ # Determine whether the currently rendering template is a {BlogArticle}.
54
+ # This can be useful in layouts and helpers.
55
+ # @return [Boolean]
56
+ def is_blog_article?
57
+ !current_article.nil?
58
+ end
59
+
60
+ # Get a {BlogArticle} representing the current article.
61
+ # @return [BlogArticle]
62
+ def current_article
63
+ article = current_resource
64
+ if article && article.is_a?(BlogArticle)
65
+ article
66
+ else
67
+ nil
68
+ end
69
+ end
70
+
71
+ # Get a path to the given tag page, based on the +taglink+ blog setting.
72
+ # @param [String] tag
73
+ # @param [Symbol, String] blog_name Optional name of the blog to use.
74
+ # @return [String]
75
+ def tag_path(tag, blog_name=nil)
76
+ build_url blog_controller(blog_name).tag_pages.link(tag)
77
+ end
78
+
79
+ # Get a path to the given year-based calendar page, based on the +year_link+ blog setting.
80
+ # @param [Number] year
81
+ # @param [Symbol, String] blog_name Optional name of the blog to use.
82
+ # @return [String]
83
+ def blog_year_path(year, blog_name=nil)
84
+ build_url blog_controller(blog_name).calendar_pages.link(year)
85
+ end
86
+
87
+ # Get a path to the given month-based calendar page, based on the +month_link+ blog setting.
88
+ # @param [Number] year
89
+ # @param [Number] month
90
+ # @param [Symbol, String] blog_name Optional name of the blog to use.
91
+ # @return [String]
92
+ def blog_month_path(year, month, blog_name=nil)
93
+ build_url blog_controller(blog_name).calendar_pages.link(year, month)
94
+ end
95
+
96
+ # Get a path to the given day-based calendar page, based on the +day_link+ blog setting.
97
+ # @param [Number] year
98
+ # @param [Number] month
99
+ # @param [Number] day
100
+ # @param [Symbol, String] blog_name Optional name of the blog to use.
101
+ # @return [String]
102
+ def blog_day_path(year, month, day, blog_name=nil)
103
+ build_url blog_controller(blog_name).calendar_pages.link(year, month, day)
104
+ end
105
+
106
+ # Whether or not pagination is enabled for this template. This can be used
107
+ # to allow a single template to work in both paginating and non-paginating modes.
108
+ # @return [Boolean]
109
+ def paginate
110
+ false
111
+ end
112
+
113
+ # Returns the list of articles to display on this particular page (when using pagination).
114
+ # @param [Symbol, String] blog_name Optional name of the blog to use.
115
+ # @return [Array<Middleman::Sitemap::Resource>]
116
+ def page_articles(blog_name=nil)
117
+ meta = current_resource.metadata
118
+ limit = meta[:page]["per_page"]
119
+
120
+ # "articles" local variable is populated by Calendar and Tag page generators
121
+ # If it's not set then use the complete list of articles
122
+ articles = meta[:locals]["articles"] || blog(blog_name).articles
123
+
124
+ limit ? articles.first(limit) : articles
125
+ end
126
+
127
+ # Generate helpers to access the path to a custom collection.
128
+ #
129
+ # For example, when using a custom property called "category" to collect articles on
130
+ # the method **category_path** will be generated.
131
+ #
132
+ # @param [Symbol] property Custom property which is being used to collect articles on
133
+ # @private
134
+ def self.generate_custom_helper(property)
135
+ define_method :"#{property}_path" do |value, blog_name=nil|
136
+ custom_pages = blog_controller(blog_name).custom_pages
137
+
138
+ if !custom_pages.key?(property)
139
+ raise "This blog does not know about the custom property #{property.inspect}"
140
+ end
141
+ build_url custom_pages[property].link(value)
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ def build_url(path)
148
+ sitemap.find_resource_by_path(path).try(:url)
149
+ end
150
+ end
151
+ end
152
+ end
@@ -1,38 +1,149 @@
1
+ require 'middleman-blog/uri_templates'
2
+
1
3
  module Middleman
2
4
  module Blog
3
-
4
5
  # A sitemap plugin that splits indexes (including tag
5
6
  # and calendar indexes) over multiple pages
6
7
  class Paginator
7
- def initialize(app, controller=nil)
8
+ include UriTemplates
9
+
10
+ def initialize(app, blog_controller)
8
11
  @app = app
9
- @blog_controller = controller
12
+ @blog_controller = blog_controller
13
+ @per_page = blog_controller.options.per_page
14
+ @page_link = blog_controller.options.page_link
10
15
  end
11
16
 
12
- def blog_data
13
- if @blog_controller
14
- @blog_controller.data
15
- else
16
- @app.blog
17
+ # Update the main sitemap resource list
18
+ # @return [void]
19
+ def manipulate_resource_list(resources)
20
+ new_resources = []
21
+
22
+ resources.each do |res|
23
+ next if res.ignored?
24
+
25
+ # Avoid recomputing metadata over and over
26
+ md = res.metadata
27
+
28
+ next unless md[:page]["pageable"]
29
+
30
+ # Skip other blogs' resources
31
+ next unless match_blog(res, md)
32
+
33
+ # "articles" local variable is populated by Calendar and Tag page generators
34
+ # If it's not set then use the complete list of articles
35
+ # TODO: Some way to allow the frontmatter to specify the article filter?
36
+ articles = md[:locals]["articles"] || @blog_controller.data.articles
37
+
38
+ # Allow blog.per_page and blog.page_link to be overridden in the frontmatter
39
+ per_page = md[:page]["per_page"] || @per_page
40
+ page_link = uri_template(md[:page]["page_link"] || @page_link)
41
+
42
+ num_pages = (articles.length / per_page.to_f).ceil
43
+
44
+ # Add the pagination metadata to the base page (page 1)
45
+ res.add_metadata locals: page_locals(1, num_pages, per_page, nil, articles)
46
+
47
+ prev_page_res = res
48
+
49
+ # Create additional resources for the 2nd and subsequent pages.
50
+ 2.upto(num_pages) do |page_num|
51
+ p = page_resource(res, page_num, page_link)
52
+
53
+ # Copy the metadata from the base page
54
+ p.add_metadata md
55
+ p.add_metadata locals: page_locals(page_num, num_pages, per_page, prev_page_res, articles)
56
+
57
+ # Add a reference in the previous page to this page
58
+ prev_page_res.add_metadata locals: { 'next_page' => p }
59
+
60
+ prev_page_res = p
61
+
62
+ new_resources << p
63
+ end
17
64
  end
65
+
66
+ resources + new_resources
18
67
  end
19
68
 
20
- def blog_options
21
- if @blog_controller
22
- @blog_controller.options
23
- else
24
- @app.blog.options
69
+ private
70
+
71
+ # Does this resource match the blog controller for this paginator?
72
+ # @return [Boolean]
73
+ def match_blog(res, md)
74
+ res_controller = md[:locals]["blog_controller"] || (res.respond_to?(:blog_controller) && res.blog_controller)
75
+ return false if res_controller && res_controller != @blog_controller
76
+ override_controller = md[:page]["blog"]
77
+ return false if override_controller && override_controller.to_s != @blog_controller.name.to_s
78
+
79
+ true
80
+ end
81
+
82
+ # Generate a resource for a particular page
83
+ # @param [Sitemap::Resource] res the original resource
84
+ # @param [Integer] page_num the page number to generate a resource for
85
+ # @param [String] page_link The pagination link path component template
86
+ def page_resource(res, page_num, page_link)
87
+ path = page_sub(res, page_num, page_link)
88
+ Sitemap::Resource.new(@app.sitemap, path, res.source_file).tap do |p|
89
+ # Copy the proxy state from the base page.
90
+ p.proxy_to(res.proxied_to) if res.proxy?
25
91
  end
26
92
  end
27
93
 
94
+ # @param [Integer] page_num the page number to generate a resource for
95
+ # @param [Integer] num_pages Total number of pages
96
+ # @param [Integer] per_page How many articles per page
97
+ # @param [Sitemap::Resource] prev_page_res The resource of the previous page
98
+ # @param [Array<Sitemap::Resource>] articles The list of all articles
99
+ def page_locals(page_num, num_pages, per_page, prev_page_res, articles)
100
+ # Index into articles of the first article of this page
101
+ page_start = (page_num - 1) * per_page
102
+
103
+ # Index into articles of the last article of this page
104
+ page_end = (page_num * per_page) - 1
105
+
106
+ {
107
+ # Set a flag to allow templates to be used with and without pagination
108
+ 'paginate' => true,
109
+
110
+ # Include the numbers, useful for displaying "Page X of Y"
111
+ 'page_number' => page_num,
112
+ 'num_pages' => num_pages,
113
+ 'per_page' => per_page,
114
+
115
+ # The range of article numbers on this page
116
+ # (1-based, for showing "Items X to Y of Z")
117
+ 'page_start' => page_start + 1,
118
+ 'page_end' => [page_end + 1, articles.length].min,
119
+
120
+ # These contain the next and previous page.
121
+ # They are set to nil if there are no more pages.
122
+ # The nils are overwritten when the later pages are generated, below.
123
+ 'next_page' => nil,
124
+ 'prev_page' => prev_page_res,
125
+
126
+ # The list of articles for this page.
127
+ 'page_articles' => articles[page_start..page_end],
128
+
129
+ # Include the articles so that non-proxied pages can use "articles" instead
130
+ # of "blog.articles" for consistency with the calendar and tag templates.
131
+ 'articles' => articles,
132
+ 'blog_controller' => @blog_controller
133
+ }
134
+ end
135
+
28
136
  # Substitute the page number into the resource URL.
137
+ # @param [Middleman::Sitemap::Resource] res The resource to generate pages for
138
+ # @param [Integer] page_num The page page_number
139
+ # @param [String] page_link The pagination link path component template
29
140
  # @return [String]
30
- def page_sub(res, num, page_link)
31
- if num == 1
141
+ def page_sub(res, page_num, page_link)
142
+ if page_num == 1
32
143
  # First page has an unmodified URL.
33
144
  res.path
34
145
  else
35
- page_url = page_link.sub(":num", num.to_s)
146
+ page_url = apply_uri_template page_link, num: page_num
36
147
  index_re = %r{(^|/)#{Regexp.escape(@app.index_file)}$}
37
148
  if res.path =~ index_re
38
149
  res.path.sub(index_re, "\\1#{page_url}/#{@app.index_file}")
@@ -41,113 +152,6 @@ module Middleman
41
152
  end
42
153
  end
43
154
  end
44
-
45
- # Update the main sitemap resource list
46
- # @return [void]
47
- def manipulate_resource_list(resources)
48
- new_resources = []
49
-
50
- resources.each do |res|
51
- next if res.ignored?
52
-
53
- md = res.metadata
54
-
55
- # Skip other blogs' resources
56
- res_controller = md[:locals]["blog_controller"] || (res.respond_to?(:blog_controller) && res.blog_controller)
57
- next if @blog_controller && res_controller && (res_controller != @blog_controller)
58
- override_controller = md[:page]["blog"]
59
- next if @blog_controller && override_controller && override_controller != @blog_controller.uid
60
-
61
- if md[:page]["pageable"]
62
- # "articles" local variable is populated by Calendar and Tag page generators
63
- # If it's not set then use the complete list of articles
64
- # TODO: Some way to allow the frontmatter to specify the article filter?
65
- articles = md[:locals]["articles"] || self.blog_data.articles
66
-
67
- # Allow blog.per_page and blog.page_link to be overridden in the frontmatter
68
- per_page = md[:page]["per_page"] || self.blog_options.per_page
69
- page_link = md[:page]["page_link"] || self.blog_options.page_link
70
-
71
- num_pages = (articles.length / per_page.to_f).ceil
72
-
73
- # Add the pagination metadata to the base page (page 1)
74
- res.add_metadata :locals => {
75
- # Set a flag to allow templates to be used with and without pagination
76
- 'paginate' => true,
77
-
78
- # Include the numbers, useful for displaying "Page X of Y"
79
- 'page_number' => 1,
80
- 'num_pages' => num_pages,
81
- 'per_page' => per_page,
82
-
83
- # The range of article numbers on this page
84
- # (1-based, for showing "Items X to Y of Z")
85
- 'page_start' => 1,
86
- 'page_end' => [per_page, articles.length].min,
87
-
88
- # These contain the next and previous page.
89
- # They are set to nil if there are no more pages.
90
- # The nils are overwritten when the later pages are generated, below.
91
- 'next_page' => nil,
92
- 'prev_page' => nil,
93
-
94
- # The list of articles for this page.
95
- 'page_articles' => articles[0..per_page-1],
96
-
97
- # Include the articles so that non-proxied pages can use "articles" instead
98
- # of "blog.articles" for consistency with the calendar and tag templates.
99
- 'articles' => articles,
100
- 'blog_controller' => @blog_controller
101
- }
102
-
103
- prev_page_res = res
104
-
105
- # Create additional resources for the 2nd and subsequent pages.
106
- (2..num_pages).each do |num|
107
- p = ::Middleman::Sitemap::Resource.new(
108
- @app.sitemap,
109
- page_sub(res, num, page_link),
110
- res.source_file
111
- )
112
-
113
- # Copy the metadata from the base page.
114
- p.add_metadata md
115
-
116
- # Copy the proxy state from the base page.
117
- p.proxy_to(res.proxied_to) if res.proxy?
118
-
119
- page_start = (num-1)*per_page
120
- page_end = (num*per_page)-1
121
-
122
- # Add pagination metadata, meanings as above.
123
- p.add_metadata :locals => {
124
- 'paginate' => true,
125
- 'page_number' => num,
126
- 'num_pages' => num_pages,
127
- 'per_page' => per_page,
128
- 'page_start' => page_start+1,
129
- 'page_end' => [page_end+1, articles.length].min,
130
- 'next_page' => nil,
131
- 'prev_page' => prev_page_res,
132
- 'page_articles' => articles[page_start..page_end],
133
- 'articles' => articles,
134
- 'blog_controller' => @blog_controller
135
- }
136
-
137
- # Add a reference in the previous page to this page
138
- prev_page_res.add_metadata :locals => {
139
- 'next_page' => p
140
- }
141
-
142
- prev_page_res = p
143
-
144
- new_resources << p
145
- end
146
- end
147
- end
148
-
149
- resources + new_resources
150
- end
151
155
  end
152
156
  end
153
157
  end
@@ -1,67 +1,49 @@
1
+ require 'middleman-blog/uri_templates'
2
+
1
3
  module Middleman
2
4
  module Blog
3
-
4
- # A sitemap plugin that adds tag pages to the sitemap
5
- # based on the tags of blog articles.
5
+ # A sitemap resource manipulator that adds a tag page to the sitemap
6
+ # for each tag in the associated blog
6
7
  class TagPages
7
- class << self
8
- # Get a path to the given tag, based on the :taglink setting.
9
- # @param [Hash] blog_options
10
- # @param [String] tag
11
- # @return [String]
12
- def link(blog_options, tag)
13
- ::Middleman::Util.normalize_path(blog_options.taglink.sub(':tag', tag.parameterize))
14
- end
15
- end
8
+ include UriTemplates
16
9
 
17
- def initialize(app, controller=nil)
18
- @app = app
19
- @blog_controller = controller
10
+ def initialize(app, blog_controller)
11
+ @sitemap = app.sitemap
12
+ @blog_controller = blog_controller
13
+ @tag_link_template = uri_template blog_controller.options.taglink
14
+ @tag_template = blog_controller.options.tag_template
15
+ @blog_data = blog_controller.data
20
16
  end
21
17
 
22
- def blog_data
23
- if @blog_controller
24
- @blog_controller.data
25
- else
26
- @app.blog
27
- end
18
+ # Get a path to the given tag, based on the :taglink setting.
19
+ # @param [String] tag
20
+ # @return [String]
21
+ def link(tag)
22
+ apply_uri_template @tag_link_template, tag: safe_parameterize(tag)
28
23
  end
29
24
 
30
- def blog_options
31
- if @blog_controller
32
- @blog_controller.options
33
- else
34
- @app.blog.options
35
- end
36
- end
37
-
38
25
  # Update the main sitemap resource list
39
26
  # @return [void]
40
27
  def manipulate_resource_list(resources)
41
- resources + self.blog_data.tags.map do |tag, articles|
42
- path = TagPages.link(self.blog_options, tag)
43
-
44
- p = ::Middleman::Sitemap::Resource.new(
45
- @app.sitemap,
46
- path
47
- )
48
- p.proxy_to(self.blog_options.tag_template)
28
+ resources + @blog_data.tags.map do |tag, articles|
29
+ tag_page_resource(tag, articles)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def tag_page_resource(tag, articles)
36
+ Sitemap::Resource.new(@sitemap, link(tag)).tap do |p|
37
+ p.proxy_to(@tag_template)
49
38
 
50
39
  # Add metadata in local variables so it's accessible to
51
40
  # later extensions
52
- p.add_metadata :locals => {
41
+ p.add_metadata locals: {
53
42
  'page_type' => 'tag',
54
43
  'tagname' => tag,
55
44
  'articles' => articles,
56
45
  'blog_controller' => @blog_controller
57
46
  }
58
- # Add metadata in instance variables for backwards compatibility
59
- p.add_metadata do
60
- @tag = tag
61
- @articles = articles
62
- end
63
-
64
- p
65
47
  end
66
48
  end
67
49
  end