middleman-blog 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.travis.yml +5 -7
  4. data/CHANGELOG.md +21 -0
  5. data/CONTRIBUTING.md +43 -0
  6. data/Gemfile +22 -14
  7. data/Gemfile-3.0 +27 -0
  8. data/{LICENSE → LICENSE.md} +2 -2
  9. data/README.md +33 -28
  10. data/Rakefile +17 -1
  11. data/features/blog_sources.feature +13 -0
  12. data/features/calendar_multiblog.feature +153 -0
  13. data/features/multiblog.feature +51 -0
  14. data/features/paginate_multiblog.feature +270 -0
  15. data/features/summary.feature +43 -0
  16. data/features/support/env.rb +6 -1
  17. data/features/tags_multiblog.feature +125 -0
  18. data/features/time_zone.feature +5 -0
  19. data/fixtures/blog-sources-app/source/blog/2013-08-08-slug-from-filename.html.markdown +7 -0
  20. data/fixtures/calendar-multiblog-app/config.rb +17 -0
  21. data/fixtures/calendar-multiblog-app/source/blog1/2011-01-01-new-article.html.markdown +7 -0
  22. data/fixtures/calendar-multiblog-app/source/blog1/2011-01-02-another-article.html.markdown +8 -0
  23. data/fixtures/calendar-multiblog-app/source/blog2/2011-01-01-new-article.html.markdown +7 -0
  24. data/fixtures/calendar-multiblog-app/source/blog2/2011-01-02-another-article.html.markdown +8 -0
  25. data/fixtures/calendar-multiblog-app/source/calendar1.html.erb +13 -0
  26. data/fixtures/calendar-multiblog-app/source/calendar2.html.erb +13 -0
  27. data/fixtures/calendar-multiblog-app/source/index.html.erb +15 -0
  28. data/fixtures/calendar-multiblog-app/source/layout.erb +15 -0
  29. data/fixtures/default-template-app/Gemfile +6 -0
  30. data/fixtures/default-template-app/config.rb +35 -0
  31. data/fixtures/default-template-app/source/2013-04-01-new-article.html.markdown +25 -0
  32. data/{features/encoding.feature → fixtures/default-template-app/source/about-me.html.erb} +0 -0
  33. data/fixtures/default-template-app/source/archives.html.erb +10 -0
  34. data/fixtures/default-template-app/source/index.html.erb +11 -0
  35. data/fixtures/default-template-app/source/javascripts/_zepto.pjax.js +744 -0
  36. data/fixtures/default-template-app/source/javascripts/app.js +11 -0
  37. data/fixtures/default-template-app/source/javascripts/modernizr.js +1 -0
  38. data/fixtures/default-template-app/source/layouts/layout.erb +62 -0
  39. data/fixtures/default-template-app/source/stylesheets/app.css.scss +109 -0
  40. data/fixtures/multiblog-app/config.rb +0 -0
  41. data/fixtures/multiblog-app/source/blog1/2012-12-12-other-article.html.markdown +5 -0
  42. data/fixtures/multiblog-app/source/blog2/2011/01/01/new-article.html.markdown +6 -0
  43. data/fixtures/multiblog-app/source/index.html.erb +7 -0
  44. data/fixtures/multiblog-app/source/layout.erb +30 -0
  45. data/fixtures/no-title-app/config.rb +3 -0
  46. data/fixtures/no-title-app/source/2013-08-07.html.markdown +6 -0
  47. data/fixtures/no-title-app/source/2013-08-08.html.markdown +7 -0
  48. data/fixtures/no-title-app/source/layout.erb +13 -0
  49. data/fixtures/paginate-multiblog-app/config.rb +31 -0
  50. data/fixtures/paginate-multiblog-app/source/blog1/2011-01-01-test-article.html.markdown +6 -0
  51. data/fixtures/paginate-multiblog-app/source/blog1/2011-01-02-test-article.html.markdown +6 -0
  52. data/fixtures/paginate-multiblog-app/source/blog1/2011-01-03-test-article.html.markdown +6 -0
  53. data/fixtures/paginate-multiblog-app/source/blog1/2011-01-04-test-article.html.markdown +6 -0
  54. data/fixtures/paginate-multiblog-app/source/blog1/2011-01-05-test-article.html.markdown +6 -0
  55. data/fixtures/paginate-multiblog-app/source/blog1/2011-02-01-test-article.html.markdown +6 -0
  56. data/fixtures/paginate-multiblog-app/source/blog1/index.html.erb +27 -0
  57. data/fixtures/paginate-multiblog-app/source/blog2/2011-01-01-test-article.html.markdown +6 -0
  58. data/fixtures/paginate-multiblog-app/source/blog2/2011-01-02-test-article.html.markdown +6 -0
  59. data/fixtures/paginate-multiblog-app/source/blog2/2011-01-03-test-article.html.markdown +6 -0
  60. data/fixtures/paginate-multiblog-app/source/blog2/2011-01-04-test-article.html.markdown +6 -0
  61. data/fixtures/paginate-multiblog-app/source/blog2/2011-01-05-test-article.html.markdown +6 -0
  62. data/fixtures/paginate-multiblog-app/source/blog2/index.html.erb +27 -0
  63. data/fixtures/paginate-multiblog-app/source/blog3/2011-01-01-test-article.html.markdown +6 -0
  64. data/fixtures/paginate-multiblog-app/source/blog3/2011-01-02-test-article.html.markdown +6 -0
  65. data/fixtures/paginate-multiblog-app/source/blog3/2011-01-03-test-article.html.markdown +6 -0
  66. data/fixtures/paginate-multiblog-app/source/blog3/2011-01-04-test-article.html.markdown +6 -0
  67. data/fixtures/paginate-multiblog-app/source/blog3/2011-01-05-test-article.html.markdown +6 -0
  68. data/fixtures/paginate-multiblog-app/source/blog3/2011-02-01-test-article.html.markdown +6 -0
  69. data/fixtures/paginate-multiblog-app/source/blog3/2011-02-02-test-article.html.markdown +6 -0
  70. data/fixtures/paginate-multiblog-app/source/blog3/index.html.erb +28 -0
  71. data/fixtures/paginate-multiblog-app/source/calendar1.html.erb +28 -0
  72. data/fixtures/paginate-multiblog-app/source/calendar2.html.erb +28 -0
  73. data/fixtures/paginate-multiblog-app/source/calendar3.html.erb +28 -0
  74. data/fixtures/paginate-multiblog-app/source/layout.erb +15 -0
  75. data/fixtures/paginate-multiblog-app/source/tag1.html.erb +23 -0
  76. data/fixtures/paginate-multiblog-app/source/tag2.html.erb +23 -0
  77. data/fixtures/paginate-multiblog-app/source/tag3.html.erb +19 -0
  78. data/fixtures/summary-app/config.rb +1 -0
  79. data/fixtures/summary-app/source/2011-01-01-article-with-no-summary-separator-and-comments-in-the-summary.html.erb +10 -0
  80. data/fixtures/summary-app/source/2011-01-01-article-with-standard-summary-separator.html.markdown +7 -0
  81. data/fixtures/summary-app/source/2012-06-19-article-with-no-summary-separator.html.markdown +9 -0
  82. data/fixtures/summary-app/source/2013-05-08-article-with-custom-separator.html.markdown +7 -0
  83. data/fixtures/summary-app/source/index.html.erb +5 -0
  84. data/fixtures/tags-multiblog-app/config.rb +15 -0
  85. data/fixtures/tags-multiblog-app/source/blog1/2011-01-01-new-article.html.markdown +7 -0
  86. data/fixtures/tags-multiblog-app/source/blog1/2011-01-02-another-article.html.markdown +9 -0
  87. data/fixtures/tags-multiblog-app/source/blog2/2011-01-01-new-article.html.markdown +7 -0
  88. data/fixtures/tags-multiblog-app/source/blog2/2011-01-02-another-article.html.markdown +9 -0
  89. data/fixtures/tags-multiblog-app/source/index.html.erb +8 -0
  90. data/fixtures/tags-multiblog-app/source/layout.erb +13 -0
  91. data/fixtures/tags-multiblog-app/source/tag1.html.erb +7 -0
  92. data/fixtures/tags-multiblog-app/source/tag2.html.erb +7 -0
  93. data/fixtures/time-zone-app/config.rb +5 -0
  94. data/fixtures/time-zone-app/source/blog/2013-06-24-hello.html.erb +5 -0
  95. data/lib/middleman-blog.rb +9 -4
  96. data/lib/middleman-blog/blog_article.rb +47 -21
  97. data/lib/middleman-blog/blog_data.rb +11 -4
  98. data/lib/middleman-blog/calendar_pages.rb +40 -25
  99. data/lib/middleman-blog/{extension.rb → extension_3_0.rb} +20 -16
  100. data/lib/middleman-blog/extension_3_1.rb +208 -0
  101. data/lib/middleman-blog/paginator.rb +33 -7
  102. data/lib/middleman-blog/tag_pages.rb +26 -9
  103. data/lib/middleman-blog/template/shared/Gemfile.tt +4 -2
  104. data/lib/middleman-blog/template/source/calendar.html.erb +1 -1
  105. data/lib/middleman-blog/template/source/feed.xml.builder +8 -7
  106. data/lib/middleman-blog/template/source/index.html.erb +1 -1
  107. data/lib/middleman-blog/template/source/tag.html.erb +1 -1
  108. data/lib/middleman-blog/truncate_html.rb +10 -1
  109. data/lib/middleman-blog/version.rb +1 -1
  110. data/middleman-blog.gemspec +12 -15
  111. metadata +180 -14
