diff --git a/Gemfile b/Gemfile
index 13d32d1d..010bd089 100644
--- a/Gemfile
+++ b/Gemfile
@@ -44,6 +44,7 @@ gem "newrelic_rpm", "~> 3.9.6"
gem "draper", "~> 0.11.1"
gem "open_uri_redirections", "~> 0.1.4"
gem "rack-timeout", "~> 0.1.0"
+gem "font-awesome-rails"
# background job queue
gem "delayed_job", :git => "git://github.com/collectiveidea/delayed_job.git", :tag => "v2.1.4"
diff --git a/Gemfile.lock b/Gemfile.lock
index 8a46b313..5a008817 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -110,6 +110,8 @@ GEM
faraday (0.9.0)
multipart-post (>= 1.2, < 3)
ffi (1.9.3)
+ font-awesome-rails (4.3.0.0)
+ railties (>= 3.2, < 5.0)
fssm (0.2.10)
haml (3.1.8)
haml-rails (0.3.5)
@@ -302,6 +304,7 @@ DEPENDENCIES
draper (~> 0.11.1)
exceptional (~> 2.0.32)
fabrication (~> 1.2.0)
+ font-awesome-rails
haml (~> 3.1.4)
haml-rails (~> 0.3.4)
i18n (~> 0.6.0)
diff --git a/app/assets/javascripts/updates.js.coffee b/app/assets/javascripts/updates.js.coffee
new file mode 100644
index 00000000..35106ca0
--- /dev/null
+++ b/app/assets/javascripts/updates.js.coffee
@@ -0,0 +1,25 @@
+jQuery ->
+ if $('.updates-scroll').length
+ $(window).scroll ->
+ next_button = $('#next_button')
+ prev_button = $('#prev_button')
+ url = next_button.attr('href')
+
+ if (next_button.length || prev_button.length) && $(window).scrollTop() > $(document).height() - $(window).height() - 150
+ next_button.addClass('hidden')
+ prev_button.addClass('hidden')
+ $('.loading-info').removeClass('hidden')
+ if url && $(window).scrollTop() > $(document).height() - $(window).height() - 2
+ $('.pagination').html("
")
+ $.getScript(url)
+ $(window).scroll()
+
+ popover = $('.scroll-help-popover')
+ $('.scroll-help').on 'click', (e) ->
+ e.stopPropagation()
+ if popover.hasClass('hidden')
+ popover.removeClass('hidden')
+ $(document).on 'click', ->
+ popover.addClass('hidden')
+ else
+ popover.addClass('hidden')
diff --git a/app/assets/javascripts/user_edit.js.coffee b/app/assets/javascripts/user_edit.js.coffee
new file mode 100644
index 00000000..adf46af8
--- /dev/null
+++ b/app/assets/javascripts/user_edit.js.coffee
@@ -0,0 +1,16 @@
+jQuery ->
+ if $('#profile').length
+ container = $('.infinite_scroll_toggle')
+ # if javascript is not supported, infinite scroll option is not displayed
+ container.removeClass('hidden')
+ checkbox = $('#user_infinite_scroll')
+
+ container.on 'click', '.fa-toggle-on', (e) =>
+ toggleIcon = $(e.target)
+ toggleIcon.removeClass('fa-toggle-on').addClass('fa-toggle-off')
+ checkbox.attr('checked', false)
+
+ container.on 'click', '.fa-toggle-off', (e) =>
+ toggleIcon = $(e.target)
+ toggleIcon.removeClass('fa-toggle-off').addClass('fa-toggle-on')
+ checkbox.attr('checked', true)
diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss
index 08ff3256..98a4108e 100644
--- a/app/assets/stylesheets/application.css.scss
+++ b/app/assets/stylesheets/application.css.scss
@@ -15,3 +15,5 @@
@import "lib/base";
@import "partials/flash";
+
+@import "font-awesome";
diff --git a/app/assets/stylesheets/content.scss.erb b/app/assets/stylesheets/content.scss.erb
index 29ce2cbc..c8ae3327 100644
--- a/app/assets/stylesheets/content.scss.erb
+++ b/app/assets/stylesheets/content.scss.erb
@@ -10,6 +10,10 @@
}
}
+.hidden {
+ display: none;
+}
+
/* ****************************** */
/* Updates Navigation */
/* ****************************** */
@@ -407,6 +411,13 @@
border-color: #522;
}
+.infinite_scroll_toggle {
+ span.fa {
+ margin-left: 9px;
+ cursor: pointer;
+ }
+}
+
/* ****************************** */
/* Sidebar */
/* ****************************** */
diff --git a/app/assets/stylesheets/layout.scss.erb b/app/assets/stylesheets/layout.scss.erb
index 40bd4b87..e5d2876f 100644
--- a/app/assets/stylesheets/layout.scss.erb
+++ b/app/assets/stylesheets/layout.scss.erb
@@ -93,6 +93,20 @@ h2.title {
margin-top: 1em;
}
+ .loading-info {
+ text-align: center;
+ padding-top: 7px;
+ p {
+ margin: 0;
+ span {
+ padding-top: 1px;
+ float: right;
+ margin-left: -20px;
+ cursor: pointer;
+ }
+ }
+ }
+
#next_button {
float: right;
margin-right: 0.5em;
@@ -102,6 +116,53 @@ h2.title {
margin-left: 0.5em;
}
}
+
+ .popover {
+ position: absolute;
+ z-index: 1000;
+ width: 230px;
+ padding: 5px;
+ border: 1px solid rgba(0,0,0,.2);
+ border-radius: 6px;
+ box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ background-color: #fff;
+
+ .arrow {
+
+ &,
+ &:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ }
+
+ border-width: 11px;
+ left: 50%;
+ margin-left: -11px;
+ border-bottom-width: 0;
+ border-top-color: #999;
+ border-top-color: rgba(0,0,0,.25);
+ bottom: -11px;
+
+ &:after {
+ border-width: 10px;
+ content: "";
+ content: " ";
+ bottom: 1px;
+ margin-left: -10px;
+ border-bottom-width: 0;
+ border-top-color: #fff;
+ }
+ }
+ }
+
+ .scroll-help-popover {
+ right: -102px;
+ bottom: 60px;
+ }
}
/* *******************************/
diff --git a/app/controllers/updates_controller.rb b/app/controllers/updates_controller.rb
index 7e4b0571..e254d313 100644
--- a/app/controllers/updates_controller.rb
+++ b/app/controllers/updates_controller.rb
@@ -5,27 +5,35 @@ class UpdatesController < ApplicationController
def index
@title = "updates"
@list_class = "all"
+ prepare_updates(Update)
respond_to do |format|
- format.html { render_index(Update) }
+ format.html { render :index, :layout => show_layout? }
format.json {
- @updates = Update
- set_pagination
render :json => @updates.map{ |u| UpdateJsonDecorator.decorate(u) }
}
+ format.js
end
end
def timeline
if current_user
@list_class = "friends"
- render_index(current_user.timeline)
+ prepare_updates(current_user.timeline)
+ respond_to do |format|
+ format.html { render :index, :layout => show_layout? }
+ format.js { render :index }
+ end
end
end
def replies
@list_class = "mentions"
- render_index(current_user.at_replies)
+ prepare_updates(current_user.at_replies)
+ respond_to do |format|
+ format.html { render :index, :layout => show_layout? }
+ format.js { render :index }
+ end
end
def export
@@ -93,20 +101,13 @@ def destroy
protected
- # Manage page state
- def set_pagination
+ # Prepare correct updates depending on request type
+ def prepare_updates(updates)
set_params_page
- @updates = @updates.paginate(:page => params[:page], :per_page => params[:per_page], :order => :created_at.desc)
+ @updates = updates.paginate(:page => params[:page], :per_page => params[:per_page], :order => :created_at.desc)
set_pagination_buttons(@updates)
end
- # Render correct haml depending on request type
- def render_index(updates)
- @updates = updates
- set_pagination
- render :index, :layout => show_layout?
- end
-
def process_params
@update_id, @update_text = "", ""
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 1aa6908b..72a2fd9f 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -44,6 +44,7 @@ def show
format.json {
render :json => @updates.map{ |u| UpdateJsonDecorator.decorate(u) }
}
+ format.js { render 'updates/index', locals: { no_highlight: true } }
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 4704062f..c9385672 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -42,6 +42,8 @@ def admin?
# Global preference set via user's profile controlling the state of the Post to Twitter checkbox
key :always_send_to_twitter, Integer, :default => 1
+ key :infinite_scroll, Boolean, default: true
+
validate :email_already_confirmed
validates_uniqueness_of :username,
:allow_nil => :true,
@@ -355,6 +357,8 @@ def update_profile!(params)
self.always_send_to_twitter = params[:user] && params[:user][:always_send_to_twitter].to_i
+ self.infinite_scroll = params[:user][:infinite_scroll] if params[:user] && params[:user][:infinite_scroll]
+
# I can't figure out how to use a real rails validator to confirm that
# password matches password_confirm, since these two attributes are
# virtual and we only want to check this in this particular case of
diff --git a/app/views/shared/_pagination.haml b/app/views/shared/_pagination.haml
index 19e67312..e0c6b037 100644
--- a/app/views/shared/_pagination.haml
+++ b/app/views/shared/_pagination.haml
@@ -1,4 +1,19 @@
%nav.pagination
+
+ .loading-info.hidden
+ .popover.scroll-help-popover.hidden
+ %b TIP:
+ You can disable infinite scroll in your
+ - if logged_in?
+ != "#{link_to 'profile settings', edit_user_path(current_user)}."
+ - else
+ profile settings.
+ .arrow
+ %p
+ Keep scrolling to load more data
+ %span.fa.fa-question-circle.fa-lg.scroll-help
+ %i.fa.fa-hand-o-down.fa-lg
+
- unless @previous_page.nil?
%a.button{:href => @previous_page, :id => "prev_button", :rel => "previous"}
« Previous
diff --git a/app/views/updates/_list.html.haml b/app/views/updates/_list.html.haml
index 3dd5cbd3..870b7419 100644
--- a/app/views/updates/_list.html.haml
+++ b/app/views/updates/_list.html.haml
@@ -1,51 +1,13 @@
- unless defined?(id)
- id = (request.path_info.gsub("/", "_")[1..-1])
+- if logged_in?
+ - infinite_scroll_toggle = current_user.infinite_scroll ? "updates-scroll" : ""
+- else
+ - infinite_scroll_toggle = controller_name == "static" ? "" : "updates-scroll"
#messages
- %ul.updates{:class => "#{list_class}#{(controller.controller_name == "updates" ? " has-update-form" : "")}", :id => id}
+ %ul.updates{:class => "#{list_class}#{(controller.controller_name == "updates" ? " has-update-form #{infinite_scroll_toggle}" : " #{infinite_scroll_toggle}")}", :id => id}
- updates.each do |update|
- - unless defined?(no_highlight) and no_highlight
- - mine = current_user.nil? ? false : (update.author.user == current_user)
- - mentioned = current_user.nil? ? false : update.mentioned?(current_user.username)
- %li.update.hentry.message{:class => (mentioned ? "mention " : "") + (mine ? "mine " : ""), :id => "update-#{update.id}", "data-id" => update.id, "data-name" => update.author ? update.author.fully_qualified_name : ""}
- - if update.author
- .author.vcard
- %a.url{:href => update.author.url, :rel => "user"}
- = AuthorDecorator.decorate(update.author).avatar
- %span.fn
- %a.url{:href => update.author.url}
- = update.author.display_name
- (#{update.author.username})
- .entry-content
- %span.message-text
- != update.to_html
-
- .info
- = TimeDecorator.decorate(update).permalink
-
- - if !update.referral.nil?
- %span.in-reply
- %a{:href => "/updates/#{update.referral.id}"}
- in reply to
- - if update.referral.author
- %span.name= update.referral.author.username
- - else
- an update
- - elsif !update.referral_url.nil?
- %span.in-reply
- %a{:href => update.referral_url}
- in reply to
-
- .actions
- -# when @timeline is true, this is a list on the user's page
- -unless current_user.nil? or (current_user.author.id == update.author.id)
- %a.share{:href => (controller.controller_name == "updates" ? "" : "/") + "?share=#{update.id}"} share
- |
- %a.reply{:href => (controller.controller_name == "updates" ? "" : "/") + "?reply=#{update.id}"} reply
-
- - if current_user and update.author.id == current_user.author.id
- = form_tag "/updates/#{update.id}" do
- %input{:type => "hidden", :name => "_method", :value => "delete"}
- = submit_tag "I Regret This", :class => "remove-update", :confirm => t(:remove_update, :scope => :confirms)
+ = render update, no_highlight: defined?(no_highlight)
!= render :partial => "shared/pagination"
diff --git a/app/views/updates/_update.html.haml b/app/views/updates/_update.html.haml
new file mode 100644
index 00000000..464119b7
--- /dev/null
+++ b/app/views/updates/_update.html.haml
@@ -0,0 +1,43 @@
+- unless defined?(no_highlight) and no_highlight
+ - mine = current_user.nil? ? false : (update.author.user == current_user)
+ - mentioned = current_user.nil? ? false : update.mentioned?(current_user.username)
+%li.update.hentry.message{:class => (mentioned ? "mention " : "") + (mine ? "mine " : ""), :id => "update-#{update.id}", "data-id" => update.id, "data-name" => update.author ? update.author.fully_qualified_name : ""}
+ - if update.author
+ .author.vcard
+ %a.url{:href => update.author.url, :rel => "user"}
+ = AuthorDecorator.decorate(update.author).avatar
+ %span.fn
+ %a.url{:href => update.author.url}
+ = update.author.display_name
+ (#{update.author.username})
+ .entry-content
+ %span.message-text
+ != update.to_html
+
+ .info
+ = TimeDecorator.decorate(update).permalink
+
+ - if !update.referral.nil?
+ %span.in-reply
+ %a{:href => "/updates/#{update.referral.id}"}
+ in reply to
+ - if update.referral.author
+ %span.name= update.referral.author.username
+ - else
+ an update
+ - elsif !update.referral_url.nil?
+ %span.in-reply
+ %a{:href => update.referral_url}
+ in reply to
+
+ .actions
+ -# when @timeline is true, this is a list on the user's page
+ -unless current_user.nil? or (current_user.author.id == update.author.id)
+ %a.share{:href => (controller.controller_name == "updates" ? "" : "/") + "?share=#{update.id}"} share
+ |
+ %a.reply{:href => (controller.controller_name == "updates" ? "" : "/") + "?reply=#{update.id}"} reply
+
+ - if current_user and update.author.id == current_user.author.id
+ = form_tag "/updates/#{update.id}" do
+ %input{:type => "hidden", :name => "_method", :value => "delete"}
+ = submit_tag "I Regret This", :class => "remove-update", :confirm => t(:remove_update, :scope => :confirms)
diff --git a/app/views/updates/index.js.erb b/app/views/updates/index.js.erb
new file mode 100644
index 00000000..b661adda
--- /dev/null
+++ b/app/views/updates/index.js.erb
@@ -0,0 +1,21 @@
+$('.updates-scroll').append('<%= j render @updates, no_highlight: defined?(no_highlight) %>');
+<% if @updates.next_page %>
+ $('.pagination').replaceWith('<%= j raw render "shared/pagination" %>');
+<% else %>
+ $('.pagination').remove();
+<% end %>
+
+var popover;
+popover = $('.scroll-help-popover');
+
+$('.scroll-help').on('click', function(e) {
+ e.stopPropagation();
+ if (popover.hasClass('hidden')) {
+ popover.removeClass('hidden');
+ return $(document).on('click', function() {
+ return popover.addClass('hidden');
+ });
+ } else {
+ return popover.addClass('hidden');
+ }
+});
diff --git a/app/views/users/edit.haml b/app/views/users/edit.haml
index 4a6ffefa..41e806af 100644
--- a/app/views/users/edit.haml
+++ b/app/views/users/edit.haml
@@ -20,6 +20,14 @@
= render :partial => "shared/field", :locals => {:obj => @user.author, :attr => :website, :params => params}
= render :partial => "shared/field", :locals => {:obj => @user.author, :attr => :bio, :as => :text, :params => params}
+ .form-section.infinite_scroll_toggle.hidden
+ = f.check_box :infinite_scroll, class: 'hidden'
+ = label_tag '', "Updates' feed infinite scroll"
+ - if current_user.infinite_scroll
+ %span.fa.fa-toggle-on.fa-lg.infinite-scroll-on
+ - else
+ %span.fa.fa-toggle-off.fa-lg.inifinite-scroll-off
+
- if @user.twitter?
.form-section.preferences
.section-label