From 80468f41b9849d6137fe6a4e9505df8f3fa65aa7 Mon Sep 17 00:00:00 2001 From: Scott Watermasysk Date: Mon, 23 May 2011 17:40:43 -0400 Subject: [PATCH] enabling pjax --- ameba.rb | 34 ++++--- lib/models.rb | 2 +- public/js/jquery.pjax.js | 213 +++++++++++++++++++++++++++++++++++++++ views/404.slim | 1 + views/about.slim | 8 ++ views/about.textile | 9 -- views/archive.slim | 1 + views/editor.slim | 2 +- views/index.slim | 2 +- views/layout.slim | 5 + views/post.slim | 1 + views/queue.slim | 1 + 12 files changed, 251 insertions(+), 28 deletions(-) create mode 100644 public/js/jquery.pjax.js create mode 100644 views/about.slim diff --git a/ameba.rb b/ameba.rb index 1d2b0ff..d94c1a0 100644 --- a/ameba.rb +++ b/ameba.rb @@ -69,7 +69,7 @@ get '/login' do set_title = 'Log In' @user = User.new - slim :login + v :login end post '/login' do @@ -79,7 +79,7 @@ redirect redirect_to else flash[:notice] = "Invalid email/password combination" - slim :login + v :login end end @@ -92,61 +92,63 @@ end get '/about' do - set_title "About" - textile :about, :layout_engine => :slim + v :about end get '/archive' do - set_title "Archive" @posts = Post.archive @robots = "noindex,follow" - slim :archive + v :archive end get '/queue' do login_required! - set_title "Queue" @posts = Post.queued @robots = "noindex,nofollow" - slim :queue + v :queue end get %r{/(.+)} do |slug| @post = Post.find_by_slug(slug) if @post @post.increment_views unless logged_in? - set_title @post.title - slim :post + v :post else 404 end end get '/' do - set_title @posts = Post.recent(10, params[:page] || 1) - slim :index + v :index end not_found do - set_title "You broke my site!" @posts = Post.popular - slim :'404' + v :'404' end def set_title (text=nil) @title = text ? "#{text} : #{@site.title}" : @site.title + pjax? ? "#{@title}" : '' end private def editor_form(post) @post = post - set_title post.title.present? ? post.title : 'Write Something' - slim :editor + v :editor end def date_from_form(date) date = 'now' if date.blank? Chronic.parse(date).ago(@site.timezone_offset.hours).utc end + +def pjax? + env['HTTP_X_PJAX'] +end + +def v(name) + slim name, :layout => !pjax? +end diff --git a/lib/models.rb b/lib/models.rb index 4d681b3..3e6ecdb 100644 --- a/lib/models.rb +++ b/lib/models.rb @@ -168,7 +168,7 @@ def set_standard_values(&blog_validation) # This is not ideal, but it makes it easier to read code in the editor # and formats it much better for end users def clean_up_pre_code_blocks(text) - r = Regexp.new('
\r?\n')
+    r = Regexp.new('
\r?\n')
     text.gsub(r, '
')
   end
   
diff --git a/public/js/jquery.pjax.js b/public/js/jquery.pjax.js
new file mode 100644
index 0000000..0095de4
--- /dev/null
+++ b/public/js/jquery.pjax.js
@@ -0,0 +1,213 @@
+// jquery.pjax.js
+// copyright chris wanstrath
+// https://github.com/defunkt/pjax
+
+(function($){
+
+// When called on a link, fetches the href with ajax into the
+// container specified as the first parameter or with the data-pjax
+// attribute on the link itself.
+//
+// Tries to make sure the back button and ctrl+click work the way
+// you'd expect.
+//
+// Accepts a jQuery ajax options object that may include these
+// pjax specific options:
+//
+// container - Where to stick the response body. Usually a String selector.
+//             $(container).html(xhr.responseBody)
+//      push - Whether to pushState the URL. Defaults to true (of course).
+//   replace - Want to use replaceState instead? That's cool.
+//
+// For convenience the first parameter can be either the container or
+// the options object.
+//
+// Returns the jQuery object
+$.fn.pjax = function( container, options ) {
+  if ( options )
+    options.container = container
+  else
+    options = $.isPlainObject(container) ? container : {container:container}
+
+  return this.live('click', function(event){
+    // Middle click, cmd click, and ctrl click should open
+    // links in a new tab as normal.
+    if ( event.which > 1 || event.metaKey )
+      return true
+
+    var defaults = {
+      url: this.href,
+      container: $(this).attr('data-pjax')
+    }
+
+    $.pjax($.extend({}, defaults, options))
+
+    event.preventDefault()
+  })
+}
+
+
+// Loads a URL with ajax, puts the response body inside a container,
+// then pushState()'s the loaded URL.
+//
+// Works just like $.ajax in that it accepts a jQuery ajax
+// settings object (with keys like url, type, data, etc).
+//
+// Accepts these extra keys:
+//
+// container - Where to stick the response body.
+//             $(container).html(xhr.responseBody)
+//      push - Whether to pushState the URL. Defaults to true (of course).
+//   replace - Want to use replaceState instead? That's cool.
+//
+// Use it just like $.ajax:
+//
+//   var xhr = $.pjax({ url: this.href, container: '#main' })
+//   console.log( xhr.readyState )
+//
+// Returns whatever $.ajax returns.
+$.pjax = function( options ) {
+  var $container = $(options.container),
+      success = options.success || $.noop
+
+  // We don't want to let anyone override our success handler.
+  delete options.success
+
+  var defaults = {
+    timeout: 650,
+    push: true,
+    replace: false,
+    // We want the browser to maintain two separate internal caches: one for
+    // pjax'd partial page loads and one for normal page loads. Without
+    // adding this secret parameter, some browsers will often confuse the two.
+    data: { _pjax: true },
+    type: 'GET',
+    dataType: 'html',
+    beforeSend: function(xhr){
+      $container.trigger('start.pjax')
+      xhr.setRequestHeader('X-PJAX', 'true')
+    },
+    error: function(){
+      window.location = options.url
+    },
+    complete: function(){
+      $container.trigger('end.pjax')
+    },
+    success: function(data){
+      // If we got no data or an entire web page, go directly
+      // to the page and let normal error handling happen.
+      if ( !$.trim(data) || / tag in the response, use it as
+      // the page's title.
+      var oldTitle = document.title,
+          title = $.trim( $container.find('title').remove().text() )
+      if ( title ) document.title = title
+
+      var state = {
+        pjax: options.container,
+        timeout: options.timeout
+      }
+
+      // We can't persist $objects using the history API so we need to store
+      // the string selector.
+      if ( $.isPlainObject(state.pjax) )
+        state.pjax = state.pjax.selector
+
+      // If there are extra params, save the complete URL in the state object
+      var query = $.param(options.data)
+      if ( query != "_pjax=true" )
+        state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query
+
+      if ( options.replace ) {
+        window.history.replaceState(state, document.title, options.url)
+      } else if ( options.push ) {
+        // this extra replaceState before first push ensures good back
+        // button behavior
+        if ( !$.pjax.active ) {
+          window.history.replaceState($.extend({}, state, {url:null}), oldTitle)
+          $.pjax.active = true
+        }
+
+        window.history.pushState(state, document.title, options.url)
+      }
+
+      // Google Analytics support
+      if ( (options.replace || options.push) && window._gaq )
+        _gaq.push(['_trackPageview'])
+
+      // Invoke their success handler if they gave us one.
+      success.apply(this, arguments)
+    }
+  }
+
+  options = $.extend(true, {}, defaults, options)
+
+  if ( $.isFunction(options.url) ) {
+    options.url = options.url()
+  }
+
+  // Cancel the current request if we're already pjaxing
+  var xhr = $.pjax.xhr
+  if ( xhr && xhr.readyState < 4) {
+    xhr.onreadystatechange = $.noop
+    xhr.abort()
+  }
+
+  $.pjax.xhr = $.ajax(options)
+  $(document).trigger('pjax', $.pjax.xhr, options)
+
+  return $.pjax.xhr
+}
+
+
+// Used to detect initial (useless) popstate.
+// If history.state exists, assume browser isn't going to fire initial popstate.
+var popped = ('state' in window.history), initialURL = location.href
+
+
+// popstate handler takes care of the back and forward buttons
+//
+// You probably shouldn't use pjax on pages with other pushState
+// stuff yet.
+$(window).bind('popstate', function(event) {
+  // Ignore inital popstate that some browsers fire on page load
+  var initialPop = !popped && location.href == initialURL
+  popped = true
+  if ( initialPop ) return
+
+  var state = event.state
+
+  if ( state && state.pjax ) {
+    var $container = $(state.pjax+'')
+    if ( $container.length )
+      $.pjax({
+        url: state.url || location.href,
+        container: $container,
+        push: false,
+        timeout: state.timeout
+      })
+    else
+      window.location = location.href
+  }
+})
+
+
+// Add the state property to jQuery's event object so we can use it in
+// $(window).bind('popstate')
+if ( $.event.props.indexOf('state') < 0 )
+  $.event.props.push('state')
+
+
+// Fall back to normalcy for older browsers.
+if ( !window.history || !window.history.pushState ) {
+  $.pjax = $.noop
+  $.fn.pjax = function() { return this }
+}
+
+
+})(jQuery);
diff --git a/views/404.slim b/views/404.slim
index dbc24b5..aef825e 100644
--- a/views/404.slim
+++ b/views/404.slim
@@ -1,3 +1,4 @@
+== set_title "You broke my site!"
 article
   header
     h1 OMG! You broke my site!
diff --git a/views/about.slim b/views/about.slim
new file mode 100644
index 0000000..b5adb2a
--- /dev/null
+++ b/views/about.slim
@@ -0,0 +1,8 @@
+== set_title 'About'
+article
+  header
+    h1 About Scott Watermasysk
+
+  section.content
+    == textile :about
+
diff --git a/views/about.textile b/views/about.textile
index ea0efd9..e47a506 100644
--- a/views/about.textile
+++ b/views/about.textile
@@ -1,9 +1,3 @@
-
-
-h1. About Scott Watermasysk -
-
- Hello! Thanks for stopping by. This is the personal site of Scott Watermasysk. @@ -30,6 +24,3 @@ Want more? Have a question? # "Picture poster":http://crinket.com Want this site's code? See the "Ameba repository":http://github.com/scottwater/ameba - -
-
diff --git a/views/archive.slim b/views/archive.slim index 0c76f77..ae1c5eb 100644 --- a/views/archive.slim +++ b/views/archive.slim @@ -1,3 +1,4 @@ +== set_title "Archive" article header h1 Archive diff --git a/views/editor.slim b/views/editor.slim index 582fa8c..b13f238 100644 --- a/views/editor.slim +++ b/views/editor.slim @@ -1,3 +1,4 @@ +== set_title @post.title.present? ? @post.title : 'Write Something' article header h1 Feel inspired & write something @@ -27,7 +28,6 @@ form action=editor_action_link(@post) method="post" id="post" fieldset input.submit type="submit" id="post-button" value=editor_button_text(@post) tab="2" -script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" script type="text/javascript" src="http://cachedcommons.org/cache/jquery-elastic-textarea/1.6.4/javascripts/jquery-elastic-textarea-min.js" script type="text/javascript" src="/js/incrementable.js" diff --git a/views/index.slim b/views/index.slim index 773664a..205139f 100644 --- a/views/index.slim +++ b/views/index.slim @@ -1,4 +1,4 @@ - +== set_title - @posts.each do |post| article header diff --git a/views/layout.slim b/views/layout.slim index 6c3eda9..8c1a966 100644 --- a/views/layout.slim +++ b/views/layout.slim @@ -14,9 +14,14 @@ html lang="en" link rel="stylesheet" href="/css/app.css" media="screen, projection" link href=url_for_feed(@site.feed) type="application/atom+xml" rel="alternate" title=@site.title link rel="shortcut icon" href="/favicon.ico" + script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js" + script type="text/javascript" src="/js/jquery.pjax.js" script type="text/javascript" src="http://use.typekit.com/qqx1qcd.js" javascript: try{Typekit.load();}catch(e){} + $(document).ready(function() { + $('a').pjax('#content'); + }); |