diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 0ebae79..e06302a 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -1,12 +1,15 @@
class PostsController < ApplicationController
def index
- @posts = Post.latest
+ @posts = Post.scoped
+ @posts = @posts.latest.limit(20).includes(:tags)
+ @posts = @posts.offset(params[:offset].to_i) if params[:offset]
+ render :partial => "listed_post", :collection => @posts.all if params[:list]
end
def show
@post = Post.find_by_id(params[:id])
- @posts = Post.latest - [@post]
+ @posts = Post.latest.limit(20).includes(:tags) - [@post]
end
end
diff --git a/app/views/admin/posts/_form.html.haml b/app/views/admin/posts/_form.html.haml
index ce07a22..6806672 100644
--- a/app/views/admin/posts/_form.html.haml
+++ b/app/views/admin/posts/_form.html.haml
@@ -1,18 +1,17 @@
+- stylesheet "posts"
+- javascript "elastic"
+
= form_for [:admin, @post] do |f|
= f.label :title
- %br
= f.text_field :title
- %br
= f.label :tag_list
- %br
= f.text_field :tag_list
- %br
= f.label :body
- %br
= f.text_area :body
- %br
= f.label :published_on
- %br
= f.date_select :published_on
%br
- = f.submit "Save", :class => "button"
\ No newline at end of file
+ = f.submit "Save", :class => "button medium"
+
+:javascript
+ $('textarea#post_body').elastic();
\ No newline at end of file
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index c627d0a..a6471bc 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -13,7 +13,7 @@
#header_container
#header
= image_tag "fade.png", :id => "fade"
- = image_tag "logo.png", :id => "logo"
+ = link_to image_tag("logo.png", :id => "logo"), root_path
#container
#menu= yield :menu
#content= yield
diff --git a/app/views/posts/_listed_post.html.haml b/app/views/posts/_listed_post.html.haml
index b6335b1..1afcdb3 100644
--- a/app/views/posts/_listed_post.html.haml
+++ b/app/views/posts/_listed_post.html.haml
@@ -8,10 +8,8 @@
%h1= link_to listed_post.title, listed_post, :title => listed_post.title
%span.detailed_info
by
- %span.focus Martin
- - unless listed_post.tags.empty?
- and tagged with
- = tag_sentence(listed_post)
+ %span.focus= listed_post.user.full_name
+ = raw "(" + link_to("Comments", post_path(listed_post, :anchor => "disqus_thread")) + ")"
.fadeout
.text
%p= truncate(strip_tags(textilize(listed_post.body)), :length => 150)
\ No newline at end of file
diff --git a/app/views/posts/_post.html.haml b/app/views/posts/_post.html.haml
index 03c8ff0..1f30c03 100644
--- a/app/views/posts/_post.html.haml
+++ b/app/views/posts/_post.html.haml
@@ -1,12 +1,13 @@
= stylesheet "coderay"
- content_for(:menu) do
- %p
- = link_to "Write a new post", [:new, :admin, :post], :class => "button in_menu"
- %br
- = link_to "Edit this one", [:edit, :admin, post], :class => "button in_menu"
- %br
- = link_to "Delete this one", [:admin, post], :method => :delete, :confirm => "Are you sure?", :class => "button red in_menu"
+ - if current_user && current_user.admin
+ %p
+ = link_to "Write a new post", [:new, :admin, :post], :class => "button in_menu"
+ %br
+ = link_to "Edit this one", [:edit, :admin, post], :class => "button in_menu"
+ %br
+ = link_to "Delete this one", [:admin, post], :method => :delete, :confirm => "Are you sure?", :class => "button red in_menu"
#title
= image_tag "http://www.gravatar.com/avatar/eb8c26db448303abe1d2f5df15c2a0a5?s=70", :class => "profile_image"
diff --git a/app/views/posts/_posts_list.html.haml b/app/views/posts/_posts_list.html.haml
index 5af206a..c28b7e0 100644
--- a/app/views/posts/_posts_list.html.haml
+++ b/app/views/posts/_posts_list.html.haml
@@ -1,4 +1,5 @@
- stylesheet "posts"
+- javascript "mousewheel", "jScrollPane"
- unless @posts.empty?
- javascript "posts"
@@ -13,4 +14,7 @@
%button#patrik.button By Patrik
%ul#posts
- = render :partial => "listed_post", :collection => @posts
\ No newline at end of file
+ = render :partial => "listed_post", :collection => @posts
+ %li#loading_post
+ %h2 Loading more posts...
+ = image_tag "/images/loader.gif"
\ No newline at end of file
diff --git a/app/views/posts/index.html.haml b/app/views/posts/index.html.haml
index a3d58de..f621954 100644
--- a/app/views/posts/index.html.haml
+++ b/app/views/posts/index.html.haml
@@ -1 +1,7 @@
-= render "posts_list"
\ No newline at end of file
+- content_for(:menu) do
+ %p
+ = link_to "Write a new post", [:new, :admin, :post], :class => "button in_menu"
+
+
+- content_for(:bottom) do
+ = render "posts_list"
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index f878db0..5db18c4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -54,7 +54,7 @@
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
- # root :to => "welcome#index"
+ root :to => "posts#index"
# See how all your routes lay out with "rake routes"
diff --git a/public/images/loader.gif b/public/images/loader.gif
new file mode 100644
index 0000000..09d621e
Binary files /dev/null and b/public/images/loader.gif differ
diff --git a/public/images/scroll_background.png b/public/images/scroll_background.png
new file mode 100644
index 0000000..1ad2e13
Binary files /dev/null and b/public/images/scroll_background.png differ
diff --git a/public/javascripts/elastic.js b/public/javascripts/elastic.js
new file mode 100644
index 0000000..d4ba339
--- /dev/null
+++ b/public/javascripts/elastic.js
@@ -0,0 +1,117 @@
+/**
+* @name Elastic
+* @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
+* @version 1.6.4
+* @requires Jquery 1.2.6+
+*
+* @author Jan Jarfalk
+* @author-email jan.jarfalk@unwrongest.com
+* @author-website http://www.unwrongest.com
+*
+* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
+*/
+
+(function(jQuery){
+ jQuery.fn.extend({
+ elastic: function() {
+
+ // We will create a div clone of the textarea
+ // by copying these attributes from the textarea to the div.
+ var mimics = [
+ 'paddingTop',
+ 'paddingRight',
+ 'paddingBottom',
+ 'paddingLeft',
+ 'fontSize',
+ 'lineHeight',
+ 'fontFamily',
+ 'width',
+ 'fontWeight'];
+
+ return this.each( function() {
+
+ // Elastic only works on textareas
+ if ( this.type != 'textarea' ) {
+ return false;
+ }
+
+ var $textarea = jQuery(this),
+ $twin = jQuery('
').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
+ lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
+ minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
+ maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
+ goalheight = 0,
+ i = 0;
+
+ // Opera returns max-height of -1 if not set
+ if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
+
+ // Append the twin to the DOM
+ // We are going to meassure the height of this, not the textarea.
+ $twin.appendTo($textarea.parent());
+
+ // Copy the essential styles (mimics) from the textarea to the twin
+ var i = mimics.length;
+ while(i--){
+ $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
+ }
+
+
+ // Sets a given height and overflow state on the textarea
+ function setHeightAndOverflow(height, overflow){
+ curratedHeight = Math.floor(parseInt(height,10));
+ if($textarea.height() != curratedHeight){
+ $textarea.css({'height': curratedHeight + 'px','overflow':overflow});
+
+ }
+ }
+
+
+ // This function will update the height of the textarea if necessary
+ function update() {
+
+ // Get curated content from the textarea.
+ var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '
');
+
+ var twinContent = $twin.html();
+
+ if(textareaContent+' ' != twinContent){
+
+ // Add an extra white space so new rows are added when you are at the end of a row.
+ $twin.html(textareaContent+' ');
+
+ // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
+ if(Math.abs($twin.height()+lineHeight - $textarea.height()) > 3){
+
+ var goalheight = $twin.height()+lineHeight;
+ if(goalheight >= maxheight) {
+ setHeightAndOverflow(maxheight,'auto');
+ } else if(goalheight <= minheight) {
+ setHeightAndOverflow(minheight,'hidden');
+ } else {
+ setHeightAndOverflow(goalheight,'hidden');
+ }
+
+ }
+
+ }
+
+ }
+
+ // Hide scrollbars
+ $textarea.css({'overflow':'hidden'});
+
+ // Update textarea size on keyup
+ $textarea.keyup(function(){ update(); });
+
+ // And this line is to catch the browser paste event
+ $textarea.live('input paste',function(e){ setTimeout( update, 250); });
+
+ // Run update once when elastic is initialized
+ update();
+
+ });
+
+ }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/public/javascripts/jScrollPane.js b/public/javascripts/jScrollPane.js
new file mode 100644
index 0000000..fded612
--- /dev/null
+++ b/public/javascripts/jScrollPane.js
@@ -0,0 +1,702 @@
+/* Copyright (c) 2009 Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * See http://kelvinluck.com/assets/jquery/jScrollPane/
+ * $Id: jScrollPane.js 90 2010-01-25 03:52:10Z kelvin.luck $
+ */
+
+/**
+ * Replace the vertical scroll bars on any matched elements with a fancy
+ * styleable (via CSS) version. With JS disabled the elements will
+ * gracefully degrade to the browsers own implementation of overflow:auto.
+ * If the mousewheel plugin has been included on the page then the scrollable areas will also
+ * respond to the mouse wheel.
+ *
+ * @example jQuery(".scroll-pane").jScrollPane();
+ *
+ * @name jScrollPane
+ * @type jQuery
+ * @param Object settings hash with options, described below.
+ * scrollbarWidth - The width of the generated scrollbar in pixels
+ * scrollbarMargin - The amount of space to leave on the side of the scrollbar in pixels
+ * wheelSpeed - The speed the pane will scroll in response to the mouse wheel in pixels
+ * showArrows - Whether to display arrows for the user to scroll with
+ * arrowSize - The height of the arrow buttons if showArrows=true
+ * animateTo - Whether to animate when calling scrollTo and scrollBy
+ * dragMinHeight - The minimum height to allow the drag bar to be
+ * dragMaxHeight - The maximum height to allow the drag bar to be
+ * animateInterval - The interval in milliseconds to update an animating scrollPane (default 100)
+ * animateStep - The amount to divide the remaining scroll distance by when animating (default 3)
+ * maintainPosition- Whether you want the contents of the scroll pane to maintain it's position when you re-initialise it - so it doesn't scroll as you add more content (default true)
+ * tabIndex - The tabindex for this jScrollPane to control when it is tabbed to when navigating via keyboard (default 0)
+ * enableKeyboardNavigation - Whether to allow keyboard scrolling of this jScrollPane when it is focused (default true)
+ * animateToInternalLinks - Whether the move to an internal link (e.g. when it's focused by tabbing or by a hash change in the URL) should be animated or instant (default false)
+ * scrollbarOnLeft - Display the scrollbar on the left side? (needs stylesheet changes, see examples.html)
+ * reinitialiseOnImageLoad - Whether the jScrollPane should automatically re-initialise itself when any contained images are loaded (default false)
+ * topCapHeight - The height of the "cap" area between the top of the jScrollPane and the top of the track/ buttons
+ * bottomCapHeight - The height of the "cap" area between the bottom of the jScrollPane and the bottom of the track/ buttons
+ * observeHash - Whether jScrollPane should attempt to automagically scroll to the correct place when an anchor inside the scrollpane is linked to (default true)
+ * @return jQuery
+ * @cat Plugins/jScrollPane
+ * @author Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
+ */
+
+(function($) {
+
+var allow_load_posts = true;
+var loading_posts = false;
+function loadPosts() {
+ if(loading_posts || allow_load_posts == false) {
+ loading_posts = false;
+ return;
+ }
+ else {
+ $("#loading_post").show();
+ loading_posts = true;
+ }
+
+ var currently_loaded_posts = $("li.post").size()
+ $.get("/posts", "list=true&offset=" + currently_loaded_posts, function(data) {
+ $("#loading_post").hide();
+ if(data == " ")
+ allow_load_posts = false;
+ else {
+ $("#loading_post").before(data);
+ $("#posts").jScrollPane({reload: true});
+ }
+ loading_posts = false;
+ });
+}
+
+$.jScrollPane = {
+ active : []
+};
+$.fn.jScrollPane = function(settings)
+{
+ settings = $.extend({}, $.fn.jScrollPane.defaults, settings);
+
+ var rf = function() { return false; };
+
+ return this.each(
+ function()
+ {
+ var $this = $(this);
+ var paneEle = this;
+ var currentScrollPosition = 0;
+ var lastPosition = 0;
+ var paneWidth;
+ var paneHeight;
+ var trackHeight;
+ var trackOffset = settings.topCapHeight;
+ var $container;
+
+ if(settings.reload)
+ $this.parent().unbind('mousewheel').unbind('mousedown.jScrollPane').unbind('keydown.jscrollpane').unbind('keyup.jscrollpane');
+
+
+ if ($(this).parent().is('.jScrollPaneContainer')) {
+ $container = $(this).parent();
+ currentScrollPosition = settings.maintainPosition ? $this.position().top : 0;
+ var $c = $(this).parent();
+ paneWidth = $c.innerWidth();
+ paneHeight = $c.outerHeight();
+ $('>.jScrollPaneTrack, >.jScrollArrowUp, >.jScrollArrowDown, >.jScrollCap', $c).remove();
+ $this.css({'top':0});
+ } else {
+ $this.data('originalStyleTag', $this.attr('style'));
+ // Switch the element's overflow to hidden to ensure we get the size of the element without the scrollbars [http://plugins.jquery.com/node/1208]
+ $this.css('overflow', 'hidden');
+ this.originalPadding = $this.css('paddingTop') + ' ' + $this.css('paddingRight') + ' ' + $this.css('paddingBottom') + ' ' + $this.css('paddingLeft');
+ this.originalSidePaddingTotal = (parseInt($this.css('paddingLeft')) || 0) + (parseInt($this.css('paddingRight')) || 0);
+ paneWidth = $this.innerWidth();
+ paneHeight = $this.innerHeight();
+ $container = $('')
+ .attr({'className':'jScrollPaneContainer'})
+ .css(
+ {
+ 'height':paneHeight+'px',
+ 'width':paneWidth+'px'
+ }
+ );
+ if (settings.enableKeyboardNavigation) {
+ $container.attr(
+ 'tabindex',
+ settings.tabIndex
+ );
+ }
+ $this.wrap($container);
+ $container = $this.parent();
+ // deal with text size changes (if the jquery.em plugin is included)
+ // and re-initialise the scrollPane so the track maintains the
+ // correct size
+ $(document).bind(
+ 'emchange',
+ function(e, cur, prev)
+ {
+ $this.jScrollPane(settings);
+ }
+ );
+
+ }
+ trackHeight = paneHeight;
+
+ if (settings.reinitialiseOnImageLoad) {
+ // code inspired by jquery.onImagesLoad: http://plugins.jquery.com/project/onImagesLoad
+ // except we re-initialise the scroll pane when each image loads so that the scroll pane is always up to size...
+ // TODO: Do I even need to store it in $.data? Is a local variable here the same since I don't pass the reinitialiseOnImageLoad when I re-initialise?
+ var $imagesToLoad = $.data(paneEle, 'jScrollPaneImagesToLoad') || $('img', $this);
+ var loadedImages = [];
+
+ if ($imagesToLoad.length) {
+ $imagesToLoad.each(function(i, val) {
+ $(this).bind('load readystatechange', function() {
+ if($.inArray(i, loadedImages) == -1){ //don't double count images
+ loadedImages.push(val); //keep a record of images we've seen
+ $imagesToLoad = $.grep($imagesToLoad, function(n, i) {
+ return n != val;
+ });
+ $.data(paneEle, 'jScrollPaneImagesToLoad', $imagesToLoad);
+ var s2 = $.extend(settings, {reinitialiseOnImageLoad:false});
+ $this.jScrollPane(s2); // re-initialise
+ }
+ }).each(function(i, val) {
+ if(this.complete || this.complete===undefined) {
+ //needed for potential cached images
+ this.src = this.src;
+ }
+ });
+ });
+ };
+ }
+
+ var p = this.originalSidePaddingTotal;
+ var realPaneWidth = paneWidth - settings.scrollbarWidth - settings.scrollbarMargin - p;
+
+ var cssToApply = {
+ 'height':'auto',
+ 'width': realPaneWidth + 'px'
+ }
+
+ if(settings.scrollbarOnLeft) {
+ cssToApply.paddingLeft = settings.scrollbarMargin + settings.scrollbarWidth + 'px';
+ } else {
+ cssToApply.paddingRight = settings.scrollbarMargin + 'px';
+ }
+
+ $this.css(cssToApply);
+
+ var contentHeight = $this.outerHeight();
+ var percentInView = paneHeight / contentHeight;
+
+ var isScrollable = percentInView < .99;
+ $container[isScrollable ? 'addClass' : 'removeClass']('jScrollPaneScrollable');
+
+ if (isScrollable) {
+ $container.append(
+ $('').addClass('jScrollCap jScrollCapTop').css({height:settings.topCapHeight}),
+ $('').attr({'className':'jScrollPaneTrack'}).css({'width':settings.scrollbarWidth+'px'}).append(
+ $('').attr({'className':'jScrollPaneDrag'}).css({'width':settings.scrollbarWidth+'px'}).append(
+ $('').attr({'className':'jScrollPaneDragTop'}).css({'width':settings.scrollbarWidth+'px'}),
+ $('').attr({'className':'jScrollPaneDragBottom'}).css({'width':settings.scrollbarWidth+'px'})
+ )
+ ),
+ $('').addClass('jScrollCap jScrollCapBottom').css({height:settings.bottomCapHeight})
+ );
+
+ var $track = $('>.jScrollPaneTrack', $container);
+ var $drag = $('>.jScrollPaneTrack .jScrollPaneDrag', $container);
+
+
+ var currentArrowDirection;
+ var currentArrowTimerArr = [];// Array is used to store timers since they can stack up when dealing with keyboard events. This ensures all timers are cleaned up in the end, preventing an acceleration bug.
+ var currentArrowInc;
+ var whileArrowButtonDown = function()
+ {
+ if (currentArrowInc > 4 || currentArrowInc % 4 == 0) {
+ positionDrag(dragPosition + currentArrowDirection * mouseWheelMultiplier);
+ }
+ currentArrowInc++;
+ };
+
+ if (settings.enableKeyboardNavigation) {
+ $container.bind(
+ 'keydown.jscrollpane',
+ function(e)
+ {
+ switch (e.keyCode) {
+ case 38: //up
+ currentArrowDirection = -1;
+ currentArrowInc = 0;
+ whileArrowButtonDown();
+ currentArrowTimerArr[currentArrowTimerArr.length] = setInterval(whileArrowButtonDown, 100);
+ return false;
+ case 40: //down
+ currentArrowDirection = 1;
+ currentArrowInc = 0;
+ whileArrowButtonDown();
+ currentArrowTimerArr[currentArrowTimerArr.length] = setInterval(whileArrowButtonDown, 100);
+ return false;
+ case 33: // page up
+ case 34: // page down
+ // TODO
+ return false;
+ default:
+ }
+ }
+ ).bind(
+ 'keyup.jscrollpane',
+ function(e)
+ {
+ if (e.keyCode == 38 || e.keyCode == 40) {
+ for (var i = 0; i < currentArrowTimerArr.length; i++) {
+ clearInterval(currentArrowTimerArr[i]);
+ }
+ return false;
+ }
+ }
+ );
+ }
+
+ if (settings.showArrows) {
+
+ var currentArrowButton;
+ var currentArrowInterval;
+
+ var onArrowMouseUp = function(event)
+ {
+ $('html').unbind('mouseup', onArrowMouseUp);
+ currentArrowButton.removeClass('jScrollActiveArrowButton');
+ clearInterval(currentArrowInterval);
+ };
+ var onArrowMouseDown = function() {
+ $('html').bind('mouseup', onArrowMouseUp);
+ currentArrowButton.addClass('jScrollActiveArrowButton');
+ currentArrowInc = 0;
+ whileArrowButtonDown();
+ currentArrowInterval = setInterval(whileArrowButtonDown, 100);
+ };
+ $container
+ .append(
+ $('')
+ .attr(
+ {
+ 'href':'javascript:;',
+ 'className':'jScrollArrowUp',
+ 'tabindex':-1
+ }
+ )
+ .css(
+ {
+ 'width':settings.scrollbarWidth+'px',
+ 'top':settings.topCapHeight + 'px'
+ }
+ )
+ .html('Scroll up')
+ .bind('mousedown', function()
+ {
+ currentArrowButton = $(this);
+ currentArrowDirection = -1;
+ onArrowMouseDown();
+ this.blur();
+ return false;
+ })
+ .bind('click', rf),
+ $('')
+ .attr(
+ {
+ 'href':'javascript:;',
+ 'className':'jScrollArrowDown',
+ 'tabindex':-1
+ }
+ )
+ .css(
+ {
+ 'width':settings.scrollbarWidth+'px',
+ 'bottom':settings.bottomCapHeight + 'px'
+ }
+ )
+ .html('Scroll down')
+ .bind('mousedown', function()
+ {
+ currentArrowButton = $(this);
+ currentArrowDirection = 1;
+ onArrowMouseDown();
+ this.blur();
+ return false;
+ })
+ .bind('click', rf)
+ );
+ var $upArrow = $('>.jScrollArrowUp', $container);
+ var $downArrow = $('>.jScrollArrowDown', $container);
+ }
+
+ if (settings.arrowSize) {
+ trackHeight = paneHeight - settings.arrowSize - settings.arrowSize;
+ trackOffset += settings.arrowSize;
+ } else if ($upArrow) {
+ var topArrowHeight = $upArrow.height();
+ settings.arrowSize = topArrowHeight;
+ trackHeight = paneHeight - topArrowHeight - $downArrow.height();
+ trackOffset += topArrowHeight;
+ }
+ trackHeight -= settings.topCapHeight + settings.bottomCapHeight;
+ $track.css({'height': trackHeight+'px', top:trackOffset+'px'})
+
+ var $pane = $(this).css({'position':'absolute', 'overflow':'visible'});
+
+ var currentOffset;
+ var maxY;
+ var mouseWheelMultiplier;
+ // store this in a seperate variable so we can keep track more accurately than just updating the css property..
+ var dragPosition = 0;
+ var dragMiddle = percentInView*paneHeight/2;
+
+ // pos function borrowed from tooltip plugin and adapted...
+ var getPos = function (event, c) {
+ var p = c == 'X' ? 'Left' : 'Top';
+ return event['page' + c] || (event['client' + c] + (document.documentElement['scroll' + p] || document.body['scroll' + p])) || 0;
+ };
+
+ var ignoreNativeDrag = function() { return false; };
+
+ var initDrag = function()
+ {
+ ceaseAnimation();
+ currentOffset = $drag.offset(false);
+ currentOffset.top -= dragPosition;
+ maxY = trackHeight - $drag[0].offsetHeight;
+ mouseWheelMultiplier = 2 * settings.wheelSpeed * maxY / contentHeight;
+ };
+
+ var onStartDrag = function(event)
+ {
+ initDrag();
+ dragMiddle = getPos(event, 'Y') - dragPosition - currentOffset.top;
+ $('html').bind('mouseup', onStopDrag).bind('mousemove', updateScroll);
+ if ($.browser.msie) {
+ $('html').bind('dragstart', ignoreNativeDrag).bind('selectstart', ignoreNativeDrag);
+ }
+ return false;
+ };
+ var onStopDrag = function()
+ {
+ $('html').unbind('mouseup', onStopDrag).unbind('mousemove', updateScroll);
+ dragMiddle = percentInView*paneHeight/2;
+ if ($.browser.msie) {
+ $('html').unbind('dragstart', ignoreNativeDrag).unbind('selectstart', ignoreNativeDrag);
+ }
+ };
+ var positionDrag = function(destY)
+ {
+ $container.scrollTop(0);
+ destY = destY < 0 ? 0 : (destY > maxY ? maxY : destY);
+ dragPosition = destY;
+ $drag.css({'top':destY+'px'});
+ var p = destY / maxY;
+ $this.data('jScrollPanePosition', (paneHeight-contentHeight)*-p);
+ $pane.css({'top':((paneHeight-contentHeight)*p) + 'px'});
+ $this.trigger('scroll');
+ if (settings.showArrows) {
+ $upArrow[destY == 0 ? 'addClass' : 'removeClass']('disabled');
+ $downArrow[destY == maxY ? 'addClass' : 'removeClass']('disabled');
+ }
+ if(p == 1 && p != lastPosition) {
+ loadPosts();
+ }
+ lastPosition = p;
+ };
+ var updateScroll = function(e)
+ {
+ positionDrag(getPos(e, 'Y') - currentOffset.top - dragMiddle);
+ };
+
+ var dragH = Math.max(Math.min(percentInView*(paneHeight-settings.arrowSize*2), settings.dragMaxHeight), settings.dragMinHeight);
+
+ $drag.css(
+ {'height':dragH+'px'}
+ ).bind('mousedown', onStartDrag);
+
+ var trackScrollInterval;
+ var trackScrollInc;
+ var trackScrollMousePos;
+ var doTrackScroll = function()
+ {
+ if (trackScrollInc > 8 || trackScrollInc%4==0) {
+ positionDrag((dragPosition - ((dragPosition - trackScrollMousePos) / 2)));
+ }
+ trackScrollInc ++;
+ };
+ var onStopTrackClick = function()
+ {
+ clearInterval(trackScrollInterval);
+ $('html').unbind('mouseup', onStopTrackClick).unbind('mousemove', onTrackMouseMove);
+ };
+ var onTrackMouseMove = function(event)
+ {
+ trackScrollMousePos = getPos(event, 'Y') - currentOffset.top - dragMiddle;
+ };
+ var onTrackClick = function(event)
+ {
+ initDrag();
+ onTrackMouseMove(event);
+ trackScrollInc = 0;
+ $('html').bind('mouseup', onStopTrackClick).bind('mousemove', onTrackMouseMove);
+ trackScrollInterval = setInterval(doTrackScroll, 100);
+ doTrackScroll();
+ return false;
+ };
+
+ $track.bind('mousedown', onTrackClick);
+
+ $container.bind(
+ 'mousewheel',
+ function (event, delta) {
+ delta = delta || (event.wheelDelta ? event.wheelDelta / 120 : (event.detail) ?
+-event.detail/3 : 0);
+ initDrag();
+ ceaseAnimation();
+ var d = dragPosition;
+ positionDrag(dragPosition - delta * mouseWheelMultiplier);
+ var dragOccured = d != dragPosition;
+ return !dragOccured;
+ }
+ );
+
+ var _animateToPosition;
+ var _animateToInterval;
+ function animateToPosition()
+ {
+ var diff = (_animateToPosition - dragPosition) / settings.animateStep;
+ if (diff > 1 || diff < -1) {
+ positionDrag(dragPosition + diff);
+ } else {
+ positionDrag(_animateToPosition);
+ ceaseAnimation();
+ }
+ }
+ var ceaseAnimation = function()
+ {
+ if (_animateToInterval) {
+ clearInterval(_animateToInterval);
+ delete _animateToPosition;
+ }
+ };
+ var scrollTo = function(pos, preventAni)
+ {
+ if (typeof pos == "string") {
+ // Legal hash values aren't necessarily legal jQuery selectors so we need to catch any
+ // errors from the lookup...
+ try {
+ $e = $(pos, $this);
+ } catch (err) {
+ return;
+ }
+ if (!$e.length) return;
+ pos = $e.offset().top - $this.offset().top;
+ }
+ ceaseAnimation();
+ var maxScroll = contentHeight - paneHeight;
+ pos = pos > maxScroll ? maxScroll : pos;
+ $this.data('jScrollPaneMaxScroll', maxScroll);
+ var destDragPosition = pos/maxScroll * maxY;
+ if (preventAni || !settings.animateTo) {
+ positionDrag(destDragPosition);
+ } else {
+ $container.scrollTop(0);
+ _animateToPosition = destDragPosition;
+ _animateToInterval = setInterval(animateToPosition, settings.animateInterval);
+ }
+ };
+ $this[0].scrollTo = scrollTo;
+
+ $this[0].scrollBy = function(delta)
+ {
+ var currentPos = -parseInt($pane.css('top')) || 0;
+ scrollTo(currentPos + delta);
+ };
+
+ initDrag();
+
+ scrollTo(-currentScrollPosition, true);
+
+ // Deal with it when the user tabs to a link or form element within this scrollpane
+ $('*', this).bind(
+ 'focus',
+ function(event)
+ {
+ var $e = $(this);
+
+ // loop through parents adding the offset top of any elements that are relatively positioned between
+ // the focused element and the jScrollPaneContainer so we can get the true distance from the top
+ // of the focused element to the top of the scrollpane...
+ var eleTop = 0;
+
+ while ($e[0] != $this[0]) {
+ eleTop += $e.position().top;
+ $e = $e.offsetParent();
+ }
+
+ var viewportTop = -parseInt($pane.css('top')) || 0;
+ var maxVisibleEleTop = viewportTop + paneHeight;
+ var eleInView = eleTop > viewportTop && eleTop < maxVisibleEleTop;
+ if (!eleInView) {
+ var destPos = eleTop - settings.scrollbarMargin;
+ if (eleTop > viewportTop) { // element is below viewport - scroll so it is at bottom.
+ destPos += $(this).height() + 15 + settings.scrollbarMargin - paneHeight;
+ }
+ scrollTo(destPos);
+ }
+ }
+ )
+
+
+ if (settings.observeHash) {
+ if (location.hash && location.hash.length > 1) {
+ setTimeout(function(){
+ scrollTo(location.hash);
+ }, $.browser.safari ? 100 : 0);
+ }
+
+ // use event delegation to listen for all clicks on links and hijack them if they are links to
+ // anchors within our content...
+ $(document).bind('click', function(e){
+ $target = $(e.target);
+ if ($target.is('a')) {
+ var h = $target.attr('href');
+ if (h && h.substr(0, 1) == '#' && h.length > 1) {
+ setTimeout(function(){
+ scrollTo(h, !settings.animateToInternalLinks);
+ }, $.browser.safari ? 100 : 0);
+ }
+ }
+ });
+ }
+
+ // Deal with dragging and selecting text to make the scrollpane scroll...
+ function onSelectScrollMouseDown(e)
+ {
+ $(document).bind('mousemove.jScrollPaneDragging', onTextSelectionScrollMouseMove);
+ $(document).bind('mouseup.jScrollPaneDragging', onSelectScrollMouseUp);
+
+ }
+
+ var textDragDistanceAway;
+ var textSelectionInterval;
+
+ function onTextSelectionInterval()
+ {
+ direction = textDragDistanceAway < 0 ? -1 : 1;
+ $this[0].scrollBy(textDragDistanceAway / 2);
+ }
+
+ function clearTextSelectionInterval()
+ {
+ if (textSelectionInterval) {
+ clearInterval(textSelectionInterval);
+ textSelectionInterval = undefined;
+ }
+ }
+
+ function onTextSelectionScrollMouseMove(e)
+ {
+ var offset = $this.parent().offset().top;
+ var maxOffset = offset + paneHeight;
+ var mouseOffset = getPos(e, 'Y');
+ textDragDistanceAway = mouseOffset < offset ? mouseOffset - offset : (mouseOffset > maxOffset ? mouseOffset - maxOffset : 0);
+ if (textDragDistanceAway == 0) {
+ clearTextSelectionInterval();
+ } else {
+ if (!textSelectionInterval) {
+ textSelectionInterval = setInterval(onTextSelectionInterval, 100);
+ }
+ }
+ }
+
+ function onSelectScrollMouseUp(e)
+ {
+ $(document)
+ .unbind('mousemove.jScrollPaneDragging')
+ .unbind('mouseup.jScrollPaneDragging');
+ clearTextSelectionInterval();
+ }
+
+ $container.bind('mousedown.jScrollPane', onSelectScrollMouseDown);
+
+
+ $.jScrollPane.active.push($this[0]);
+
+ } else {
+ $this.css(
+ {
+ 'height':paneHeight+'px',
+ 'width':paneWidth-this.originalSidePaddingTotal+'px',
+ 'padding':this.originalPadding
+ }
+ );
+ $this[0].scrollTo = $this[0].scrollBy = function() {};
+ // clean up listeners
+ $this.parent().unbind('mousewheel').unbind('mousedown.jScrollPane').unbind('keydown.jscrollpane').unbind('keyup.jscrollpane');
+ }
+
+ }
+ )
+};
+
+$.fn.jScrollPaneRemove = function()
+{
+ $(this).each(function()
+ {
+ $this = $(this);
+ var $c = $this.parent();
+ if ($c.is('.jScrollPaneContainer')) {
+ $this.css(
+ {
+ 'top':'',
+ 'height':'',
+ 'width':'',
+ 'padding':'',
+ 'overflow':'',
+ 'position':''
+ }
+ );
+ $this.attr('style', $this.data('originalStyleTag'));
+ $c.after($this).remove();
+ }
+ });
+}
+
+$.fn.jScrollPane.defaults = {
+ scrollbarWidth : 11,
+ scrollbarMargin : 5,
+ wheelSpeed : 18,
+ showArrows : false,
+ arrowSize : 0,
+ animateTo : false,
+ dragMinHeight : 1,
+ dragMaxHeight : 99999,
+ animateInterval : 100,
+ animateStep: 3,
+ maintainPosition: true,
+ scrollbarOnLeft: false,
+ reinitialiseOnImageLoad: false,
+ reload: false,
+ tabIndex : 0,
+ enableKeyboardNavigation: true,
+ animateToInternalLinks: false,
+ topCapHeight: 0,
+ bottomCapHeight: 0,
+ observeHash: true
+};
+
+// clean up the scrollTo expandos
+$(window)
+ .bind('unload', function() {
+ var els = $.jScrollPane.active;
+ for (var i=0; i