<%= render_primerized_flash %>
@@ -134,8 +134,7 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
<%= content_tag :div,
id: 'content',
- class: "#{initial_classes} #{'content--split' if content_for?(:content_body_right)}",
- data: stimulus_content_data do %>
+ class: "#{initial_classes} #{'content--split' if content_for?(:content_body_right)}" do %>
diff --git a/docs/development/concepts/stimulus/README.md b/docs/development/concepts/stimulus/README.md
index af7199d220ff..5bb9b78b1c50 100644
--- a/docs/development/concepts/stimulus/README.md
+++ b/docs/development/concepts/stimulus/README.md
@@ -44,7 +44,7 @@ You need to take care to prefix all actions, values etc. with the exact same pat
### Requiring a page controller
-If you have a single controller used in a partial, we have added a helper to use in a partial in order to append a controller to the `#content`tag. This is useful if your template doesn't have a single DOM root. For example, to load the dynamic `project-storage-form` controller and provide a custom value to it:
+If you have a single controller used in a partial, we have added a helper to use in a partial in order to append a controller to the `#content-wrapper` tag. This is useful if your template doesn't have a single DOM root. For example, to load the dynamic `project-storage-form` controller and provide a custom value to it:
```erb
<% content_controller 'project-storage-form',
diff --git a/frontend/src/stimulus/controllers/poll-for-changes.controller.ts b/frontend/src/stimulus/controllers/poll-for-changes.controller.ts
index b7b9407b5823..811dc91926fd 100644
--- a/frontend/src/stimulus/controllers/poll-for-changes.controller.ts
+++ b/frontend/src/stimulus/controllers/poll-for-changes.controller.ts
@@ -36,11 +36,17 @@ export default class PollForChangesController extends ApplicationController {
url: String,
interval: Number,
reference: String,
+ autoscrollEnabled: Boolean,
};
+ static targets = ['reloadButton'];
+
+ declare reloadButtonTarget:HTMLLinkElement;
+
declare referenceValue:string;
declare urlValue:string;
declare intervalValue:number;
+ declare autoscrollEnabledValue:boolean;
private interval:number;
@@ -52,6 +58,10 @@ export default class PollForChangesController extends ApplicationController {
void this.triggerTurboStream();
}, this.intervalValue || 10_000);
}
+
+ if (this.autoscrollEnabledValue) {
+ window.addEventListener('DOMContentLoaded', this.autoscrollToLastKnownPosition.bind(this));
+ }
}
disconnect() {
@@ -59,6 +69,10 @@ export default class PollForChangesController extends ApplicationController {
clearInterval(this.interval);
}
+ reloadButtonTargetConnected() {
+ this.reloadButtonTarget.addEventListener('click', this.rememberCurrentScrollPosition.bind(this));
+ }
+
triggerTurboStream() {
void fetch(`${this.urlValue}?reference=${this.referenceValue}`, {
headers: {
@@ -73,4 +87,29 @@ export default class PollForChangesController extends ApplicationController {
}
});
}
+
+ rememberCurrentScrollPosition() {
+ const currentPosition = document.getElementById('content-body')?.scrollTop;
+
+ if (currentPosition !== undefined) {
+ sessionStorage.setItem(this.scrollPositionKey(), currentPosition.toString());
+ }
+ }
+
+ autoscrollToLastKnownPosition() {
+ const lastKnownPos = sessionStorage.getItem(this.scrollPositionKey());
+ if (lastKnownPos) {
+ const content = document.getElementById('content-body');
+
+ if (content) {
+ content.scrollTop = parseInt(lastKnownPos, 10);
+ }
+ }
+
+ sessionStorage.removeItem(this.scrollPositionKey());
+ }
+
+ private scrollPositionKey():string {
+ return `${this.urlValue}/scrollPosition`;
+ }
}
diff --git a/modules/meeting/app/components/meetings/header_component.html.erb b/modules/meeting/app/components/meetings/header_component.html.erb
index 13a451beba3b..c27700bbe91c 100644
--- a/modules/meeting/app/components/meetings/header_component.html.erb
+++ b/modules/meeting/app/components/meetings/header_component.html.erb
@@ -1,16 +1,14 @@
<%=
- params = {
- controller: "poll-for-changes",
- poll_for_changes_reference_value: @meeting.changed_hash,
- poll_for_changes_url_value: check_for_updates_meeting_path(@meeting),
- poll_for_changes_interval_value: check_for_updates_interval
- }
+ helpers.content_controller "poll-for-changes",
+ poll_for_changes_reference_value: @meeting.changed_hash,
+ poll_for_changes_url_value: check_for_updates_meeting_path(@meeting),
+ poll_for_changes_interval_value: check_for_updates_interval,
+ poll_for_changes_autoscroll_enabled_value: true
component_wrapper do
render(Primer::OpenProject::PageHeader.new(
test_selector: "meeting-page-header",
- state: @state,
- data: params
+ state: @state
)) do |header|
header.with_title do |title|
title.with_editable_form(model: @meeting,
diff --git a/modules/meeting/app/components/meetings/update_flash_component.rb b/modules/meeting/app/components/meetings/update_flash_component.rb
index 31c991ed513f..0b25370a52e3 100644
--- a/modules/meeting/app/components/meetings/update_flash_component.rb
+++ b/modules/meeting/app/components/meetings/update_flash_component.rb
@@ -43,7 +43,7 @@ def call
banner.with_action_button(
tag: :a,
href: helpers.meeting_path(meeting),
- data: { turbo: false },
+ data: { turbo: false, poll_for_changes_target: "reloadButton" },
size: :medium
) { I18n.t("label_meeting_reload") }
diff --git a/modules/meeting/spec/support/pages/structured_meeting/show.rb b/modules/meeting/spec/support/pages/structured_meeting/show.rb
index 10a9367420d4..34e531761a98 100644
--- a/modules/meeting/spec/support/pages/structured_meeting/show.rb
+++ b/modules/meeting/spec/support/pages/structured_meeting/show.rb
@@ -43,7 +43,7 @@ def trigger_dropdown_menu_item(name)
def trigger_change_poll
script = <<~JS
- var target = document.querySelector('[data-test-selector="meeting-page-header"]');
+ var target = document.querySelector('#content-wrapper');
var controller = window.Stimulus.getControllerForElementAndIdentifier(target, 'poll-for-changes')
controller.triggerTurboStream();
JS
<%= t(:label_content) %>
<% if content_for?(:content_header) %>
@@ -162,7 +161,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= content_for :content_body_right %>
<% end %>
<% end %>
-
+ <% end %>