@@ -0,0 +1,5 @@
1
+ Feature: Setup time zoen
2
+ Scenario: Time.zone can be set through set at config.rb
3
+ Given the Server is running at "time-zone-app"
4
+ When I go to "/blog/2013/06/24/hello.html"
5
+ Then I should see "(GMT+09:00) Tokyo"
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: "Testing Article"
3
+ slug: slug-from-frontmatter
4
+ date: 2013-08-08
5
+ ---
6
+
7
+ Article with slug specified in frontmatter
@@ -0,0 +1,17 @@
1
+ Time.zone = "Pacific Time (US & Canada)"
2
+
3
+ activate :blog do |blog|
4
+ blog.name = "blog_name_1"
5
+ blog.prefix = "blog1"
6
+ blog.sources = ":year-:month-:day-:title.html"
7
+ blog.permalink = ":year-:month-:day-:title.html"
8
+ blog.calendar_template = "calendar1.html"
9
+ end
10
+
11
+ activate :blog do |blog|
12
+ blog.name = "blog_name_2"
13
+ blog.prefix = "blog2"
14
+ blog.sources = ":year-:month-:day-:title.html"
15
+ blog.permalink = ":year-:month-:day-:title.html"
16
+ blog.calendar_template = "calendar2.html"
17
+ end
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: "Newer Article"
3
+ date: 2011-01-01
4
+ tags: foo, bar
5
+ ---
6
+
7
+ Newer Article Content
@@ -0,0 +1,8 @@
1
+ ---
2
+ title: "Another Article"
3
+ date: 2011-01-02 5:45 PM PST
4
+ tags:
5
+ - foo
6
+ ---
7
+
8
+ Another Article Content
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: "Newer Article"
3
+ date: 2011-01-01
4
+ tags: foo, bar
5
+ ---
6
+
7
+ Newer Article Content
@@ -0,0 +1,8 @@
1
+ ---
2
+ title: "Another Article"
3
+ date: 2011-01-02 5:45 PM PST
4
+ tags:
5
+ - foo
6
+ ---
7
+
8
+ Another Article Content
@@ -0,0 +1,13 @@
1
+ Year1: '<%= year %>'
2
+ Month1: '<%= month if page_type == "month" or page_type == "day" %>'
3
+ Day1: '<%= day if page_type == "day" %>'
4
+
5
+ <% articles[0...5].each_with_index do |article, i| %>
6
+ <article class="<%= (i == 0) ? 'first' : '' %>">
7
+ <h1><a href="<%= article.url %>"><%= article.title %></a> <span><%= article.date.strftime('%b %e %Y') %></span></h1>
8
+
9
+ <%= article.summary %>
10
+
11
+ <div class="more"><a href="<%= article.url %>">read on &raquo;</a></div>
12
+ </article>
13
+ <% end %>
@@ -0,0 +1,13 @@
1
+ Year2: '<%= year %>'
2
+ Month2: '<%= month if page_type == "month" or page_type == "day" %>'
3
+ Day2: '<%= day if page_type == "day" %>'
4
+
5
+ <% articles[0...5].each_with_index do |article, i| %>
6
+ <article class="<%= (i == 0) ? 'first' : '' %>">
7
+ <h1><a href="<%= article.url %>"><%= article.title %></a> <span><%= article.date.strftime('%b %e %Y') %></span></h1>
8
+
9
+ <%= article.summary %>
10
+
11
+ <div class="more"><a href="<%= article.url %>">read on &raquo;</a></div>
12
+ </article>
13
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <% blog(:blog_name_1).articles[0...12].each do |article| %>
2
+ <li><a href="<%= article.url %>"><%= article.title %></a> <time><%= article.date.strftime('%b %e') %></time></li>
3
+ <% end %>
4
+
5
+ <% blog(:blog_name_2).articles[0...12].each do |article| %>
6
+ <li><a href="<%= article.url %>"><%= article.title %></a> <time><%= article.date.strftime('%b %e') %></time></li>
7
+ <% end %>
8
+
9
+ Year Path1: '<%= blog_year_path(2011, :blog_name_1) %>'
10
+ Month Path1: '<%= blog_month_path(2011,1, :blog_name_1) %>'
11
+ Day Path1: '<%= blog_day_path(2011,1,1, :blog_name_1) %>'
12
+
13
+ Year Path2: '<%= blog_year_path(2011, :blog_name_2) %>'
14
+ Month Path2: '<%= blog_month_path(2011,1, :blog_name_2) %>'
15
+ Day Path2: '<%= blog_day_path(2011,1,1, :blog_name_2) %>'
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ </head>
5
+ <body>
6
+ <% if is_blog_article? %>
7
+ <%= yield %>
8
+ Url: <%= current_article.url %>
9
+ Previous: <%= current_article.previous_article.try(:url) %>
10
+ Next: <%= current_article.next_article.try(:url) %>
11
+ <% else %>
12
+ <%= yield %>
13
+ <% end %>
14
+ </body>
15
+ </html>
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "middleman", :github => "middleman/middleman"
4
+ gem "middleman-blog", :github => "middleman/middleman-blog"
5
+ gem "middleman-syntax", :github => "middleman/middleman-syntax"
6
+ gem "zurb-foundation", "~> 4.1.6"
@@ -0,0 +1,35 @@
1
+ require 'zurb-foundation'
2
+
3
+ spec = Gem::Specification.find_by_name("zurb-foundation")
4
+ set :js_assets_paths, [File.join(spec.gem_dir, "js")]
5
+
6
+ activate :directory_indexes
7
+
8
+ activate :blog
9
+
10
+ set :blog_name, "Deep Thoughts"
11
+ set :blog_author, "Nick Adams"
12
+ set :blog_avatar, "http://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50"
13
+ set :top_nav_title, { :title => "Home", :target => "index.html" }
14
+ set :top_nav_items, [
15
+ { :title => "About Me", :target => "about-me.html" },
16
+ { :title => "Archives", :target => "archives.html" }
17
+ # { :title => "Other Page", :target => "other-page.html" }
18
+ ]
19
+
20
+ helpers do
21
+ def page_title
22
+ title = blog_name.dup
23
+ if current_page.data.title
24
+ title << ": #{current_page.data.title}"
25
+ elsif is_blog_article?
26
+ title << ": #{current_article.title}"
27
+ end
28
+ title
29
+ end
30
+
31
+ def link_to_with_active(title, url, class_name = 'active')
32
+ active_class = (current_resource == sitemap.find_resource_by_path(url)) ? class_name : ''
33
+ link_to(title, url, :class => active_class)
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ ---
2
+ title: "This is a headline for a blog post if it’s two lines long."
3
+ ---
4
+
5
+ Morbi dapibus scelerisque risus, non auctor enim varius vitae. Proin in eros tortor. Aliquam erat volutpat. Sed tempus mollis faucibus. Nunc nunc dolor, ullamcorper rhoncus malesuada in, consectetur vitae nisi. Nulla facilisi.
6
+
7
+ ## Heading Example
8
+
9
+ Quisque varius euismod tempor. Nullam nisi risus, tempor in auctor ac, tempor eget nisl. Pellentesque fermentum luctus sapien vel pretium. In hac habitasse platea dictumst. Aliquam ac purus nec enim imperdiet vehicula a a risus.
10
+
11
+ ### Heading Example
12
+
13
+ Quisque varius euismod tempor. Nullam nisi risus, tempor in auctor ac, tempor eget nisl. Pellentesque fermentum luctus sapien vel pretium. In hac habitasse platea dictumst. Aliquam ac purus nec enim imperdiet vehicula a a risus.
14
+
15
+ #### Heading Example
16
+
17
+ Quisque varius euismod tempor. Nullam nisi risus, tempor in auctor ac, tempor eget nisl. Pellentesque fermentum luctus sapien vel pretium. In hac habitasse platea dictumst. Aliquam ac purus nec enim imperdiet vehicula a a risus.
18
+
19
+ ##### Heading Example
20
+
21
+ Quisque varius euismod tempor. Nullam nisi risus, tempor in auctor ac, tempor eget nisl. Pellentesque fermentum luctus sapien vel pretium. In hac habitasse platea dictumst. Aliquam ac purus nec enim imperdiet vehicula a a risus.
22
+
23
+ ###### Heading Example
24
+
25
+ Quisque varius euismod tempor. Nullam nisi risus, tempor in auctor ac, tempor eget nisl. Pellentesque fermentum luctus sapien vel pretium. In hac habitasse platea dictumst. Aliquam ac purus nec enim imperdiet vehicula a a risus.
@@ -0,0 +1,10 @@
1
+ <section id="recent">
2
+ <h2>
3
+ Archive
4
+ </h2>
5
+ <ul class="disc">
6
+ <% blog.articles.each do |article| %>
7
+ <li><a href="<%= article.url %>"><%= article.title %></a> <time><%= article.date.strftime('%b %e') %></time></li>
8
+ <% end %>
9
+ </ul>
10
+ </section>
@@ -0,0 +1,11 @@
1
+ <% blog.articles[0...5].each_with_index do |article, i| %>
2
+ <article class="<%= (i == 0) ? 'first' : '' %>">
3
+ <span><%= article.date.strftime('%b %e %Y') %></span>
4
+
5
+ <h1><a href="<%= article.url %>"><%= article.title %></a></h1>
6
+
7
+ <%= article.summary %>
8
+
9
+ <div class="more"><a href="<%= article.url %>">read on &raquo;</a></div>
10
+ </article>
11
+ <% end %>
@@ -0,0 +1,744 @@
1
+ // zepto.pjax.js
2
+ // copyright chris wanstrath
3
+ // https://github.com/defunkt/jquery-pjax
4
+ // https://github.com/shogun70/jquery-pjax - duration
5
+ // https://github.com/jimisaacs/zepto-pjax
6
+ // https://github.com/najeira/zepto-pjax
7
+ // https://github.com/cshenoy/zepto-pjax - zepto1.0 w/duration
8
+
9
+ (function($){
10
+
11
+ /**
12
+ * Add noop to Zepto - should be in there already
13
+ * It's good to have to only reference one dummy function rather than create multiple empties.
14
+ */
15
+ $.noop = function(){};
16
+
17
+
18
+ // When called on a link, fetches the href with ajax into the
19
+ // container specified as the first parameter or with the data-pjax
20
+ // attribute on the link itself.
21
+ //
22
+ // Tries to make sure the back button and ctrl+click work the way
23
+ // you'd expect.
24
+ //
25
+ // Accepts a Zepto ajax options object that may include these
26
+ // pjax specific options:
27
+ //
28
+ // container - Where to stick the response body. Usually a String selector.
29
+ // $(container).html(xhr.responseBody)
30
+ // push - Whether to pushState the URL. Defaults to true (of course).
31
+ // replace - Want to use replaceState instead? That's cool.
32
+ //
33
+ // For convenience the first parameter can be either the container or
34
+ // the options object.
35
+ //
36
+ // Returns the jQuery object
37
+ $.fn.pjax = function( selector, container, options ) {
38
+ return this.on('click.pjax', selector, function(event){
39
+ handleClick(event, container, options);
40
+ });
41
+ };
42
+
43
+ // Public: pjax on click handler
44
+ //
45
+ // Exported as $.pjax.click.
46
+ //
47
+ // event - "click" Zepto.Event
48
+ // options - pjax options
49
+ //
50
+ // Examples
51
+ //
52
+ // $('a').live('click', $.pjax.click)
53
+ // // is the same as
54
+ // $('a').pjax()
55
+ //
56
+ // $(document).on('click', 'a', function(event) {
57
+ // var container = $(this).closest('[data-pjax-container]')
58
+ // return $.pjax.click(event, container)
59
+ // })
60
+ //
61
+ // Returns false if pjax runs, otherwise nothing.
62
+ function handleClick(event, container, options) {
63
+ options = optionsFor(container, options);
64
+ var link = event.currentTarget;
65
+
66
+ if (link.tagName.toUpperCase() !== 'A')
67
+ throw "$.fn.pjax or $.pjax.click requires an anchor element";
68
+
69
+ // Middle click, cmd click, and ctrl click should open
70
+ // links in a new tab as normal.
71
+ if ( event.which > 1 || event.metaKey || event.ctrlKey )
72
+ return;
73
+
74
+ // Ignore cross origin links
75
+ if ( location.protocol !== link.protocol || location.host !== link.host )
76
+ return;
77
+
78
+ // Ignore anchors on the same page
79
+ if (link.hash && link.href.replace(link.hash, '') ===
80
+ location.href.replace(location.hash, ''))
81
+ return;
82
+
83
+ // Ignore empty anchor "foo.html#"
84
+ if (link.href === location.href + '#')
85
+ return;
86
+
87
+ var defaults = {
88
+ url: link.href,
89
+ container: $(link).attr('data-pjax'),
90
+ target: link,
91
+ clickedElement: $(link), // DEPRECATED: use target
92
+ fragment: null
93
+ };
94
+
95
+ $.pjax($.extend({}, defaults, options));
96
+
97
+ event.preventDefault();
98
+ }
99
+
100
+
101
+ // Loads a URL with ajax, puts the response body inside a container,
102
+ // then pushState()'s the loaded URL.
103
+ //
104
+ // Works just like $.ajax in that it accepts a jQuery ajax
105
+ // settings object (with keys like url, type, data, etc).
106
+ //
107
+ // Accepts these extra keys:
108
+ //
109
+ // container - Where to stick the response body.
110
+ // $(container).html(xhr.responseBody)
111
+ // push - Whether to pushState the URL. Defaults to true (of course).
112
+ // replace - Want to use replaceState instead? That's cool.
113
+ //
114
+ // Use it just like $.ajax:
115
+ //
116
+ // var xhr = $.pjax({ url: this.href, container: '#main' })
117
+ // console.log( xhr.readyState )
118
+ //
119
+ // Returns whatever $.ajax returns.
120
+ var pjax = $.pjax = function( options ) {
121
+
122
+ // options from handleClick fn
123
+ options = $.extend({}, $.ajaxSettings, pjax.defaults, options);
124
+
125
+ if ($.isFunction(options.url)) {
126
+ options.url = options.url();
127
+ }
128
+
129
+ var target = options.target;
130
+
131
+ // DEPRECATED: use options.target
132
+ if (!target && options.clickedElement) target = options.clickedElement[0];
133
+
134
+ var hash = parseURL(options.url).hash;
135
+
136
+ // DEPRECATED: Save references to original event callbacks. However,
137
+ // listening for custom pjax:* events is prefered.
138
+ var oldBeforeSend = options.beforeSend,
139
+ oldComplete = options.complete,
140
+ oldSuccess = options.success,
141
+ oldError = options.error;
142
+
143
+ var context = options.context = findContainerFor(options.container);
144
+
145
+ // console.log(options, $.ajaxSettings);
146
+ //console.log(options, context, context.contents());
147
+
148
+ // We want the browser to maintain two separate internal caches: one
149
+ // for pjax'd partial page loads and one for normal page loads.
150
+ // Without adding this secret parameter, some browsers will often
151
+ // confuse the two.
152
+ if (!options.data) options.data = {};
153
+ options.data._pjax = context.selector;
154
+
155
+ function fire(type, args) {
156
+ console.log(' type ',type);
157
+ var event = $.Event(type, { relatedTarget: target });
158
+ context.trigger(event, args);
159
+ if (event.isDefaultPrevented) {
160
+ return !event.isDefaultPrevented();
161
+ }
162
+ return !event.defaultPrevented;
163
+ }
164
+
165
+ var timeoutTimer, durationTimer;
166
+ var success, complete, error;
167
+
168
+ options.beforeSend = function(xhr, settings) {
169
+ var timeout = settings.timeout;
170
+ if (settings.timeout > 0) {
171
+ timeoutTimer = setTimeout(function() {
172
+ if (fire('pjax:timeout', [xhr, options]))
173
+ xhr.abort('timeout');
174
+ }, settings.timeout);
175
+
176
+ // Clear timeout setting so jquerys internal timeout isn't invoked
177
+ settings.timeout = 0;
178
+ }
179
+ // No timeout for non-GET requests
180
+ // Its not safe to request the resource again with a fallback method.
181
+ if (settings.type !== 'GET') {
182
+ settings.timeout = 0;
183
+ }
184
+
185
+ xhr.setRequestHeader('X-PJAX', 'true');
186
+ xhr.setRequestHeader('X-PJAX-Container', context.selector);
187
+
188
+ if (!fire('pjax:beforeSend', [xhr, settings]))
189
+ return false;
190
+
191
+
192
+ var duration = settings.duration;
193
+ if (timeoutTimer && duration >= timeout) duration = timeout - 1;
194
+ if ( (duration >= 0) && (duration != null) ) {
195
+ durationTimer = setTimeout(function() {
196
+ durationTimer = null;
197
+ if (complete) {
198
+ if (success) success();
199
+ if (error) error(); // success and error are mutually exclusive
200
+ complete();
201
+ return;
202
+ }
203
+
204
+ // otherwise fire the waiting event
205
+ fire('pjax:waiting', [xhr, options]);
206
+ }, duration);
207
+ }
208
+
209
+ if (options.push && !options.replace) {
210
+ // Cache current container element before replacing it
211
+ cachePush(pjax.state.id, context.clone(true, true).contents());
212
+
213
+ window.history.pushState(null, "", options.url);
214
+ }
215
+
216
+ options.requestUrl = parseURL(settings.url).href;
217
+
218
+ fire('pjax:start', [xhr, options]);
219
+ // start.pjax is deprecated
220
+ fire('start.pjax', [xhr, options]);
221
+
222
+ fire('pjax:send', [xhr, settings]);
223
+
224
+ };
225
+
226
+ options.complete = function(xhr, textStatus){
227
+ complete = function() { _complete.call(options, xhr, textStatus); };
228
+ if (!durationTimer) complete();
229
+ };
230
+
231
+ function _complete(xhr, textStatus) {
232
+ if (timeoutTimer)
233
+ clearTimeout(timeoutTimer);
234
+
235
+ // DEPRECATED: Invoke original `complete` handler
236
+ if (oldComplete) oldComplete.apply(this, arguments);
237
+
238
+ fire('pjax:complete', [xhr, textStatus, options]);
239
+
240
+ fire('pjax:end', [xhr, options]);
241
+ // end.pjax is deprecated
242
+ fire('end.pjax', [xhr, options]);
243
+ }
244
+
245
+ options.error = function(xhr, textStatus, errorThrown) {
246
+ console.log('error');
247
+ var container = extractContainer("", xhr, options);
248
+
249
+ // DEPRECATED: Invoke original `error` handler
250
+ if (oldError) oldError.apply(this, arguments);
251
+
252
+ var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options]);
253
+ if (textStatus !== 'abort' && allowed)
254
+ window.location = container.url;
255
+ };
256
+
257
+ options.success = function(data, status, xhr, options) {
258
+ success = function() { _success.call(options, data, status, xhr) }
259
+ if (!durationTimer) success();
260
+ };
261
+
262
+ function _success(data, status, xhr) {
263
+ var container = extractContainer(data, xhr, options);
264
+
265
+ if (!container.contents) {
266
+ window.location = container.url;
267
+ return;
268
+ }
269
+
270
+ pjax.state = {
271
+ id: options.id || uniqueId(),
272
+ url: container.url,
273
+ title: container.title,
274
+ container: context.selector,
275
+ fragment: options.fragment,
276
+ timeout: options.timeout,
277
+ direction: options.direction,
278
+ duration: options.duration
279
+ };
280
+
281
+ if (options.push || options.replace) {
282
+ window.history.replaceState(pjax.state, container.title, container.url);
283
+ }
284
+
285
+ if (container.title) document.title = container.title;
286
+ context.html(container.contents);
287
+
288
+ // Scroll to top by default
289
+ if (typeof options.scrollTo === 'number' && $.scrollTop)
290
+ $(window).scrollTop(options.scrollTo);
291
+
292
+ // Google Analytics support
293
+ if ( (options.replace || options.push) && window._gaq )
294
+ _gaq.push(['_trackPageview']);
295
+
296
+ // If the URL has a hash in it, make sure the browser
297
+ // knows to navigate to the hash.
298
+ if ( hash !== '' ) {
299
+ // Avoid using simple hash set here. Will add another history
300
+ // entry. Replace the url with replaceState and scroll to target
301
+ // by hand.
302
+ //
303
+ // window.location.hash = hash
304
+ var url = parseURL(container.url);
305
+ url.hash = hash;
306
+
307
+ pjax.state.url = url.href;
308
+ window.history.replaceState(pjax.state, container.title, url.href);
309
+
310
+ var target = $(url.hash);
311
+ if (target.length) $(window).scrollTop(target.offset().top);
312
+ }
313
+
314
+ // DEPRECATED: Invoke original `success` handler
315
+ if (oldSuccess) oldSuccess.apply(this, arguments);
316
+
317
+ fire('pjax:success', [data, status, xhr, options]);
318
+ }
319
+
320
+
321
+ // Initialize pjax.state for the initial page load. Assume we're
322
+ // using the container and options of the link we're loading for the
323
+ // back button to the initial page. This ensures good back button
324
+ // behavior.
325
+ if (!pjax.state) {
326
+ pjax.state = {
327
+ id: uniqueId(),
328
+ url: window.location.href,
329
+ title: document.title,
330
+ container: context.selector,
331
+ fragment: options.fragment,
332
+ timeout: options.timeout,
333
+ direction: options.direction
334
+ };
335
+ window.history.replaceState(pjax.state, document.title);
336
+ }
337
+
338
+ // Cancel the current request if we're already pjaxing
339
+ var xhr = pjax.xhr;
340
+ if ( xhr && xhr.readyState < 4) {
341
+ xhr.onreadystatechange = $.noop;
342
+ xhr.abort();
343
+ }
344
+
345
+ pjax.options = options;
346
+ pjax.xhr = $.ajax(options);
347
+
348
+ // pjax event is deprecated
349
+ $(document).trigger('pjax', [pjax.xhr, options]);
350
+
351
+ /*if (xhr.readyState > 0) {
352
+ // pjax event is deprecated
353
+ console.log('xhr readystate', xhr.readyState);
354
+ $(document).trigger('pjax', [xhr, options]);
355
+
356
+ if (options.push && !options.replace) {
357
+ // Cache current container element before replacing it
358
+ cachePush(pjax.state.id, context.clone().contents());
359
+
360
+ window.history.pushState(null, "", options.url);
361
+ }
362
+
363
+ if(!options.duration) {
364
+ fire('pjax:start', [xhr, options]);
365
+ fire('pjax:send', [xhr, options]);
366
+ }
367
+ }*/
368
+
369
+ return pjax.xhr;
370
+ };
371
+
372
+
373
+ // Internal: Generate unique id for state object.
374
+ //
375
+ // Use a timestamp instead of a counter since ids should still be
376
+ // unique across page loads.
377
+ //
378
+ // Returns Number.
379
+ function uniqueId() {
380
+ return (new Date).getTime();
381
+ }
382
+
383
+ // Internal: Strips _pjax param from url
384
+ //
385
+ // url - String
386
+ //
387
+ // Returns String.
388
+ function stripPjaxParam(url) {
389
+ return url
390
+ .replace(/\?_pjax=[^&]+&?/, '?')
391
+ .replace(/_pjax=[^&]+&?/, '')
392
+ .replace(/[\?&]$/, '');
393
+ }
394
+
395
+ // Internal: Parse URL components and returns a Locationish object.
396
+ //
397
+ // url - String URL
398
+ //
399
+ // Returns HTMLAnchorElement that acts like Location.
400
+ function parseURL(url) {
401
+ var a = document.createElement('a');
402
+ a.href = url;
403
+ return a;
404
+ }
405
+
406
+ // Internal: Build options Object for arguments.
407
+ //
408
+ // For convenience the first parameter can be either the container or
409
+ // the options object.
410
+ //
411
+ // Examples
412
+ //
413
+ // optionsFor('#container')
414
+ // // => {container: '#container'}
415
+ //
416
+ // optionsFor('#container', {push: true})
417
+ // // => {container: '#container', push: true}
418
+ //
419
+ // optionsFor({container: '#container', push: true})
420
+ // // => {container: '#container', push: true}
421
+ //
422
+ // Returns options Object.
423
+ function optionsFor(container, options) {
424
+ // Both container and options
425
+ if ( container && options )
426
+ options.container = container;
427
+
428
+ // First argument is options Object
429
+ else if ( $.isPlainObject(container) )
430
+ options = container;
431
+
432
+ // Only container
433
+ else
434
+ options = {container: container};
435
+
436
+ // Find and validate container
437
+ if (options.container)
438
+ options.container = findContainerFor(options.container);
439
+
440
+ return options;
441
+ }
442
+
443
+ // Internal: Find container element for a variety of inputs.
444
+ //
445
+ // Because we can't persist elements using the history API, we must be
446
+ // able to find a String selector that will consistently find the Element.
447
+ //
448
+ // container - A selector String, jQuery object, or DOM Element.
449
+ //
450
+ // Returns a jQuery object whose context is `document` and has a selector.
451
+ function findContainerFor(container) {
452
+ container = $(container)
453
+
454
+ if ( !container.length ) {
455
+ throw "no pjax container for " + container.selector
456
+ } else if ( container.selector !== '' && container.context === document ) {
457
+ return container
458
+ } else if ( container.attr('id') ) {
459
+ return $('#' + container.attr('id'))
460
+ } else {
461
+ throw "cant get selector for pjax container!"
462
+ }
463
+ }
464
+
465
+ // Internal: Filter and find all elements matching the selector.
466
+ //
467
+ // Where $.fn.find only matches descendants, findAll will test all the
468
+ // top level elements in the jQuery object as well.
469
+ //
470
+ // elems - jQuery object of Elements
471
+ // selector - String selector to match
472
+ //
473
+ // Returns a jQuery object.
474
+ function findAll(elems, selector) {
475
+ var results = $()
476
+ elems.each(function() {
477
+ if ($(this).is(selector))
478
+ results = results.add(this)
479
+ results = results.add(selector, this)
480
+ })
481
+ return results
482
+ }
483
+
484
+ // Internal: Extracts container and metadata from response.
485
+ //
486
+ // 1. Extracts X-PJAX-URL header if set
487
+ // 2. Extracts inline <title> tags
488
+ // 3. Builds response Element and extracts fragment if set
489
+ //
490
+ // data - String response data
491
+ // xhr - XHR response
492
+ // options - pjax options Object
493
+ //
494
+ // Returns an Object with url, title, and contents keys.
495
+ function extractContainer(data, xhr, options) {
496
+ var obj = {};
497
+
498
+ // Prefer X-PJAX-URL header if it was set, otherwise fallback to
499
+ // using the original requested url.
500
+ obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl);
501
+
502
+ // Attempt to parse response html into elements
503
+ var $data = $(data);
504
+
505
+ // If response data is empty, return fast
506
+ if ($data.length === 0)
507
+ return obj;
508
+
509
+ // If there's a <title> tag in the response, use it as
510
+ // the page's title.
511
+ obj.title = findAll($data, 'title').last().text();
512
+
513
+ if (options.fragment) {
514
+ // If they specified a fragment, look for it in the response
515
+ // and pull it out.
516
+ var $fragment = findAll($data, options.fragment).first()
517
+
518
+ if ($fragment.length) {
519
+ obj.contents = $fragment.contents()
520
+
521
+ // If there's no title, look for data-title and title attributes
522
+ // on the fragment
523
+ if (!obj.title)
524
+ obj.title = $fragment.attr('title') || $fragment.data('title')
525
+ }
526
+
527
+ } else if (!/<html/i.test(data)) {
528
+ obj.contents = $data
529
+ }
530
+
531
+ // Clean up any <title> tags
532
+ if (obj.contents) {
533
+ // Remove any parent title elements
534
+ obj.contents = obj.contents.not('title')
535
+
536
+ // Then scrub any titles from their descendents
537
+ obj.contents.find('title').remove()
538
+ }
539
+
540
+ // Trim any whitespace off the title
541
+ if (obj.title) obj.title = obj.title.trim()
542
+
543
+ return obj
544
+ }
545
+
546
+ // Public: Reload current page with pjax.
547
+ //
548
+ // Returns whatever $.pjax returns.
549
+ pjax.reload = function(container, options) {
550
+ console.log('pjax reload');
551
+ var defaults = {
552
+ url: window.location.href,
553
+ push: false,
554
+ replace: true,
555
+ scrollTo: false
556
+ };
557
+
558
+ return $.pjax($.extend(defaults, optionsFor(container, options)));
559
+ };
560
+
561
+
562
+ pjax.defaults = {
563
+ timeout: 650,
564
+ push: true,
565
+ replace: false,
566
+ type: 'GET',
567
+ dataType: 'html',
568
+ scrollTo: 0,
569
+ maxCacheLength: 20
570
+ };
571
+
572
+ // Internal: History DOM caching class.
573
+ var cacheMapping = {};
574
+ var cacheForwardStack = [];
575
+ var cacheBackStack = [];
576
+ // Push previous state id and container contents into the history
577
+ // cache. Should be called in conjunction with `pushState` to save the
578
+ // previous container contents.
579
+ //
580
+ // id - State ID Number
581
+ // value - DOM Element to cache
582
+ //
583
+ // Returns nothing.
584
+ function cachePush(id, value) {
585
+ cacheMapping[id] = value
586
+ cacheBackStack.push(id)
587
+
588
+ // Remove all entires in forward history stack after pushing
589
+ // a new page.
590
+ while (cacheForwardStack.length)
591
+ delete cacheMapping[cacheForwardStack.shift()];
592
+
593
+ // Trim back history stack to max cache length.
594
+ while (cacheBackStack.length > pjax.defaults.maxCacheLength)
595
+ delete cacheMapping[cacheBackStack.shift()];
596
+ }
597
+ // Shifts cache from directional history cache. Should be
598
+ // called on `popstate` with the previous state id and container
599
+ // contents.
600
+ //
601
+ // direction - "forward" or "back" String
602
+ // id - State ID Number
603
+ // value - DOM Element to cache
604
+ //
605
+ // Returns nothing.
606
+ function cachePop(direction, id, value) {
607
+ var pushStack, popStack;
608
+ cacheMapping[id] = value;
609
+
610
+ if (direction === 'forward') {
611
+ pushStack = cacheBackStack;
612
+ popStack = cacheForwardStack;
613
+ } else {
614
+ pushStack = cacheForwardStack;
615
+ popStack = cacheBackStack;
616
+ }
617
+
618
+ pushStack.push(id);
619
+ if (id = popStack.pop())
620
+ delete cacheMapping[id];
621
+ }
622
+
623
+
624
+ // Export $.pjax.click
625
+ pjax.click = handleClick;
626
+
627
+
628
+ // popstate handler takes care of the back and forward buttons
629
+ //
630
+ // You probably shouldn't use pjax on pages with other pushState
631
+ // stuff yet.
632
+ $(window).bind('popstate', function(event){
633
+ console.log('popstate args ', arguments);
634
+ var state = event.state;
635
+
636
+ if (state && state.container) {
637
+ var container = $(state.container);
638
+ if (container.length) {
639
+ var contents = cacheMapping[state.id];
640
+
641
+ if (pjax.state) {
642
+ // Since state ids always increase, we can deduce the history
643
+ // direction from the previous state.
644
+ var direction = pjax.options.direction = pjax.state.id < state.id ? 'forward' : 'back';
645
+
646
+ // Cache current container before replacement and inform the
647
+ // cache which direction the history shifted.
648
+ cachePop(direction, pjax.state.id, container.clone().contents());
649
+ }
650
+
651
+ var popstateEvent = $.Event('pjax:popstate', {
652
+ state: state,
653
+ direction: direction
654
+ });
655
+ container.trigger(popstateEvent);
656
+
657
+ var options = {
658
+ id: state.id,
659
+ url: state.url,
660
+ container: container,
661
+ push: false,
662
+ fragment: state.fragment,
663
+ timeout: state.timeout,
664
+ scrollTo: false,
665
+ direction: direction,
666
+ duration: state.duration
667
+ };
668
+
669
+ if (contents) {
670
+ console.log(state);
671
+ // pjax event is deprecated
672
+ $(document).trigger('pjax', [null, options]);
673
+ container.trigger('pjax:start', [null, options]);
674
+
675
+ setTimeout(function() {
676
+ if (state.title) document.title = state.title;
677
+ container.html(contents);
678
+ pjax.state = state;
679
+
680
+ container.trigger('pjax:end', [null, options]);
681
+ container[0].offsetHeight;
682
+ }, state.duration || 0);
683
+ } else {
684
+ $.pjax(options);
685
+ }
686
+
687
+ // Force reflow/relayout before the browser tries to restore the
688
+ // scroll position.
689
+ container[0].offsetHeight;
690
+ } else {
691
+ window.location = location.href;
692
+ }
693
+ }
694
+ });
695
+
696
+
697
+ // Is pjax supported by this browser?
698
+ $.support = {};
699
+ $.support.pjax =
700
+ window.history && window.history.pushState && window.history.replaceState
701
+ // pushState isn't reliable on iOS until 5.
702
+ && !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/);
703
+
704
+ // Fall back to normalcy for older browsers.
705
+ if ( !$.support.pjax ) {
706
+ $.pjax = function( options ) {
707
+ var url = $.isFunction(options.url) ? options.url() : options.url,
708
+ method = options.type ? options.type.toUpperCase() : 'GET';
709
+
710
+ var form = $('<form>', {
711
+ method: method === 'GET' ? 'GET' : 'POST',
712
+ action: url,
713
+ style: 'display:none'
714
+ });
715
+
716
+ if (method !== 'GET' && method !== 'POST') {
717
+ form.append($('<input>', {
718
+ type: 'hidden',
719
+ name: '_method',
720
+ value: method.toLowerCase()
721
+ }));
722
+ }
723
+
724
+ var data = options.data;
725
+ if (typeof data === 'string') {
726
+ $.each(data.split('&'), function(index, value) {
727
+ var pair = value.split('=');
728
+ form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}));
729
+ });
730
+ } else if (typeof data === 'object') {
731
+ for (key in data)
732
+ form.append($('<input>', {type: 'hidden', name: key, value: data[key]}));
733
+ }
734
+
735
+ $(document.body).append(form);
736
+ form.submit();
737
+ };
738
+
739
+ $.pjax.click = $.noop;
740
+ $.pjax.reload = window.location.reload;
741
+ $.fn.pjax = function() { return this; }
742
+ }
743
+
744
+ })(Zepto || jQuery);