Merge pull request #23139 from opf/merge-release/17.4-20260508131130

Merge release/17.4 into dev
This commit is contained in:
Alexander Brandon Coles
2026-05-08 16:33:33 +01:00
committed by GitHub
10 changed files with 15 additions and 57 deletions
@@ -297,7 +297,7 @@ module OpenProject
{
# Existing callers share one mirror container target on the page until
# parent-specific DnD handling is extracted in follow-up work.
generic_drag_and_drop_target: "container mirrorContainer",
generic_drag_and_drop_target: "container",
target_container_accessor: ":scope > ul",
target_id: drag_and_drop.fetch(:target_id),
target_allowed_drag_type: drag_and_drop.fetch(:allowed_drag_type)
+9 -1
View File
@@ -89,7 +89,15 @@ Click **Continue** to proceed.
### Define project details
Next, enter the **name** and an optional **description** for your project. You can also integrate the project into your existing project hierarchy by selecting a **parent project**, which will make the new project a **subproject**.
Next, enter the **name** for your project.
Based on the project name, OpenProject automatically suggests an **identifier**. You can edit the suggested identifier manually if needed. The identifier is validated automatically to ensure it follows the required rules.
> [!NOTE]
> The identifier suggestion updates automatically when you change the project name.
> If you manually edit the identifier and later go back to change the project name again, OpenProject updates the identifier suggestion once more.
You can also optionally enter a **description** for your project. You can also integrate the project into your existing project hierarchy by selecting a **parent project**, which will make the new project a **subproject**.
Click **Complete** to finish the setup.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 42 KiB

@@ -63,12 +63,6 @@ describe('GenericDragAndDropController', () => {
return ariaPressedTarget.call(controller, el);
}
function callResolveMirrorContainer():Element {
const resolveMirrorContainer = Reflect.get(controller, 'resolveMirrorContainer') as (this:GenericDragAndDropController) => Element;
return resolveMirrorContainer.call(controller);
}
describe('canStartDrag', () => {
it('allows dragging a draggable row in handle-less mode', () => {
const row = draggableRow();
@@ -146,21 +140,4 @@ describe('GenericDragAndDropController', () => {
expect(callAriaPressedTarget(row)).toBe(handle);
});
});
describe('resolveMirrorContainer', () => {
it('returns the configured mirror container target when present', () => {
const mirrorContainer = document.createElement('div');
Object.defineProperty(controller, 'hasMirrorContainerTarget', { value: true, configurable: true });
Object.defineProperty(controller, 'mirrorContainerTarget', { value: mirrorContainer, configurable: true });
expect(callResolveMirrorContainer()).toBe(mirrorContainer);
});
it('falls back to document.body when no mirror container target exists', () => {
Object.defineProperty(controller, 'hasMirrorContainerTarget', { value: false, configurable: true });
expect(callResolveMirrorContainer()).toBe(document.body);
});
});
});
@@ -43,12 +43,10 @@ interface TargetConfig {
}
export default class GenericDragAndDropController extends Controller {
static targets = ['container', 'scrollContainer', 'mirrorContainer'];
static targets = ['container', 'scrollContainer'];
containerTargets:HTMLElement[];
scrollContainerTargets:HTMLElement[];
declare readonly hasMirrorContainerTarget:boolean;
declare readonly mirrorContainerTarget:HTMLElement;
static values = {
handle: { type: Boolean, default: true },
@@ -129,7 +127,6 @@ export default class GenericDragAndDropController extends Controller {
moves: (el, _source, handle, _sibling) => this.canStartDrag(el, handle),
accepts: (el:Element, target:Element, source:Element, sibling:Element) => this.accepts(el, target, source, sibling),
revertOnSpill: true, // enable reverting of elements if they are dropped outside of a valid target
mirrorContainer: this.resolveMirrorContainer(),
},
)
.on('cloned', (clone, _original, type) => {
@@ -260,10 +257,6 @@ export default class GenericDragAndDropController extends Controller {
return container;
}
private resolveMirrorContainer():Element {
return this.hasMirrorContainerTarget ? this.mirrorContainerTarget : document.body;
}
// Returns the data-draggable-id of the element preceding el in its container,
// or null if el is the first item (signals "move to top").
private resolveTargetPrevious(el:Element):string|null {
@@ -119,7 +119,7 @@ RSpec.describe Backlogs::BucketComponent, type: :component do
it "wires the bucket drop-target data on the box" do
expect(rendered_component).to have_css(".Box") do |box|
expect(box["data-generic-drag-and-drop-target"]).to eq("container mirrorContainer")
expect(box["data-generic-drag-and-drop-target"]).to eq("container")
expect(box["data-target-id"]).to eq("backlog_bucket:#{backlog_bucket.id}")
expect(box["data-target-allowed-drag-type"]).to eq("story")
end
@@ -64,7 +64,7 @@ RSpec.describe Backlogs::InboxComponent, type: :component do
it "wires drop-target data attributes for the inbox" do
expect(page).to have_css(".Box#inbox_project_#{project.id}") do |box|
expect(box["data-generic-drag-and-drop-target"]).to eq("container mirrorContainer")
expect(box["data-generic-drag-and-drop-target"]).to eq("container")
expect(box["data-target-id"]).to eq("inbox")
expect(box["data-target-allowed-drag-type"]).to eq("story")
end
@@ -94,7 +94,7 @@ RSpec.describe Backlogs::SprintComponent, type: :component do
it "wires drop-target data attributes for the sprint" do
expect(rendered_component).to have_css(".Box") do |box|
expect(box["data-generic-drag-and-drop-target"]).to eq("container mirrorContainer")
expect(box["data-generic-drag-and-drop-target"]).to eq("container")
expect(box["data-target-container-accessor"]).to eq(":scope > ul")
expect(box["data-target-id"]).to eq("sprint:#{sprint.id}")
expect(box["data-target-allowed-drag-type"]).to eq("story")
@@ -106,26 +106,6 @@ RSpec.describe "Backlogs::Backlog", :skip_csrf, type: :rails_request do
expect(response.body).to include('id="sprint_backlogs_container"')
end
end
it "uses the inbox border box as the drag mirror container" do
get "/projects/#{project.identifier}/backlogs/backlog", headers: { "Turbo-Frame" => "backlogs_container" }
expect(response).to have_http_status(:ok)
expect(response.body).to include(%(id="inbox_project_#{project.id}"))
expect(response.body).to include('data-generic-drag-and-drop-target="container mirrorContainer"')
end
context "with backlog buckets enabled", with_flag: { backlog_buckets: true } do
shared_let(:backlog_bucket) { create(:backlog_bucket, project:) }
it "uses each backlog bucket border box as the drag mirror container" do
get "/projects/#{project.identifier}/backlogs/backlog", headers: { "Turbo-Frame" => "backlogs_container" }
expect(response).to have_http_status(:ok)
expect(response.body).to include(%(data-test-selector="backlog-bucket-#{backlog_bucket.id}"))
expect(response.body).to include('data-generic-drag-and-drop-target="container mirrorContainer"')
end
end
end
end
@@ -165,7 +165,7 @@ RSpec.describe OpenProject::Common::WorkPackageCardBoxComponent, type: :componen
it "uses the configured drag-and-drop data" do
expect(rendered_component).to have_css(".Box") do |box|
expect(box["data-generic-drag-and-drop-target"]).to eq("container mirrorContainer")
expect(box["data-generic-drag-and-drop-target"]).to eq("container")
expect(box["data-target-container-accessor"]).to eq(":scope > ul")
expect(box["data-target-id"]).to eq("sprint:#{sprint.id}")
expect(box["data-target-allowed-drag-type"]).to eq("story")