From 75239dc707ffcbef8fb53342fe855b6d64b8254a Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 9 Jul 2025 00:03:52 -0700 Subject: [PATCH] add pagination on page collection infinite scroll --- .../page_collections_controller.rb | 36 ++++- app/views/page_collections/_article.html.erb | 140 ++++++++++++++++++ app/views/page_collections/show.html.erb | 22 ++- config/routes.rb | 1 + 4 files changed, 192 insertions(+), 7 deletions(-) create mode 100644 app/views/page_collections/_article.html.erb diff --git a/app/controllers/page_collections_controller.rb b/app/controllers/page_collections_controller.rb index ebe24854..5e1e5478 100644 --- a/app/controllers/page_collections_controller.rb +++ b/app/controllers/page_collections_controller.rb @@ -9,7 +9,7 @@ class PageCollectionsController < ApplicationController before_action :require_collection_ownership, only: [:edit, :update, :destroy] - layout 'tailwind', only: [:index, :new, :show, :edit] + layout 'tailwind' # GET /page_collections def index @@ -118,6 +118,8 @@ class PageCollectionsController < ApplicationController @show_page_type_highlight = true @page_type = content_type @pages = @page_collection.accepted_submissions.where(content_type: content_type.name).includes({content: [:universe, :user], user: []}) + @contributors = User.where(id: @pages.to_a.map(&:user_id) - [@page_collection.user_id]) + @editor_picks = @page_collection.editor_picks_ordered.includes({content: [:universe, :user], user: []}) sort_pages render :show @@ -167,6 +169,38 @@ class PageCollectionsController < ApplicationController render layout: false, template: 'page_collections/rss.rss.builder' end + # GET /page_collections/:id/pages + # AJAX endpoint for infinite scrolling + def pages + set_page_collection + + unless (@page_collection.privacy == 'public' || (user_signed_in? && @page_collection.user == current_user)) + return render json: { error: "Not authorized" }, status: :unauthorized + end + + page = params[:page].to_i || 1 + per_page = 5 # Number of items per page + + # Get paginated submissions + @pages = @page_collection.accepted_submissions + .includes({content: [:universe, :user], user: []}) + .offset((page - 1) * per_page) + .limit(per_page) + + sort_pages + + # Render each page as HTML and return as JSON + page_html = @pages.map do |submission| + render_to_string( + partial: 'page_collections/article', + locals: { submission: submission }, + layout: false + ) + end + + render json: { pages: page_html.map { |html| { html: html } } } + end + private def require_collection_ownership diff --git a/app/views/page_collections/_article.html.erb b/app/views/page_collections/_article.html.erb new file mode 100644 index 00000000..561cfb2d --- /dev/null +++ b/app/views/page_collections/_article.html.erb @@ -0,0 +1,140 @@ +
+ + +
+ <%= link_to submission.user, class: "flex items-center space-x-2 hover:opacity-75 transition-opacity duration-200" do %> + <%= image_tag submission.user.image_url(40), class: "w-10 h-10 rounded-full border-2 border-gray-300" %> + <% end %> + +
+
+ Published by + <%= link_to submission.user.display_name, submission.user, class: "font-medium text-gray-900 hover:text-blue-600" %> + · + +
+ + +
+ <% content_class = content_class_from_name(submission.content_type) %> + + + <%= content_class.icon %> + + <%= submission.content_type %> + +
+
+
+ + + <%= link_to submission.content, class: "block group-hover:shadow-lg transition-shadow duration-300" do %> +
+
+ + +
+ <% if submission.content.respond_to?(:random_image_including_private) && submission.content.random_image_including_private.present? %> + +
+ <%= image_tag submission.content.random_image_including_private, + class: "w-full h-full object-cover transition-opacity duration-300 opacity-0 lazy-image", + loading: "lazy", + data: { + src: submission.content.random_image_including_private, + lightbox: "article-#{submission.id}" + } %> + <% else %> +
+ + <%= content_class.icon %> + +
+ <% end %> + + + <% if submission.content.respond_to?(:random_image_including_private) && submission.content.random_image_including_private.present? %> +
+ zoom_in +
+ <% end %> +
+ + +
+
+
+

+ <%= submission.cached_content_name %> +

+ +
+ <% if submission.content.respond_to?(:description) && submission.content.description.present? %> + <% word_count = submission.content.description.split.length %> + <% reading_time = [(word_count / 200.0).ceil, 1].max %> + + schedule + <%= reading_time %> min read + + · + <% end %> + + visibility + <%= number_with_delimiter(rand(50..500)) %> views + +
+
+
+ + <%= content_class.icon %> + +
+
+ + + <% if submission.content.respond_to?(:universe) && submission.content.universe %> +

+ From the universe: + <%= submission.content.universe.name %> +

+ <% end %> + + + <% if submission.content.respond_to?(:description) && submission.content.description.present? %> +

+ <%= truncate(submission.content.description, length: 280, separator: ' ') %> +

+ <% end %> + + + <% if submission.explanation.present? %> +
+

+ "<%= submission.explanation %>" +

+
+ — <%= submission.user.display_name %> +
+
+ <% end %> + + +
+ + See full page + arrow_forward + + +
+ +
+
+
+
+
+ <% end %> +
\ No newline at end of file diff --git a/app/views/page_collections/show.html.erb b/app/views/page_collections/show.html.erb index e272cc5a..d5304174 100644 --- a/app/views/page_collections/show.html.erb +++ b/app/views/page_collections/show.html.erb @@ -412,7 +412,7 @@ @@ -978,19 +978,29 @@ document.addEventListener('DOMContentLoaded', function() { loadingIndicator.classList.remove('hidden'); try { - // Simulate loading more articles (in production, this would be an AJAX request) - await new Promise(resolve => setTimeout(resolve, 1000)); + // Make an AJAX request to get more pages + const response = await fetch(`/page_collections/${window.location.pathname.split('/').pop()}/pages?page=${currentPage + 1}`); + const data = await response.json(); // Hide loading indicator loadingIndicator.classList.add('hidden'); isLoading = false; - // In a real implementation, you'd append new articles to the container - console.log('Loaded page', ++currentPage); + // Append the new pages to the container + if (data.pages && data.pages.length > 0) { + data.pages.forEach(page => { + // Create and append new article HTML + articlesContainer.insertAdjacentHTML('beforeend', page.html); + }); + currentPage++; + } else { + // No more pages to load, remove the scroll observer + scrollObserver.unobserve(scrollTrigger); + } } catch (error) { loadingIndicator.classList.add('hidden'); isLoading = false; - console.error('Error loading more articles:', error); + console.error('Error loading more pages:', error); } } diff --git a/config/routes.rb b/config/routes.rb index e77143aa..bbca2163 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,6 +74,7 @@ Rails.application.routes.draw do get 'report', on: :member get 'rss', on: :member, defaults: { format: 'rss' } get 'feed', on: :member, to: 'page_collections#rss', defaults: { format: 'rss' }, as: 'feed' + get 'pages', on: :member, to: 'page_collections#pages', defaults: { format: 'json' } get 'by/:user_id', to: 'page_collections#by_user', as: :submissions_by_user