diff --git a/app/controllers/asciicasts_controller.rb b/app/controllers/asciicasts_controller.rb
index ef88cae..ec1bd37 100644
--- a/app/controllers/asciicasts_controller.rb
+++ b/app/controllers/asciicasts_controller.rb
@@ -1,5 +1,4 @@
class AsciicastsController < ApplicationController
- PER_PAGE = 15
before_filter :load_resource, :only => [:show, :raw, :edit, :update, :destroy]
before_filter :ensure_authenticated!, :only => [:edit, :update, :destroy]
@@ -8,23 +7,11 @@ class AsciicastsController < ApplicationController
respond_to :html, :json, :js
def index
- @asciicasts = PaginatingDecorator.new(
- Asciicast.newest_paginated(params[:page], PER_PAGE)
- )
+ asciicasts = AsciicastList.new(params[:category], params[:order])
- @category_name = "All asciicasts"
- @current_category = :all
- end
-
- def popular
- @asciicasts = PaginatingDecorator.new(
- Asciicast.popular_paginated(params[:page], PER_PAGE)
- )
-
- @category_name = "Popular asciicasts"
- @current_category = :popular
-
- render :index
+ render locals: {
+ asciicast_list: AsciicastListDecorator.new(asciicasts, params[:page])
+ }
end
def show
diff --git a/app/decorators/asciicast_list_decorator.rb b/app/decorators/asciicast_list_decorator.rb
new file mode 100644
index 0000000..4533fe0
--- /dev/null
+++ b/app/decorators/asciicast_list_decorator.rb
@@ -0,0 +1,23 @@
+class AsciicastListDecorator < ApplicationDecorator
+
+ PER_PAGE = 12
+
+ attr_reader :page, :per_page
+
+ delegate_all
+
+ def initialize(model, page, per_page = nil)
+ super(model)
+ @page = page
+ @per_page = per_page || PER_PAGE
+ end
+
+ def category_name
+ "#{category.to_s.capitalize} asciicasts"
+ end
+
+ def items
+ PaginatingDecorator.new(model.items.paginate(page, per_page))
+ end
+
+end
diff --git a/app/models/asciicast.rb b/app/models/asciicast.rb
index 052d72d..19d5ddc 100644
--- a/app/models/asciicast.rb
+++ b/app/models/asciicast.rb
@@ -1,5 +1,6 @@
class Asciicast < ActiveRecord::Base
- MAX_DELAY = 5.0
+
+ ORDER_MODES = { recency: 'created_at', popularity: 'views_count' }
mount_uploader :stdin_data, StdinDataUploader
mount_uploader :stdin_timing, StdinTimingUploader
@@ -30,6 +31,20 @@ class Asciicast < ActiveRecord::Base
Digest::MD5.hexdigest timestamps.join('/')
end
+ def self.paginate(page, per_page)
+ page(page).per(per_page)
+ end
+
+ def self.for_category_ordered(category, order)
+ collection = all
+
+ if category == :featured
+ collection = collection.featured
+ end
+
+ collection.order("#{ORDER_MODES[order]} DESC")
+ end
+
def stdout
@stdout ||= BufferedStdout.new(stdout_data.decompressed_path,
stdout_timing.decompressed_path).lazy
diff --git a/app/models/asciicast_list.rb b/app/models/asciicast_list.rb
new file mode 100644
index 0000000..b9e0571
--- /dev/null
+++ b/app/models/asciicast_list.rb
@@ -0,0 +1,15 @@
+class AsciicastList
+
+ attr_reader :category, :order, :repository
+
+ def initialize(category, order, repository = Asciicast)
+ @category = (category || :all).to_sym
+ @order = (order || :recency).to_sym
+ @repository = repository
+ end
+
+ def items
+ repository.for_category_ordered(category, order)
+ end
+
+end
diff --git a/app/views/asciicasts/index.html.slim b/app/views/asciicasts/index.html.slim
index 1b7eb68..2f20bdc 100644
--- a/app/views/asciicasts/index.html.slim
+++ b/app/views/asciicasts/index.html.slim
@@ -1,9 +1,30 @@
-section.supplimental
- .wrapper
- .main
- h1 = @category_name
- = render :partial => 'asciicasts/previews', :locals => { :asciicasts => @asciicasts }
- = paginate @asciicasts
+.asciicasts-list-page
+ .container
+ .row
+ .col-md-3
+ = render 'shared/browse_categories', current_category: asciicast_list.category
- .extras
- = render :partial => 'shared/browse_categories'
+ .col-md-9
+ .row
+ .col-md-6
+ h2 = asciicast_list.category_name
+
+ .col-md-6.text-right
+ .sorting
+ span Sort by
+ .btn-group.text-left
+ button.btn.btn-default.dropdown-toggle[type="button" data-toggle="dropdown"]
+ | #{asciicast_list.order}
+ span.caret
+ ul.dropdown-menu[role="menu"]
+ li
+ a href="?order=recency"
+ | recency
+ li
+ a href="?order=popularity"
+ | popularity
+
+ .row.asciicast-list
+ .col-md-12
+ = render 'asciicasts/previews', asciicasts: asciicast_list.items, per_row: 2
+ = paginate asciicast_list.items
diff --git a/app/views/kaminari/_first_page.html.erb b/app/views/kaminari/_first_page.html.erb
new file mode 100644
index 0000000..bf23ff0
--- /dev/null
+++ b/app/views/kaminari/_first_page.html.erb
@@ -0,0 +1,3 @@
+
+ <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote %>
+
diff --git a/app/views/kaminari/_gap.html.erb b/app/views/kaminari/_gap.html.erb
new file mode 100644
index 0000000..416ebcd
--- /dev/null
+++ b/app/views/kaminari/_gap.html.erb
@@ -0,0 +1,3 @@
+
+ <%= link_to raw(t 'views.pagination.truncate'), '#' %>
+
diff --git a/app/views/kaminari/_last_page.html.erb b/app/views/kaminari/_last_page.html.erb
new file mode 100644
index 0000000..fb619ea
--- /dev/null
+++ b/app/views/kaminari/_last_page.html.erb
@@ -0,0 +1,3 @@
+
+ <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote} %>
+
diff --git a/app/views/kaminari/_next_page.html.erb b/app/views/kaminari/_next_page.html.erb
new file mode 100644
index 0000000..15e10e4
--- /dev/null
+++ b/app/views/kaminari/_next_page.html.erb
@@ -0,0 +1,3 @@
+
+ <%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, :rel => 'next', :remote => remote %>
+
diff --git a/app/views/kaminari/_page.html.erb b/app/views/kaminari/_page.html.erb
new file mode 100644
index 0000000..205a81f
--- /dev/null
+++ b/app/views/kaminari/_page.html.erb
@@ -0,0 +1,3 @@
+
+ <%= link_to page, page.current? ? '#' : url, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil} %>
+
diff --git a/app/views/kaminari/_paginator.html.erb b/app/views/kaminari/_paginator.html.erb
new file mode 100644
index 0000000..2c8757b
--- /dev/null
+++ b/app/views/kaminari/_paginator.html.erb
@@ -0,0 +1,15 @@
+<%= paginator.render do -%>
+
+<% end -%>
diff --git a/app/views/kaminari/_prev_page.html.erb b/app/views/kaminari/_prev_page.html.erb
new file mode 100644
index 0000000..d94a50a
--- /dev/null
+++ b/app/views/kaminari/_prev_page.html.erb
@@ -0,0 +1,3 @@
+
+ <%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, :rel => 'prev', :remote => remote %>
+
diff --git a/app/views/shared/_browse_categories.html.erb b/app/views/shared/_browse_categories.html.erb
deleted file mode 100644
index 2fae34b..0000000
--- a/app/views/shared/_browse_categories.html.erb
+++ /dev/null
@@ -1,17 +0,0 @@
-Browse
-
-
- -
- <%= link_to_category 'All', browse_path, :all %>
-
-
- -
- <%= link_to_category 'Popular', popular_path, :popular %>
-
-
- <% if current_user -%>
- -
- <%= link_to 'Mine', profile_path(current_user) %>
-
- <% end -%>
-
diff --git a/app/views/shared/_browse_categories.html.slim b/app/views/shared/_browse_categories.html.slim
new file mode 100644
index 0000000..81fe491
--- /dev/null
+++ b/app/views/shared/_browse_categories.html.slim
@@ -0,0 +1,5 @@
+h2 Browse
+
+= category_links current_category do |categories|
+ = categories.link_to 'All', browse_path, :all
+ = categories.link_to 'Featured', category_path(:featured), :featured
diff --git a/config/routes.rb b/config/routes.rb
index 115b5ec..2dc0bbe 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,7 +1,7 @@
Asciinema::Application.routes.draw do
get "/browse" => "asciicasts#index", :as => :browse
- get "/browse/popular" => "asciicasts#popular", :as => :popular
+ get "/browse/:category" => "asciicasts#index", :as => :category
resources :asciicasts, :path => 'a' do
member do
diff --git a/spec/controllers/asciicasts_controller_spec.rb b/spec/controllers/asciicasts_controller_spec.rb
index 281b761..7119f07 100644
--- a/spec/controllers/asciicasts_controller_spec.rb
+++ b/spec/controllers/asciicasts_controller_spec.rb
@@ -11,49 +11,33 @@ shared_examples_for 'non-owner user trying to modify' do
end
describe AsciicastsController do
+
let(:user) { stub_model(User, :nickname => 'nick') }
let(:asciicast) { stub_model(Asciicast, :id => 666) }
subject { response }
describe '#index' do
- let(:asciicasts) { [double('asciicast')] }
- let(:page) { double('page').to_s }
+ let(:asciicast_list) { double('asciicast_list') }
+ let(:decorated_asciicast_list) { double('decorated_asciicast_list') }
before do
- Asciicast.should_receive(:newest_paginated).
- with(page, an_instance_of(Fixnum)).and_return(asciicasts)
+ allow(controller).to receive(:render)
- PaginatingDecorator.should_receive(:new).with(asciicasts).
- and_return(asciicasts)
+ allow(AsciicastList).to receive(:new).
+ with('featured', 'recency') { asciicast_list }
+ allow(AsciicastListDecorator).to receive(:new).
+ with(asciicast_list, '2') { decorated_asciicast_list }
- get :index, :page => page
+ get :index, category: 'featured', order: 'recency', page: '2'
end
it { should be_success }
- specify { assigns(:asciicasts).should == asciicasts }
- specify { assigns(:category_name).should =~ /^All/ }
- specify { assigns(:current_category).should == :all }
- end
-
- describe '#popular' do
- let(:asciicasts) { [double('asciicast')] }
- let(:page) { double('page').to_s }
-
- before do
- Asciicast.should_receive(:popular_paginated).
- with(page, an_instance_of(Fixnum)).and_return(asciicasts)
-
- PaginatingDecorator.should_receive(:new).with(asciicasts).
- and_return(asciicasts)
- get :popular, :page => page
+ it 'renders template with asciicast_list' do
+ expect(controller).to have_received(:render).
+ with(locals: { asciicast_list: decorated_asciicast_list })
end
-
- it { should be_success }
- specify { assigns(:asciicasts).should == asciicasts }
- specify { assigns(:category_name).should =~ /^Popular/ }
- specify { assigns(:current_category).should == :popular }
end
describe '#show' do
diff --git a/spec/decorators/asciicast_list_decorator_spec.rb b/spec/decorators/asciicast_list_decorator_spec.rb
new file mode 100644
index 0000000..228fed2
--- /dev/null
+++ b/spec/decorators/asciicast_list_decorator_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe AsciicastListDecorator do
+
+ let(:decorator) { described_class.new(list, 3, 10) }
+ let(:list) { double('list', category: :foo, items: items) }
+ let(:items) { double('items', paginate: paginated) }
+ let(:paginated) { [Asciicast.new] }
+
+ describe '#category_name' do
+ subject { decorator.category_name }
+
+ it { should eq('Foo asciicasts') }
+ end
+
+ describe '#items' do
+ subject { decorator.items }
+
+ it 'returns the items paginated' do
+ expect(subject).to eq(paginated)
+ expect(items).to have_received(:paginate).with(3, 10)
+ end
+
+ it 'wraps the paginated items in a PaginatingDecorator' do
+ paginating_decorator = double('paginating_decorator')
+
+ allow(PaginatingDecorator).to receive(:new).
+ with(paginated) { paginating_decorator }
+
+ expect(subject).to be(paginating_decorator)
+ end
+ end
+
+end
diff --git a/spec/features/asciicasts_spec.rb b/spec/features/asciicasts_spec.rb
index bad18cd..f21c72c 100644
--- a/spec/features/asciicasts_spec.rb
+++ b/spec/features/asciicasts_spec.rb
@@ -2,25 +2,26 @@ require 'spec_helper'
feature "Asciicast lists" do
- let!(:asciicast) { create(:asciicast) }
+ let!(:asciicast) { create(:asciicast, title: 'foo bar') }
+ let!(:featured_asciicast) { create(:asciicast, title: 'qux', featured: true) }
scenario 'Visiting all' do
visit browse_path
expect(page).to have_content(/All Asciicasts/i)
expect_browse_links
- expect(page).to have_link("bashing")
- expect(page).to have_selector('.supplimental .play-button')
+ expect(page).to have_link("foo bar")
+ expect(page).to have_selector('.asciicast-list .play-button')
end
- scenario 'Visiting popular' do
+ scenario 'Visiting featured' do
visit asciicast_path(asciicast)
- visit popular_path
+ visit category_path(:featured)
- expect(page).to have_content(/Popular Asciicasts/i)
+ expect(page).to have_content(/Featured Asciicasts/i)
expect_browse_links
- expect(page).to have_link("bashing")
- expect(page).to have_selector('.supplimental .play-button')
+ expect(page).to have_link("qux")
+ expect(page).to have_selector('.asciicast-list .play-button')
end
end
diff --git a/spec/models/asciicast_list_spec.rb b/spec/models/asciicast_list_spec.rb
new file mode 100644
index 0000000..00dd15d
--- /dev/null
+++ b/spec/models/asciicast_list_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe AsciicastList do
+
+ let(:list) { described_class.new(category, order, repository) }
+
+ let(:category) { 'featured' }
+ let(:order) { 'recency' }
+ let(:repository) { double('repository') }
+
+ describe '#category' do
+ subject { list.category }
+
+ context "when it was passed as a string" do
+ let(:category) { 'thecat' }
+
+ it { should eq(:thecat) }
+ end
+
+ context "when it was passed as nil" do
+ let(:category) { nil }
+
+ it { should eq(:all) }
+ end
+ end
+
+ describe '#order' do
+ subject { list.order }
+
+ context "when it was passed as a string" do
+ let(:order) { 'thecat' }
+
+ it { should eq(:thecat) }
+ end
+
+ context "when it was passed as nil" do
+ let(:order) { nil }
+
+ it { should eq(:recency) }
+ end
+ end
+
+ describe '#items' do
+ subject { list.items }
+
+ let(:category) { 'foo' }
+ let(:order) { 'bar' }
+ let(:asciicasts) { [Asciicast.new] }
+
+ before do
+ allow(repository).to receive(:for_category_ordered) { asciicasts }
+ subject
+ end
+
+ it { should eq(asciicasts) }
+
+ it 'calls for_category_ordered on repository with proper args' do
+ expect(repository).to have_received(:for_category_ordered).
+ with(:foo, :bar)
+ end
+ end
+
+end
diff --git a/spec/models/asciicast_spec.rb b/spec/models/asciicast_spec.rb
index c6d08ed..bd0ef06 100644
--- a/spec/models/asciicast_spec.rb
+++ b/spec/models/asciicast_spec.rb
@@ -3,6 +3,55 @@ require 'tempfile'
describe Asciicast do
+ describe '.for_category_ordered' do
+ subject { described_class.for_category_ordered(category, order) }
+
+ let!(:asciicast_1) { create(:asciicast, created_at: 2.hours.ago,
+ views_count: 10,
+ featured: false) }
+ let!(:asciicast_2) { create(:asciicast, created_at: 1.hour.ago,
+ views_count: 20,
+ featured: true) }
+ let!(:asciicast_3) { create(:asciicast, created_at: 4.hours.ago,
+ views_count: 30,
+ featured: false) }
+ let!(:asciicast_4) { create(:asciicast, created_at: 3.hours.ago,
+ views_count: 40,
+ featured: true) }
+
+ context "when category is :all" do
+ let(:category) { :all }
+
+ context "and order is :recency" do
+ let(:order) { :recency }
+
+ it { should eq([asciicast_2, asciicast_1, asciicast_4, asciicast_3]) }
+ end
+
+ context "and order is :popularity" do
+ let(:order) { :popularity }
+
+ it { should eq([asciicast_4, asciicast_3, asciicast_2, asciicast_1]) }
+ end
+ end
+
+ context "when category is :featured" do
+ let(:category) { :featured }
+
+ context "and order is :recency" do
+ let(:order) { :recency }
+
+ it { should eq([asciicast_2, asciicast_4]) }
+ end
+
+ context "and order is :popularity" do
+ let(:order) { :popularity }
+
+ it { should eq([asciicast_4, asciicast_2]) }
+ end
+ end
+ end
+
let(:asciicast) { described_class.new }
describe '#stdout' do
diff --git a/spec/support/feature_helpers.rb b/spec/support/feature_helpers.rb
index 477639e..1a6da5c 100644
--- a/spec/support/feature_helpers.rb
+++ b/spec/support/feature_helpers.rb
@@ -3,7 +3,7 @@ module Asciinema
def expect_browse_links
expect(page).to have_link('All')
- expect(page).to have_link('Popular')
+ expect(page).to have_link('Featured')
end
def expect_doc_links