diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 58992a4e5..3e74b38fe 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -13,7 +13,7 @@ class ApplicationController < ActionController::Base
before_action :maybe_redirect_to_setup, unless: :signed_in?
before_action :authenticate_user!, unless: :devise_controller?
- before_action :set_csp, if: -> { request.get? && !request.headers['HTTP_X_TURBO'] }
+ before_action :set_csp, if: -> { request.get? && !request.headers['HTTP_X_TURBO'] && !turbo_infinite_scroll? }
helper_method :button_title,
:current_account,
@@ -65,6 +65,10 @@ class ApplicationController < ActionController::Base
private
+ def turbo_infinite_scroll?
+ request.headers['HTTP_X_TURBO_INFINITE_SCROLL'].present?
+ end
+
def turbo_native_ios?
request.user_agent.to_s.include?('Hotwire Native iOS')
end
diff --git a/app/controllers/submissions_archived_controller.rb b/app/controllers/submissions_archived_controller.rb
index 46fa8278b..d3f1ddfda 100644
--- a/app/controllers/submissions_archived_controller.rb
+++ b/app/controllers/submissions_archived_controller.rb
@@ -29,5 +29,17 @@ class SubmissionsArchivedController < ApplicationController
ActiveRecord::Associations::Preloader.new(records: @submissions.filter_map(&:template),
associations: :author).call
+
+ render_infinite_scroll(@pagy, @submissions) if turbo_infinite_scroll?
+ end
+
+ private
+
+ def render_infinite_scroll(pagy, submissions)
+ render turbo_stream: [
+ turbo_stream.before('infinite_scroll', partial: 'templates/submission', collection: submissions,
+ locals: { with_template: true, archived: true }),
+ turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: })
+ ]
end
end
diff --git a/app/controllers/submissions_dashboard_controller.rb b/app/controllers/submissions_dashboard_controller.rb
index dea9a6f3a..91f6f04b5 100644
--- a/app/controllers/submissions_dashboard_controller.rb
+++ b/app/controllers/submissions_dashboard_controller.rb
@@ -30,5 +30,17 @@ class SubmissionsDashboardController < ApplicationController
ActiveRecord::Associations::Preloader.new(records: @submissions.filter_map(&:template),
associations: :author).call
+
+ render_infinite_scroll(@pagy, @submissions) if turbo_infinite_scroll?
+ end
+
+ private
+
+ def render_infinite_scroll(pagy, submissions)
+ render turbo_stream: [
+ turbo_stream.before('infinite_scroll', partial: 'templates/submission', collection: submissions,
+ locals: { with_template: true }),
+ turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: })
+ ]
end
end
diff --git a/app/controllers/template_folders_controller.rb b/app/controllers/template_folders_controller.rb
index a315b1674..3a305e3b0 100644
--- a/app/controllers/template_folders_controller.rb
+++ b/app/controllers/template_folders_controller.rb
@@ -13,10 +13,7 @@ class TemplateFoldersController < ApplicationController
.where(folder: [@template_folder, *(params[:q].present? ? @template_folder.subfolders : [])])
.preload(:author, :template_accesses)
- @template_folders =
- @template_folder.subfolders.where(id: Template.accessible_by(current_ability).active.select(:folder_id))
-
- @template_folders = TemplateFolders.search(@template_folders, params[:q])
+ @template_folders = filter_folders(@template_folder)
@template_folders = TemplateFolders.sort(@template_folders, current_user, selected_order)
if @templates.exists?
@@ -40,6 +37,8 @@ class TemplateFoldersController < ApplicationController
@templates = @templates.none
end
+
+ render_infinite_scroll(@pagy, @templates, @template_folders) if turbo_infinite_scroll?
end
def edit; end
@@ -55,6 +54,27 @@ class TemplateFoldersController < ApplicationController
private
+ def filter_folders(template_folder)
+ template_folders =
+ template_folder.subfolders.where(id: Template.accessible_by(current_ability).active.select(:folder_id))
+
+ TemplateFolders.search(template_folders, params[:q])
+ end
+
+ def render_infinite_scroll(pagy, templates, template_folders)
+ rendering =
+ if templates.present?
+ { partial: 'templates/template', collection: templates }
+ else
+ { partial: 'template_folders/folder', collection: template_folders, as: :folder }
+ end
+
+ render turbo_stream: [
+ turbo_stream.before('infinite_scroll', **rendering),
+ turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: })
+ ]
+ end
+
def selected_order
@selected_order ||=
if can?(:manage, :countless)
diff --git a/app/controllers/templates_archived_controller.rb b/app/controllers/templates_archived_controller.rb
index 84d40074b..7dd078b1e 100644
--- a/app/controllers/templates_archived_controller.rb
+++ b/app/controllers/templates_archived_controller.rb
@@ -12,13 +12,23 @@ class TemplatesArchivedController < ApplicationController
@pagy, @templates = pagy_auto(@templates.select_for_list, limit: 12)
- return unless params[:q].present? && @templates.blank?
+ if params[:q].present? && @templates.blank?
+ @related_submissions_pagy, @related_submissions = load_related_submissions
+ end
- @related_submissions_pagy, @related_submissions = load_related_submissions
+ render_infinite_scroll(@pagy, @templates) if turbo_infinite_scroll?
end
private
+ def render_infinite_scroll(pagy, templates)
+ render turbo_stream: [
+ turbo_stream.before('infinite_scroll', partial: 'templates/template', collection: templates,
+ locals: { with_folder: true }),
+ turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: })
+ ]
+ end
+
def load_related_submissions
related_submissions =
Submission.accessible_by(current_ability)
diff --git a/app/controllers/templates_archived_submissions_controller.rb b/app/controllers/templates_archived_submissions_controller.rb
index 662590d08..10096268a 100644
--- a/app/controllers/templates_archived_submissions_controller.rb
+++ b/app/controllers/templates_archived_submissions_controller.rb
@@ -16,7 +16,19 @@ class TemplatesArchivedSubmissionsController < ApplicationController
end
@pagy, @submissions = pagy_auto(@submissions.select_for_list.preload(submitters: :start_form_submission_events))
+
+ render_infinite_scroll(@pagy, @submissions, @template) if turbo_infinite_scroll?
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
+
+ private
+
+ def render_infinite_scroll(pagy, submissions, template)
+ render turbo_stream: [
+ turbo_stream.before('infinite_scroll', partial: 'templates/submission', collection: submissions,
+ locals: { template:, archived: true }),
+ turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: })
+ ]
+ end
end
diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb
index c45fe6cdc..e8ff9ff4e 100644
--- a/app/controllers/templates_controller.rb
+++ b/app/controllers/templates_controller.rb
@@ -21,6 +21,8 @@ class TemplatesController < ApplicationController
@pagy, @submissions =
pagy_auto(submissions.select_for_list.preload(:template_accesses, submitters: :start_form_submission_events))
+
+ render_infinite_scroll(@pagy, @submissions, @template) if turbo_infinite_scroll?
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
@@ -86,6 +88,14 @@ class TemplatesController < ApplicationController
private
+ def render_infinite_scroll(pagy, submissions, template)
+ render turbo_stream: [
+ turbo_stream.before('infinite_scroll', partial: 'templates/submission', collection: submissions,
+ locals: { template:, archived: template.archived_at? }),
+ turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: })
+ ]
+ end
+
def template_params
params.require(:template).permit(
:name,
diff --git a/app/controllers/templates_dashboard_controller.rb b/app/controllers/templates_dashboard_controller.rb
index d9a683c95..b655caaca 100644
--- a/app/controllers/templates_dashboard_controller.rb
+++ b/app/controllers/templates_dashboard_controller.rb
@@ -43,10 +43,26 @@ class TemplatesDashboardController < ApplicationController
@related_submissions_pagy, @related_submissions = load_related_submissions
end
end
+
+ render_infinite_scroll(@pagy, @templates, @template_folders) if turbo_infinite_scroll?
end
private
+ def render_infinite_scroll(pagy, templates, template_folders)
+ rendering =
+ if templates.present?
+ { partial: 'templates/template', collection: templates }
+ else
+ { partial: 'template_folders/folder', collection: template_folders, as: :folder }
+ end
+
+ render turbo_stream: [
+ turbo_stream.before('infinite_scroll', **rendering),
+ turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: })
+ ]
+ end
+
def load_templates(templates, folders_count, show_shared_inline: false)
templates = templates.preload(:author, :template_accesses)
diff --git a/app/controllers/templates_shared_controller.rb b/app/controllers/templates_shared_controller.rb
index 8855d94de..178f49945 100644
--- a/app/controllers/templates_shared_controller.rb
+++ b/app/controllers/templates_shared_controller.rb
@@ -19,13 +19,22 @@ class TemplatesSharedController < ApplicationController
@pagy, @templates = pagy_auto(@templates.select_for_list, limit: 12)
- return unless params[:q].present? && @templates.blank?
+ if params[:q].present? && @templates.blank?
+ @related_submissions_pagy, @related_submissions = load_related_submissions(is_archived: @is_archived)
+ end
- @related_submissions_pagy, @related_submissions = load_related_submissions(is_archived: @is_archived)
+ render_infinite_scroll(@pagy, @templates) if turbo_infinite_scroll?
end
private
+ def render_infinite_scroll(pagy, templates)
+ render turbo_stream: [
+ turbo_stream.before('infinite_scroll', partial: 'templates/template', collection: templates),
+ turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: })
+ ]
+ end
+
def load_related_submissions(is_archived:)
shared_templates = Templates.shared(current_user)
shared_templates = is_archived ? shared_templates.archived : shared_templates.active
diff --git a/app/javascript/application.js b/app/javascript/application.js
index 4e3b73e3f..903220a14 100644
--- a/app/javascript/application.js
+++ b/app/javascript/application.js
@@ -13,6 +13,7 @@ import NativeAction from './elements/native_action'
import NativeSearch from './elements/native_search'
import NativeModal from './elements/native_modal'
import NativeEvent from './elements/native_event'
+import InfiniteScroll from './elements/infinite_scroll'
import ModalButton from './elements/modal_button'
import FileDropzone from './elements/file_dropzone'
import MenuActive from './elements/menu_active'
@@ -127,6 +128,7 @@ safeRegisterElement('native-action', NativeAction)
safeRegisterElement('native-search', NativeSearch)
safeRegisterElement('native-modal', NativeModal)
safeRegisterElement('native-event', NativeEvent)
+safeRegisterElement('infinite-scroll', InfiniteScroll)
safeRegisterElement('modal-button', ModalButton)
safeRegisterElement('file-dropzone', FileDropzone)
safeRegisterElement('menu-active', MenuActive)
diff --git a/app/javascript/elements/infinite_scroll.js b/app/javascript/elements/infinite_scroll.js
new file mode 100644
index 000000000..1e0ecee92
--- /dev/null
+++ b/app/javascript/elements/infinite_scroll.js
@@ -0,0 +1,41 @@
+import { renderStreamMessage } from '@hotwired/turbo'
+
+export default class extends HTMLElement {
+ connectedCallback () {
+ if (!this.dataset.src) {
+ this.remove()
+
+ return
+ }
+
+ this.observer = new IntersectionObserver((entries) => {
+ if (entries.some((entry) => entry.isIntersecting)) {
+ this.load()
+ }
+ }, { rootMargin: '400px' })
+
+ this.observer.observe(this)
+ }
+
+ disconnectedCallback () {
+ this.observer?.disconnect()
+ }
+
+ load () {
+ if (this.loading || !this.dataset.src) return
+
+ this.loading = true
+
+ fetch(this.dataset.src, {
+ headers: { 'X-Turbo-Infinite-Scroll': 'true' }
+ }).then(async (response) => {
+ if (!response.ok) return
+
+ delete this.dataset.src
+
+ renderStreamMessage(await response.text())
+ }).finally(() => {
+ this.loading = false
+ })
+ }
+}
diff --git a/app/views/shared/_infinite_scroll.html.erb b/app/views/shared/_infinite_scroll.html.erb
new file mode 100644
index 000000000..09b7fbe7a
--- /dev/null
+++ b/app/views/shared/_infinite_scroll.html.erb
@@ -0,0 +1,5 @@
+<% if pagy.next || pagy.page > 1 %>
+