Set up POC for infinite scrolling on AT (Messy Commit!)

This commit is contained in:
Kabiru Mwenja
2025-09-18 19:10:23 +03:00
parent 874bdebeb7
commit 2468d4d98a
14 changed files with 375 additions and 210 deletions
@@ -1,50 +1,33 @@
<%=
if deferred
render(
WorkPackages::ActivitiesTab::Journals::IndexComponent.new(work_package:, filter:, deferred:)
)
else
component_wrapper(tag: "turbo-frame") do
flex_layout(classes: "work-packages-activities-tab-index-component") do |activities_tab_wrapper_container|
activities_tab_wrapper_container.with_row(classes: "work-packages-activities-tab-index-component--errors") do
render(
WorkPackages::ActivitiesTab::ErrorStreamComponent.new
)
end
component_wrapper(tag: "turbo-frame") do
flex_layout(classes: "work-packages-activities-tab-index-component") do |activities_tab_wrapper_container|
activities_tab_wrapper_container.with_row(classes: "work-packages-activities-tab-index-component--errors") do
render(error_stream_component)
end
activities_tab_wrapper_container.with_row(id: index_content_wrapper_key, data: wrapper_data_attributes) do
flex_layout do |activities_tab_container|
activities_tab_container.with_row(mb: 2) do
render(
WorkPackages::ActivitiesTab::Journals::FilterAndSortingComponent.new(
work_package:,
filter:
)
)
activities_tab_wrapper_container.with_row(id: index_content_wrapper_key, data: wrapper_data_attributes) do
flex_layout do |activities_tab_container|
activities_tab_container.with_row(mb: 2) do
render(filter_and_sorting_component)
end
activities_tab_container.with_row(flex_layout: true, classes: "work-packages-activities-tab-index-component--content-container", mt: 3) do |journals_wrapper_container|
journals_wrapper_container.with_row(
classes: "work-packages-activities-tab-index-component--journals-container work-packages-activities-tab-index-component--journals-container_with-initial-input-compensation",
data: { "work-packages--activities-tab--index-target": "journalsContainer" }
) do
render(list_journals_component)
end
activities_tab_container.with_row(flex_layout: true, classes: "work-packages-activities-tab-index-component--content-container", mt: 3) do |journals_wrapper_container|
if adding_comment_allowed?
journals_wrapper_container.with_row(
classes: "work-packages-activities-tab-index-component--journals-container work-packages-activities-tab-index-component--journals-container_with-initial-input-compensation",
data: { "work-packages--activities-tab--index-target": "journalsContainer" }
id: add_comment_wrapper_key,
classes: "work-packages-activities-tab-index-component--input-container work-packages-activities-tab-index-component--input-container_sort-#{journal_sorting}",
p: 3,
bg: :subtle,
data: add_comment_wrapper_data_attributes
) do
render(
WorkPackages::ActivitiesTab::Journals::IndexComponent.new(work_package:, filter:)
)
end
if adding_comment_allowed?
journals_wrapper_container.with_row(
id: add_comment_wrapper_key,
classes: "work-packages-activities-tab-index-component--input-container work-packages-activities-tab-index-component--input-container_sort-#{journal_sorting}",
p: 3,
bg: :subtle,
data: add_comment_wrapper_data_attributes
) do
render(
WorkPackages::ActivitiesTab::Journals::NewComponent.new(work_package:, filter:, last_server_timestamp:)
)
end
render(add_journal_component)
end
end
end
@@ -37,13 +37,13 @@ module WorkPackages
include WorkPackages::ActivitiesTab::SharedHelpers
include WorkPackages::ActivitiesTab::StimulusControllers
def initialize(work_package:, last_server_timestamp:, filter: :all, deferred: false)
def initialize(work_package:, journals:, last_server_timestamp:, filter: :all)
super
@work_package = work_package
@journals = journals
@filter = filter
@last_server_timestamp = last_server_timestamp
@deferred = deferred
end
def self.wrapper_key = "work-package-activities-tab-content"
@@ -51,9 +51,25 @@ module WorkPackages
def self.add_comment_wrapper_key = "work-packages-activities-tab-add-comment-component"
delegate :index_content_wrapper_key, :add_comment_wrapper_key, to: :class
def filter_and_sorting_component
WorkPackages::ActivitiesTab::Journals::FilterAndSortingComponent.new(work_package:, filter:)
end
def list_journals_component
WorkPackages::ActivitiesTab::Journals::IndexComponent.new(work_package:, journals:, filter:)
end
def add_journal_component
WorkPackages::ActivitiesTab::Journals::NewComponent.new(work_package:, filter:, last_server_timestamp:)
end
def error_stream_component
WorkPackages::ActivitiesTab::ErrorStreamComponent.new
end
private
attr_reader :work_package, :filter, :last_server_timestamp, :deferred
attr_reader :work_package, :journals, :filter, :last_server_timestamp
def wrapper_data_attributes # rubocop:disable Metrics/AbcSize
stimulus_controllers = {
@@ -1,75 +1,41 @@
<%=
if deferred
helpers.turbo_frame_tag("work-package-activities-tab-content-older-journals") do
flex_layout do |older_journals_container|
older_journals.each do |record|
older_journals_container.with_row do
if record.is_a?(Changeset)
render(WorkPackages::ActivitiesTab::Journals::RevisionComponent.new(changeset: record, filter:))
else
render(
WorkPackages::ActivitiesTab::Journals::ItemComponent.new(
journal: record, filter:,
grouped_emoji_reactions: wp_journals_grouped_emoji_reactions[record.id]
)
)
component_wrapper(class: "work-packages-activities-tab-journals-index-component") do
flex_layout(data: { test_selector: "op-wp-journals-#{filter}-#{journal_sorting}" }) do |journals_index_wrapper_container|
journals_index_wrapper_container.with_row(
classes: "work-packages-activities-tab-journals-index-component--journals-inner-container",
mb: inner_container_margin_bottom
) do
flex_layout(
id: insert_target_modifier_id,
data: { test_selector: "op-wp-journals-container" }
) do |journals_index_container|
if empty_state?
journals_index_container.with_row(mt: 2, mb: 3) do
render(WorkPackages::ActivitiesTab::Journals::EmptyComponent.new)
end
end
if journal_sorting_asc?
journals_index_container.with_row do
render(infinite_scroll_component)
end
end
journals_index_container.with_row do
render(page_component)
end
if journal_sorting_desc?
journals_index_container.with_row do
render(infinite_scroll_component)
end
end
end
end
end
else
component_wrapper(class: "work-packages-activities-tab-journals-index-component") do
flex_layout(data: { test_selector: "op-wp-journals-#{filter}-#{journal_sorting}" }) do |journals_index_wrapper_container|
journals_index_wrapper_container.with_row(
classes: "work-packages-activities-tab-journals-index-component--journals-inner-container",
mb: inner_container_margin_bottom
) do
flex_layout(
id: insert_target_modifier_id,
data: { test_selector: "op-wp-journals-container" }
) do |journals_index_container|
if empty_state?
journals_index_container.with_row(mt: 2, mb: 3) do
render(
WorkPackages::ActivitiesTab::Journals::EmptyComponent.new
)
end
end
if !journal_sorting_desc? && journals.count > MAX_RECENT_JOURNALS
journals_index_container.with_row do
helpers.turbo_frame_tag("work-package-activities-tab-content-older-journals", src: work_package_activities_path(work_package, filter:, deferred: true))
end
end
recent_journals.each do |record|
journals_index_container.with_row do
if record.is_a?(Changeset)
render(WorkPackages::ActivitiesTab::Journals::RevisionComponent.new(changeset: record, filter:))
else
render(
WorkPackages::ActivitiesTab::Journals::ItemComponent.new(
journal: record, filter:,
grouped_emoji_reactions: wp_journals_grouped_emoji_reactions[record.id]
)
)
end
end
end
if journal_sorting_desc? && journals.count > MAX_RECENT_JOURNALS
journals_index_container.with_row do
helpers.turbo_frame_tag("work-package-activities-tab-content-older-journals", src: work_package_activities_path(work_package, filter:, deferred: true))
end
end
end
end
unless empty_state?
journals_index_wrapper_container
.with_row(classes: "work-packages-activities-tab-journals-index-component--stem-connection")
end
unless empty_state?
journals_index_wrapper_container
.with_row(classes: "work-packages-activities-tab-journals-index-component--stem-connection")
end
end
end
@@ -32,99 +32,46 @@ module WorkPackages
module ActivitiesTab
module Journals
class IndexComponent < ApplicationComponent
MAX_RECENT_JOURNALS = 30
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
include WorkPackages::ActivitiesTab::SharedHelpers
def initialize(work_package:, filter: :all, deferred: false)
def initialize(work_package:, journals:, filter: :all)
super
@work_package = work_package
@journals = journals
@filter = filter
@deferred = deferred
end
def infinite_scroll_component
WorkPackages::ActivitiesTab::Journals::InfiniteScrollComponent.new(work_package:)
end
def page_component
WorkPackages::ActivitiesTab::Journals::PageComponent.new(journals:, emoji_reactions:, page: 1, filter:)
end
def self.insert_target_modifier_id = "work-package-journal-days"
delegate :insert_target_modifier_id, to: :class
private
attr_reader :work_package, :filter, :deferred
attr_reader :work_package, :journals, :filter
def insert_target_modified?
true
end
def insert_target_modifier_id
"work-package-journal-days"
end
def journal_sorting_desc?
journal_sorting == "desc"
end
def base_journals
combine_and_sort_records(fetch_journals, fetch_revisions)
end
def fetch_journals
API::V3::Activities::ActivityEagerLoadingWrapper.wrap(
work_package
.journals
.internal_visible
.includes(:user, :customizable_journals, :attachable_journals, :storable_journals, :notifications)
.reorder(version: journal_sorting)
.with_sequence_version
)
end
def fetch_revisions
work_package.changesets.includes(:user, :repository)
end
def combine_and_sort_records(journals, revisions)
(journals + revisions).sort_by do |record|
timestamp = record_timestamp(record)
journal_sorting_desc? ? [-timestamp, -record.id] : [timestamp, record.id]
end
end
def record_timestamp(record)
if record.is_a?(API::V3::Activities::ActivityEagerLoadingWrapper)
record.created_at&.to_i
elsif record.is_a?(Changeset)
record.committed_on.to_i
end
end
def journals
base_journals
end
def recent_journals
if journal_sorting_desc?
base_journals.first(MAX_RECENT_JOURNALS)
else
base_journals.last(MAX_RECENT_JOURNALS)
end
end
def older_journals
if journal_sorting_desc?
base_journals.drop(MAX_RECENT_JOURNALS)
else
base_journals.take(base_journals.size - MAX_RECENT_JOURNALS)
end
end
def journal_with_notes
work_package
.journals
.where.not(notes: "")
end
def wp_journals_grouped_emoji_reactions
@wp_journals_grouped_emoji_reactions ||=
def emoji_reactions
@emoji_reactions ||=
EmojiReactions::GroupedQueries.grouped_work_package_journals_emoji_reactions_by_reactable(work_package)
end
@@ -0,0 +1,25 @@
<%=
component_wrapper(
data: {
controller: "infinite-scroll",
infinite_scroll_insert_target_id_value: insert_target_id,
infinite_scroll_page_url_value: page_streams_url,
infinite_scroll_current_page_value: 1, # initial page
infinite_scroll_is_last_page_value: false # initial value
}
) do
flex_layout(
data: { infinite_scroll_target: "skeleton" },
class: "work-packages-activities-tab-journals-item-component",
bg: :default
) do |skeleton|
skeleton.with_row(mb: 2) do
render(Primer::Alpha::SkeletonBox.new(width: "25%", height: "40px"))
end
skeleton.with_row(mb: 3) do
render(Primer::Alpha::SkeletonBox.new(width: "100%", height: "150px"))
end
end
end
%>
@@ -0,0 +1,57 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackages
module ActivitiesTab
module Journals
class InfiniteScrollComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
def initialize(work_package:)
super
@work_package = work_package
end
private
attr_reader :work_package
def insert_target_id
WorkPackages::ActivitiesTab::Journals::IndexComponent.insert_target_modifier_id
end
def page_streams_url
page_streams_work_package_activities_path(work_package, format: :turbo_stream)
end
end
end
end
end
@@ -0,0 +1,20 @@
<%=
component_wrapper do
flex_layout do |flex_container|
journals.each do |record|
flex_container.with_row do
if record.is_a?(Changeset)
render(WorkPackages::ActivitiesTab::Journals::RevisionComponent.new(changeset: record, filter:))
else
render(
WorkPackages::ActivitiesTab::Journals::ItemComponent.new(
journal: record, filter:,
grouped_emoji_reactions: emoji_reactions.fetch(record.id, {})
)
)
end
end
end
end
end
%>
@@ -0,0 +1,62 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackages
module ActivitiesTab
module Journals
class PageComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
include WorkPackages::ActivitiesTab::SharedHelpers
def initialize(journals:, emoji_reactions:, page:, filter: :all)
super
@journals = journals
@emoji_reactions = emoji_reactions
@filter = filter
@page = page
end
def render?
journals.any?
end
def wrapper_uniq_by
page
end
private
attr_reader :journals, :emoji_reactions, :page, :filter
end
end
end
end
@@ -61,6 +61,14 @@ module WorkPackages
end
end
def journal_sorting_asc?
journal_sorting == "asc"
end
def journal_sorting_desc?
journal_sorting == "desc"
end
def journal_sorting
User.current.preference&.comments_sorting || OpenProject::Configuration.default_comment_sort_order
end
@@ -71,6 +71,15 @@ module OpTurbo
)
end
def insert_via_turbo_stream(action:, component:, target_component:)
case action
when :append
append_via_turbo_stream(component:, target_component:)
when :prepend
prepend_via_turbo_stream(component:, target_component:)
end
end
def append_via_turbo_stream(component:, target_component:)
turbo_streams << target_component.insert_as_turbo_stream(component:, view_context:, action: :append)
end
@@ -104,6 +113,14 @@ module OpTurbo
turbo_streams << instance.render_as_turbo_stream(view_context:, action: :flash)
end
def set_dataset_attributes_via_turbo_stream(target, **attributes)
attributes.each do |attribute, value|
turbo_streams << OpTurbo::StreamComponent
.new(action: :set_dataset_attribute, target:, attribute:, value:)
.render_in(view_context)
end
end
def scroll_into_view_via_turbo_stream(target, behavior: :auto, block: :start)
turbo_streams << OpTurbo::StreamComponent
.new(action: :scroll_into_view, target:, behavior:, block:)
@@ -29,6 +29,7 @@
# ++
class WorkPackages::ActivitiesTabController < ApplicationController
include Pagy::Backend
include OpTurbo::ComponentStream
include FlashMessagesOutputSafetyHelper
@@ -37,19 +38,44 @@ class WorkPackages::ActivitiesTabController < ApplicationController
before_action :find_journal, only: %i[edit cancel_edit update toggle_reaction]
before_action :set_filter
before_action :authorize
before_action :initialize_pagination, only: %i[index page_streams update_filter update_sorting]
def index
render(
WorkPackages::ActivitiesTab::IndexComponent.new(
work_package: @work_package,
journals: @paginated_journals,
filter: @filter,
last_server_timestamp: get_current_server_timestamp,
deferred: ActiveRecord::Type::Boolean.new.cast(params[:deferred])
last_server_timestamp: get_current_server_timestamp
),
layout: false
)
end
def page_streams
insert_via_turbo_stream(
target_component: WorkPackages::ActivitiesTab::Journals::IndexComponent.new(
work_package: @work_package,
journals: Journal.none # we do not need to pass any journals here since we just want the component key
),
component: WorkPackages::ActivitiesTab::Journals::PageComponent.new(
journals: @paginated_journals,
emoji_reactions: wp_journals_emoji_reactions,
page: @paginator.page,
filter: @filter
),
action: journal_sorting.asc? ? :append : :prepend
)
set_dataset_attributes_via_turbo_stream(
WorkPackages::ActivitiesTab::Journals::InfiniteScrollComponent.wrapper_key,
infinite_scroll_current_page_value: @paginator.page,
infinite_scroll_is_last_page_value: @paginator.next.blank?
)
respond_with_turbo_streams
end
def update_streams
set_last_server_timestamp_to_headers
@@ -173,6 +199,48 @@ class WorkPackages::ActivitiesTabController < ApplicationController
private
def initialize_pagination
@paginator, @paginated_journals = pagy_array(base_journals, items: 30)
# For UI display: if user wants "oldest first" UI, reverse the array
@paginated_journals = @paginated_journals.reverse if journal_sorting.asc?
end
def base_journals
combine_and_sort_records(fetch_journals, fetch_revisions)
end
def fetch_journals
API::V3::Activities::ActivityEagerLoadingWrapper.wrap(fetch_ar_journals)
end
def fetch_ar_journals
@work_package
.journals
.internal_visible
.includes(:user, :customizable_journals, :attachable_journals, :storable_journals, :notifications)
.reorder(version: :desc) # Always fetch newest first for pagination
.with_sequence_version
end
def fetch_revisions
@work_package.changesets.includes(:user, :repository)
end
def combine_and_sort_records(journals, revisions)
(journals + revisions).sort_by do |record|
timestamp = record_timestamp(record)
[-timestamp, -record.id] # Always sort DESC (newest first)
end
end
def record_timestamp(record)
if record.is_a?(API::V3::Activities::ActivityEagerLoadingWrapper)
record.created_at&.to_i
elsif record.is_a?(Changeset)
record.committed_on.to_i
end
end
def respond_with_error(error_message)
respond_to do |format|
# turbo_frame requests (tab is initially rendered and an error occured) are handled below
@@ -218,7 +286,8 @@ class WorkPackages::ActivitiesTabController < ApplicationController
end
def journal_sorting
User.current.preference&.comments_sorting || OpenProject::Configuration.default_comment_sort_order
ActiveSupport::StringInquirer
.new(User.current.preference&.comments_sorting || OpenProject::Configuration.default_comment_sort_order)
end
def sanitized_journal_notes
@@ -287,6 +356,7 @@ class WorkPackages::ActivitiesTabController < ApplicationController
replace_via_turbo_stream(
component: WorkPackages::ActivitiesTab::IndexComponent.new(
work_package: @work_package,
journals: @paginated_journals,
filter: @filter,
last_server_timestamp: get_current_server_timestamp
)
@@ -297,6 +367,7 @@ class WorkPackages::ActivitiesTabController < ApplicationController
update_via_turbo_stream(
component: WorkPackages::ActivitiesTab::Journals::IndexComponent.new(
work_package: @work_package,
journals: @paginated_journals,
filter: @filter
)
)
@@ -339,7 +410,7 @@ class WorkPackages::ActivitiesTabController < ApplicationController
rerender_updated_journals(journals, last_update_timestamp, grouped_emoji_reactions, editing_journals)
rerender_journals_with_updated_notification(journals, last_update_timestamp, grouped_emoji_reactions, editing_journals)
append_or_prepend_journals(journals, last_update_timestamp, grouped_emoji_reactions)
insert_latest_journals_via_turbo_stream(journals, last_update_timestamp, grouped_emoji_reactions)
if journals.present?
remove_potential_empty_state
@@ -348,13 +419,11 @@ class WorkPackages::ActivitiesTabController < ApplicationController
end
def generate_work_package_journals_emoji_reactions_update_streams
wp_journal_emoji_reactions =
EmojiReactions::GroupedQueries.grouped_work_package_journals_emoji_reactions_by_reactable(@work_package)
@work_package.journals.each do |journal|
update_via_turbo_stream(
component: WorkPackages::ActivitiesTab::Journals::ItemComponent::Reactions.new(
journal:,
grouped_emoji_reactions: wp_journal_emoji_reactions[journal.id] || {}
grouped_emoji_reactions: wp_journals_emoji_reactions[journal.id] || {}
)
)
end
@@ -404,32 +473,21 @@ class WorkPackages::ActivitiesTabController < ApplicationController
end
end
def append_or_prepend_journals(journals, last_update_timestamp, grouped_emoji_reactions)
journals.where("created_at > ?", last_update_timestamp).find_each do |journal|
append_or_prepend_latest_journal_via_turbo_stream(journal, grouped_emoji_reactions.fetch(journal.id, {}))
end
end
def append_or_prepend_latest_journal_via_turbo_stream(journal, grouped_emoji_reactions)
def insert_latest_journals_via_turbo_stream(journals, last_update_timestamp, emoji_reactions)
target_component = WorkPackages::ActivitiesTab::Journals::IndexComponent.new(
work_package: @work_package,
journals: Journal.none, # we do not need to pass any journals here since we just want the component key
filter: @filter
)
component = WorkPackages::ActivitiesTab::Journals::ItemComponent.new(
journal:, filter: @filter, grouped_emoji_reactions:
)
stream_config = {
target_component:,
component:
}
# Append or prepend the new journal depending on the sorting
if journal_sorting == "asc"
append_via_turbo_stream(**stream_config)
else
prepend_via_turbo_stream(**stream_config)
journals.where("created_at > ?", last_update_timestamp).find_each do |journal|
insert_via_turbo_stream(
target_component:,
component: WorkPackages::ActivitiesTab::Journals::ItemComponent.new(
journal:, filter: @filter, grouped_emoji_reactions: emoji_reactions.fetch(journal.id, {})
),
action: journal_sorting.asc? ? :append : :prepend
)
end
end
@@ -467,6 +525,11 @@ class WorkPackages::ActivitiesTabController < ApplicationController
)
end
def wp_journals_emoji_reactions
@wp_journals_emoji_reactions ||= EmojiReactions::GroupedQueries
.grouped_work_package_journals_emoji_reactions_by_reactable(@work_package)
end
def grouped_emoji_reactions_for_journal
EmojiReactions::GroupedQueries
.grouped_emoji_reactions_by_reactable(reactable: @journal)[@journal.id]
+2 -2
View File
@@ -61,7 +61,7 @@
# Array extra: Paginate arrays efficiently, avoiding expensive array-wrapping and without overriding
# See https://ddnexus.github.io/pagy/docs/extras/array
# require 'pagy/extras/array'
require "pagy/extras/array"
# Calendar extra: Add pagination filtering by calendar time unit (year, quarter, month, week, day)
# See https://ddnexus.github.io/pagy/docs/extras/calendar
@@ -105,7 +105,7 @@
# Keyset extra: Paginate with the Pagy keyset pagination technique
# See https://ddnexus.github.io/pagy/docs/extras/keyset
# require 'pagy/extras/keyset'
require "pagy/extras/keyset"
# Meilisearch extra: Paginate `Meilisearch` result objects
# See https://ddnexus.github.io/pagy/docs/extras/meilisearch
+1 -1
View File
@@ -261,7 +261,7 @@ Rails.application.reloader.to_prepare do
work_packages: %i[show index show_conflict_flash_message share_upsell],
work_packages_api: [:get],
"work_packages/reports": %i[report report_details],
"work_packages/activities_tab": %i[index update_streams update_sorting update_filter],
"work_packages/activities_tab": %i[index page_streams update_streams update_sorting update_filter],
"work_packages/menus": %i[show],
"work_packages/hover_card": %i[show],
work_package_relations_tab: %i[index],
+1
View File
@@ -735,6 +735,7 @@ Rails.application.routes.draw do
end
collection do
get :page_streams
get :update_streams
get :update_filter # filter not persisted
put :update_sorting # sorting is persisted