middleman-blog 3.4.1 → 3.5.0

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.
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