From 795e506d0c40de7381c2363ba61b8397b84927ed Mon Sep 17 00:00:00 2001 From: MAEDA Go Date: Thu, 15 Jan 2026 21:13:46 +0900 Subject: [PATCH 2/4] Replace hard-coded `left`, `border-left`, and `top` CSS properties with logical properties to support RTL layout in Gantt --- app/views/gantts/_chart.html.erb | 28 ++++++++-------- lib/redmine/helpers/gantt.rb | 32 +++++++++--------- test/functional/gantts_controller_test.rb | 30 ++++++++--------- test/unit/lib/redmine/helpers/gantt_test.rb | 36 ++++++++++----------- 4 files changed, 63 insertions(+), 63 deletions(-) diff --git a/app/views/gantts/_chart.html.erb b/app/views/gantts/_chart.html.erb index 870096870..10667b656 100644 --- a/app/views/gantts/_chart.html.erb +++ b/app/views/gantts/_chart.html.erb @@ -119,7 +119,7 @@ <% months_height = (show_weeks ? header_height : header_height + g_height) %> <% gantt.months.times do %> <% width = (((month_f >> 1) - month_f) * zoom - 1).to_i %> - <% month_style = +"left: #{left}px;" %> + <% month_style = +"inset-inline-start: #{left}px;" %> <% month_style << "width: #{width}px;" %> <% month_style << "height: #{months_height}px;" %> <%= content_tag(:div, style: month_style, class: "gantt_hdr") do %> @@ -139,8 +139,8 @@ <% else %> <% week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1) %> <% width = (7 - gantt.date_from.cwday + 1) * zoom - 1 %> - <% gap_style = +"left: #{left}px;" %> - <% gap_style << "top: 19px;" %> + <% gap_style = +"inset-inline-start: #{left}px;" %> + <% gap_style << "inset-block-start: 19px;" %> <% gap_style << "width: #{width}px;" %> <% gap_style << "height: #{weeks_height}px;" %> <%= content_tag(:div, ' '.html_safe, style: gap_style, class: "gantt_hdr") %> @@ -148,8 +148,8 @@ <% end %> <% while week_f <= gantt.date_to %> <% width = ((week_f + 6 <= gantt.date_to) ? 7 * zoom - 1 : (gantt.date_to - week_f + 1) * zoom - 1).to_i %> - <% week_style = +"left: #{left}px;" %> - <% week_style << "top: 19px;" %> + <% week_style = +"inset-inline-start: #{left}px;" %> + <% week_style << "inset-block-start: 19px;" %> <% week_style << "width: #{width}px;" %> <% week_style << "height: #{weeks_height}px;" %> <%= content_tag(:div, style: week_style, class: "gantt_hdr") do %> @@ -169,8 +169,8 @@ <% day_num = gantt.date_from %> <% (gantt.date_to - gantt.date_from + 1).to_i.times do %> <% width = zoom - 1 %> - <% day_style = +"left:#{left}px;" %> - <% day_style << "top:37px;" %> + <% day_style = +"inset-inline-start:#{left}px;" %> + <% day_style << "inset-block-start:37px;" %> <% day_style << "width:#{width}px;" %> <% day_style << "height:#{days_height}px;" %> <% day_style << "font-size:0.7em;" %> @@ -192,8 +192,8 @@ <% days_top = (show_day_num ? 55 : 37) %> <% (gantt.date_from..gantt.date_to).each do |g_date| %> <% width = zoom - 1 %> - <% day_style = +"left: #{left}px;" %> - <% day_style << "top: #{days_top}px;" %> + <% day_style = +"inset-inline-start: #{left}px;" %> + <% day_style << "inset-block-start: #{days_top}px;" %> <% day_style << "width: #{width}px;" %> <% day_style << "height: #{days_height}px;" %> <% day_style << "font-size:0.7em;" %> @@ -215,18 +215,18 @@ <% today_left = (((User.current.today - gantt.date_from + 1) * zoom).floor - 1).to_i %> <% today_style = +"position: absolute;" %> <% today_style << "height: #{g_height}px;" %> - <% today_style << "top: #{headers_height + 1}px;" %> - <% today_style << "left: #{today_left}px;" %> + <% today_style << "inset-block-start: #{headers_height + 1}px;" %> + <% today_style << "inset-inline-start: #{today_left}px;" %> <% today_style << "width:10px;" %> - <% today_style << "border-left: 1px dashed red;" %> + <% today_style << "border-inline-start: 1px dashed red;" %> <%= content_tag(:div, ' '.html_safe, style: today_style, id: 'today_line') %> <% end %> <% draw_area_style = +"position: absolute;" draw_area_style << "height: #{g_height}px;" - draw_area_style << "top: #{headers_height + 1}px;" - draw_area_style << 'left: 0px;' + draw_area_style << "inset-block-start: #{headers_height + 1}px;" + draw_area_style << 'inset-inline-start: 0px;' draw_area_style << "width: #{g_width - 1}px;" %> <%= content_tag(:div, '', style: draw_area_style, id: "gantt_draw_area", data: {'gantt--chart-target': 'drawArea'}) %> diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 4df1f034d..50864fb7d 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -359,7 +359,7 @@ module Redmine data_options = {} data_options[:collapse_expand] = "issue-#{issue.id}" data_options[:number_of_rows] = number_of_rows - style = "position: absolute;top: #{options[:top]}px; font-size: 0.8em;" + style = "position: absolute;inset-block-start: #{options[:top]}px; font-size: 0.8em;" content = view.content_tag( :div, view.column_content(options[:column], issue), @@ -827,7 +827,7 @@ module Redmine params[:indent] += 18 end end - style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" + style = "position: absolute;inset-block-start:#{params[:top]}px;inset-inline-start:#{params[:indent]}px;" style += "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] tag_options[:style] = style output = view.content_tag(:div, content, tag_options) @@ -890,8 +890,8 @@ module Redmine if coords[:bar_start] && coords[:bar_end] width = coords[:bar_end] - coords[:bar_start] - 2 style = +"" - style << "top:#{params[:top]}px;" - style << "left:#{coords[:bar_start]}px;" + style << "inset-block-start:#{params[:top]}px;" + style << "inset-inline-start:#{coords[:bar_start]}px;" style << "width:#{width}px;" html_id = "task-todo-issue-#{object.id}" if object.is_a?(Issue) html_id = "task-todo-version-#{object.id}" if object.is_a?(Version) @@ -910,8 +910,8 @@ module Redmine if coords[:bar_late_end] width = coords[:bar_late_end] - coords[:bar_start] - 2 style = +"" - style << "top:#{params[:top]}px;" - style << "left:#{coords[:bar_start]}px;" + style << "inset-block-start:#{params[:top]}px;" + style << "inset-inline-start:#{coords[:bar_start]}px;" style << "width:#{width}px;" output << view.content_tag(:div, ' '.html_safe, :style => style, @@ -921,8 +921,8 @@ module Redmine if coords[:bar_progress_end] width = coords[:bar_progress_end] - coords[:bar_start] - 2 style = +"" - style << "top:#{params[:top]}px;" - style << "left:#{coords[:bar_start]}px;" + style << "inset-block-start:#{params[:top]}px;" + style << "inset-inline-start:#{coords[:bar_start]}px;" style << "width:#{width}px;" html_id = "task-done-issue-#{object.id}" if object.is_a?(Issue) html_id = "task-done-version-#{object.id}" if object.is_a?(Version) @@ -937,8 +937,8 @@ module Redmine if markers if coords[:start] style = +"" - style << "top:#{params[:top]}px;" - style << "left:#{coords[:start]}px;" + style << "inset-block-start:#{params[:top]}px;" + style << "inset-inline-start:#{coords[:start]}px;" style << "width:15px;" output << view.content_tag(:div, ' '.html_safe, :style => style, @@ -947,8 +947,8 @@ module Redmine end if coords[:end] style = +"" - style << "top:#{params[:top]}px;" - style << "left:#{coords[:end]}px;" + style << "inset-block-start:#{params[:top]}px;" + style << "inset-inline-start:#{coords[:end]}px;" style << "width:15px;" output << view.content_tag(:div, ' '.html_safe, :style => style, @@ -959,8 +959,8 @@ module Redmine # Renders the label on the right if label style = +"" - style << "top:#{params[:top]}px;" - style << "left:#{(coords[:bar_end] || 0) + 8}px;" + style << "inset-block-start:#{params[:top]}px;" + style << "inset-inline-start:#{(coords[:bar_end] || 0) + 8}px;" style << "width:15px;" output << view.content_tag(:div, label, :style => style, @@ -977,8 +977,8 @@ module Redmine :class => 'toggle-selection') style = +"" style << "position: absolute;" - style << "top:#{params[:top]}px;" - style << "left:#{coords[:bar_start]}px;" + style << "inset-block-start:#{params[:top]}px;" + style << "inset-inline-start:#{coords[:bar_start]}px;" style << "width:#{coords[:bar_end] - coords[:bar_start]}px;" style << "height:12px;" output << view.content_tag(:div, s.html_safe, diff --git a/test/functional/gantts_controller_test.rb b/test/functional/gantts_controller_test.rb index 2fe83bdd5..65dc0fc47 100644 --- a/test/functional/gantts_controller_test.rb +++ b/test/functional/gantts_controller_test.rb @@ -228,23 +228,23 @@ class GanttsControllerTest < Redmine::ControllerTest # eCookbook assert_subject_row('div.project-name', row: '0', text: project.name) - assert_chart_row('div.task.project.task_todo', row: '0', style_substring: 'left:0px;width:138px') + assert_chart_row('div.task.project.task_todo', row: '0', style_substring: 'inset-inline-start:0px;width:138px') assert_issue_row(3, 'Bug #3', row: '1') - assert_chart_row('div.task.leaf.task_todo', row: '1', style_substring: 'left:0px;width:38px') + assert_chart_row('div.task.leaf.task_todo', row: '1', style_substring: 'inset-inline-start:0px;width:38px') assert_issue_row(7, 'Bug #7', row: '2') - assert_chart_row('div.task.leaf.task_todo', row: '2', style_substring: 'left:16px;width:42px') + assert_chart_row('div.task.leaf.task_todo', row: '2', style_substring: 'inset-inline-start:16px;width:42px') assert_issue_row(1, 'Bug #1', row: '3') - assert_chart_row('div.task.leaf.task_todo', row: '3', style_substring: 'left:52px;width:46px') + assert_chart_row('div.task.leaf.task_todo', row: '3', style_substring: 'inset-inline-start:52px;width:46px') # Version 1.0 assert_subject_row('div#version-2', row: '4', text: '1.0') - assert_chart_row('div.task.version', row: '4', style_substring: 'left:48px;width:90px') + assert_chart_row('div.task.version', row: '4', style_substring: 'inset-inline-start:48px;width:90px') assert_issue_row(2, 'Feature request #2', row: '5') - assert_chart_row('div.task.leaf.task_todo', row: '5', style_substring: 'left:48px;width:90px') + assert_chart_row('div.task.leaf.task_todo', row: '5', style_substring: 'inset-inline-start:48px;width:90px') # Private child of eCookbook assert_subject_row( @@ -252,16 +252,16 @@ class GanttsControllerTest < Redmine::ControllerTest row: '6', text: projects(:projects_005).name ) - assert_chart_row('div.task.project.task_todo', row: '6', style_substring: 'left:56px;width:6px') + assert_chart_row('div.task.project.task_todo', row: '6', style_substring: 'inset-inline-start:56px;width:6px') assert_issue_row(6, 'Bug #6', row: '7') - assert_chart_row('div.task.leaf.task_todo', row: '7', style_substring: 'left:56px;width:6px') + assert_chart_row('div.task.leaf.task_todo', row: '7', style_substring: 'inset-inline-start:56px;width:6px') assert_issue_row(9, 'Bug #9', row: '8') - assert_chart_row('div.task.leaf.task_todo', row: '8', style_substring: 'left:56px;width:6px') + assert_chart_row('div.task.leaf.task_todo', row: '8', style_substring: 'inset-inline-start:56px;width:6px') assert_issue_row(10, 'Bug #10', row: '9') - assert_chart_row('div.task.leaf.task_todo', row: '9', style_substring: 'left:56px;width:6px') + assert_chart_row('div.task.leaf.task_todo', row: '9', style_substring: 'inset-inline-start:56px;width:6px') assert_select 'div.task[id=?][data-rels*=9]', 'task-todo-issue-10' # eCookbook Subproject1 @@ -303,7 +303,7 @@ class GanttsControllerTest < Redmine::ControllerTest # eCookbook assert_subject_row('div.project-name', row: '0', text: projects(:projects_001).name) - assert_chart_row('div.task.project.task_todo', row: '0', style_substring: 'left:0px;width:258px') + assert_chart_row('div.task.project.task_todo', row: '0', style_substring: 'inset-inline-start:0px;width:258px') # Private child of eCookbook assert_subject_row( @@ -311,19 +311,19 @@ class GanttsControllerTest < Redmine::ControllerTest row: '1', text: project.name ) - assert_chart_row('div.task.project.task_todo', row: '1', style_substring: 'left:176px;width:6px') + assert_chart_row('div.task.project.task_todo', row: '1', style_substring: 'inset-inline-start:176px;width:6px') # Bug #6 assert_issue_row(6, 'Bug #6', row: '2') - assert_chart_row('div.task.leaf.task_todo', row: '2', style_substring: 'left:176px;width:6px') + assert_chart_row('div.task.leaf.task_todo', row: '2', style_substring: 'inset-inline-start:176px;width:6px') # Bug #9 assert_issue_row(9, 'Bug #9', row: '3') - assert_chart_row('div.task.leaf.task_todo', row: '3', style_substring: 'left:176px;width:6px') + assert_chart_row('div.task.leaf.task_todo', row: '3', style_substring: 'inset-inline-start:176px;width:6px') # Bug #10 assert_issue_row(10, 'Bug #10', row: '4') - assert_chart_row('div.task.leaf.task_todo', row: '4', style_substring: 'left:176px;width:6px') + assert_chart_row('div.task.leaf.task_todo', row: '4', style_substring: 'inset-inline-start:176px;width:6px') assert_select 'div.task[id=?][data-rels*=9]', 'task-todo-issue-10' end diff --git a/test/unit/lib/redmine/helpers/gantt_test.rb b/test/unit/lib/redmine/helpers/gantt_test.rb index 4a5c01980..9a469c429 100644 --- a/test/unit/lib/redmine/helpers/gantt_test.rb +++ b/test/unit/lib/redmine/helpers/gantt_test.rb @@ -135,14 +135,14 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest setup_subjects @output_buffer = @gantt.subjects assert_select "div.project-name a", /#{@project.name}/ - assert_select 'div.project-name[style*="left:4px"]' + assert_select 'div.project-name[style*="inset-inline-start:4px"]' end test "#subjects version should be rendered" do setup_subjects @output_buffer = @gantt.subjects assert_select "div.version-name a", /#{@version.name}/ - assert_select 'div.version-name[style*="left:24px"]' + assert_select 'div.version-name[style*="inset-inline-start:24px"]' end test "#subjects version without assigned issues should not be rendered" do @@ -160,7 +160,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest @output_buffer = @gantt.subjects assert_select "div.issue-subject", /#{@issue.subject}/ # subject 62px: 44px + 18px(collapse/expand icon's width) - assert_select 'div.issue-subject[style*="left:62px"]' + assert_select 'div.issue-subject[style*="inset-inline-start:62px"]' end test "#subjects issue assigned to a shared version of another project should be rendered" do @@ -205,13 +205,13 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest ) @output_buffer = @gantt.subjects # parent task 44px - assert_select 'div.issue-subject[style*="left:44px"]', /#{@issue.subject}/ + assert_select 'div.issue-subject[style*="inset-inline-start:44px"]', /#{@issue.subject}/ # children 64px - assert_select 'div.issue-subject[style*="left:64px"]', /child1/ + assert_select 'div.issue-subject[style*="inset-inline-start:64px"]', /child1/ # children 76px: 64px + 18px(collapse/expand icon's width) - assert_select 'div.issue-subject[style*="left:82px"]', /child2/ + assert_select 'div.issue-subject[style*="inset-inline-start:82px"]', /child2/ # grandchild 96px: 84px + 18px(collapse/expand icon's width) - assert_select 'div.issue-subject[style*="left:102px"]', /grandchild/, @output_buffer + assert_select 'div.issue-subject[style*="inset-inline-start:102px"]', /grandchild/, @output_buffer end test "#lines" do @@ -319,7 +319,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest create_gantt @output_buffer = @gantt.subject('subject', :format => :html, :indent => 40) # subject 52px: 40px(indent) + 12px(collapse/expand icon's width) - assert_select 'div[style*="left:58px"]' + assert_select 'div[style*="inset-inline-start:58px"]' end test "#line_for_project" do @@ -353,7 +353,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest test "#line todo line should start from the starting point on the left" do create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 30, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.task_todo[style*="left:28px"]', 1 + assert_select 'div.task_todo[style*="inset-inline-start:28px"]', 1 end test "#line todo line should appear if it ends on the leftmost date in the gantt" do @@ -361,7 +361,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest [gantt_start - 1, gantt_start].each do |start_date| @output_buffer = @gantt.line(start_date, gantt_start, 30, false, 'line', :format => :html, :zoom => 4) # the leftmost date (Date.today - 14 days) - assert_select 'div.task_todo[style*="left:0px"]', 1, @output_buffer + assert_select 'div.task_todo[style*="inset-inline-start:0px"]', 1, @output_buffer assert_select 'div.task_todo[style*="width:2px"]', 1, @output_buffer end end @@ -371,7 +371,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest [gantt_end, gantt_end + 1].each do |end_date| @output_buffer = @gantt.line(gantt_end, end_date, 30, false, 'line', :format => :html, :zoom => 4) # the rightmost date (Date.today + 14 days) - assert_select 'div.task_todo[style*="left:112px"]', 1, @output_buffer + assert_select 'div.task_todo[style*="inset-inline-start:112px"]', 1, @output_buffer assert_select 'div.task_todo[style*="width:2px"]', 1, @output_buffer end end @@ -385,7 +385,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest test "#line late line should start from the starting point on the left" do create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 30, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.task_late[style*="left:28px"]', 1 + assert_select 'div.task_late[style*="inset-inline-start:28px"]', 1 end test "#line late line should be the total delayed width" do @@ -411,7 +411,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest test "#line done line should start from the starting point on the left" do create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 30, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.task_done[style*="left:28px"]', 1 + assert_select 'div.task_done[style*="inset-inline-start:28px"]', 1 end test "#line done line should be the width for the done ratio" do @@ -437,7 +437,7 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest test "#line done line should not be the total done width if the gantt starts after start date" do create_gantt @output_buffer = @gantt.line(today - 16, today - 2, 30, false, 'line', :format => :html, :zoom => 4) - assert_select 'div.task_done[style*="left:0px"]', 1 + assert_select 'div.task_done[style*="inset-inline-start:0px"]', 1 assert_select 'div.task_done[style*="width:8px"]', 1 end @@ -445,10 +445,10 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 30, true, 'line', :format => :html, :zoom => 4) assert_select "div.starting", 1 - assert_select 'div.starting[style*="left:28px"]', 1 + assert_select 'div.starting[style*="inset-inline-start:28px"]', 1 # starting marker on the leftmost boundary of the gantt @output_buffer = @gantt.line(gantt_start, today + 7, 30, true, 'line', :format => :html, :zoom => 4) - assert_select 'div.starting[style*="left:0px"]', 1 + assert_select 'div.starting[style*="inset-inline-start:0px"]', 1 end test "#line starting marker should not appear if the start date is before gantt start date" do @@ -461,10 +461,10 @@ class Redmine::Helpers::GanttHelperTest < Redmine::HelperTest create_gantt @output_buffer = @gantt.line(today - 7, today + 7, 30, true, 'line', :format => :html, :zoom => 4) assert_select "div.ending", 1 - assert_select 'div.ending[style*="left:88px"]', 1 + assert_select 'div.ending[style*="inset-inline-start:88px"]', 1 # ending marker on the rightmost boundary of the gantt @output_buffer = @gantt.line(today - 7, gantt_end, 30, true, 'line', :format => :html, :zoom => 4) - assert_select 'div.ending[style*="left:116px"]', 1 + assert_select 'div.ending[style*="inset-inline-start:116px"]', 1 end test "#line ending marker should not appear if the end date is before gantt start date" do -- 2.50.1