From cea2a7ff145eb1dea83957cead0ec08d76ac25d7 Mon Sep 17 00:00:00 2001
From: tohosaku <tohosaku@users.osdn.me>
Date: Tue, 1 Aug 2023 02:20:27 +0000
Subject: [PATCH] Support vertical list layout calendar #33682

---
 app/controllers/my_controller.rb    |  1 -
 app/helpers/calendars_helper.rb     | 29 ------------
 app/views/common/_calendar.html.erb | 73 ++++++++++++-----------------
 lib/redmine/helpers/calendar.rb     | 21 +++++++++
 public/stylesheets/application.css  | 69 +++++++++++++++++++++------
 public/stylesheets/responsive.css   | 32 +++++++++++++
 6 files changed, 138 insertions(+), 87 deletions(-)
 delete mode 100644 app/helpers/calendars_helper.rb

diff --git a/app/controllers/my_controller.rb b/app/controllers/my_controller.rb
index 48a68b626..9eaaa90e7 100644
--- a/app/controllers/my_controller.rb
+++ b/app/controllers/my_controller.rb
@@ -33,7 +33,6 @@ class MyController < ApplicationController
   helper :custom_fields
   helper :queries
   helper :activities
-  helper :calendars
 
   def index
     page
diff --git a/app/helpers/calendars_helper.rb b/app/helpers/calendars_helper.rb
deleted file mode 100644
index e207bc391..000000000
--- a/app/helpers/calendars_helper.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-# Redmine - project management software
-# Copyright (C) 2006-2023  Jean-Philippe Lang
-#
-# 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.
-
-module CalendarsHelper
-  include Redmine::Utils::DateCalculation
-
-  def calendar_day_css_classes(calendar, day)
-    css = day.month==calendar.month ? +'even' : +'odd'
-    css << " today" if User.current.today == day
-    css << " nwday" if non_working_week_days.include?(day.cwday)
-    css
-  end
-end
diff --git a/app/views/common/_calendar.html.erb b/app/views/common/_calendar.html.erb
index 63cb43c97..d29038d67 100644
--- a/app/views/common/_calendar.html.erb
+++ b/app/views/common/_calendar.html.erb
@@ -1,48 +1,37 @@
 <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
 <%= hidden_field_tag 'back_url', url_for(:params => request.query_parameters), :id => nil %>
-<table class="cal">
-<thead>
-  <tr>
-    <th scope="col" title="<%= l(:label_week) %>" class="week-number"></th>
-    <% 7.times do |i| %>
-      <th scope="col"><%= day_name((calendar.first_wday + i) % 7) %></th>
-    <% end %>
-  </tr>
-</thead>
-<tbody>
-<tr>
-<% day = calendar.startdt %>
-<% while day <= calendar.enddt %>
-  <% if day.cwday == calendar.first_wday %>
-    <td class='week-number' title='<%= l(:label_week) %>'>
-      <%= (day + (11 - day.cwday) % 7).cweek %>
-    </td>
+<ul class="cal">
+  <li scope="col" title="<%= l(:label_week) %>" class="calhead week-number"></li>
+  <% 7.times do |i| %>
+    <li scope="col" class="calhead"><%= day_name((calendar.first_wday + i) % 7) %></li>
   <% end %>
-<td class="<%= calendar_day_css_classes(calendar, day) %>">
-<p class="day-num"><%= day.day %></p>
-<% calendar.events_on(day).each do |i| %>
-  <% if i.is_a? Issue %>
-  <div class="<%= i.css_classes %> <%= 'starting' if day == i.start_date %> <%= 'ending' if day == i.due_date %> tooltip hascontextmenu">
-  <%= "#{i.project} -" unless @project && @project == i.project %>
-  <%= link_to_issue i, :truncate => 30 %>
-  <span class="tip"><%= render_issue_tooltip i %></span>
-  <%= check_box_tag 'ids[]', i.id, false, :style => 'display:none;', :id => nil %>
-  </div>
-  <% else %>
-  <span class="icon icon-package">
-    <%= "#{i.project} -" unless @project && @project == i.project %>
-    <%= link_to_version i %>
-  </span>
+  <% calendar.format_month.each_slice(7) do |week| %>
+    <li class='week-number' title='<%= l(:label_week) %>'>
+      <%= calendar.week_number week.first %><span class="label-week"><%= l(:label_week) %></span>
+    </li>
+    <% week.each do |day| %>
+    <li class="<%= calendar.day_css_classes day %> calbody">
+      <p class="day-num"><%= day.day %>
+        <span class="day-letter">&nbsp;(<%= calendar.day_letter(day.cwday) %>)</span>
+      </p>
+      <% calendar.events_on(day).each do |i| %>
+        <% if i.is_a? Issue %>
+          <%= tag.div class: [ i.css_classes, 'tooltip hascontextmenu', starting: day == i.start_date, ending: day == i.due_date] do %>
+            <%= "#{i.project} -" unless @project && @project == i.project %>
+            <%= link_to_issue i, :truncate => 30 %>
+            <span class="tip"><%= render_issue_tooltip i %></span>
+            <%= check_box_tag 'ids[]', i.id, false, :style => 'display:none;', :id => nil %>
+          <% end %>
+        <% else %>
+          <span class="icon icon-package">
+            <%= "#{i.project} -" unless @project && @project == i.project %>
+            <%= link_to_version i %>
+          </span>
+        <% end %>
+      <% end %>
+    </li>
+    <% end %>
   <% end %>
-<% end %>
-</td>
-<% if day.cwday==calendar.last_wday and day!=calendar.enddt %>
-  </tr><tr>
-<% end %>
-<% day = day + 1 %>
-<% end %>
-</tr>
-</tbody>
-</table>
+</ul>
 <% end %>
 <%= context_menu %>
diff --git a/lib/redmine/helpers/calendar.rb b/lib/redmine/helpers/calendar.rb
index 2f5b5d097..ad57677fc 100644
--- a/lib/redmine/helpers/calendar.rb
+++ b/lib/redmine/helpers/calendar.rb
@@ -23,6 +23,8 @@ module Redmine
     # Simple class to compute the start and end dates of a calendar
     class Calendar
       include Redmine::I18n
+      include Redmine::Utils::DateCalculation
+
       attr_reader :startdt, :enddt
 
       def initialize(date, lang = current_language, period = :month)
@@ -47,6 +49,21 @@ module Redmine
         end
       end
 
+      def format_month
+        (@startdt..@enddt).to_a
+      end
+
+      def week_number(day)
+        (day + (11 - day.cwday) % 7).cweek
+      end
+
+      def day_css_classes(day)
+        css = day.month==month ? +'even' : +'odd'
+        css << " today" if User.current.today == day
+        css << " nwday" if non_working_week_days.include?(day.cwday)
+        css
+      end
+
       # Sets calendar events
       def events=(events)
         @events = events
@@ -82,6 +99,10 @@ module Redmine
       def last_wday
         @last_dow ||= (first_wday + 5)%7 + 1
       end
+
+      def day_letter(day)
+        ::I18n.t('date.abbr_day_names')[day % 7]
+      end
     end
   end
 end
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 7d256832b..fc6e6ad10 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -1100,21 +1100,60 @@ vertical-align: bottom;
 }
 
 /***** Calendar *****/
-table.cal {width: 100%; margin: 0 0 6px 0; border: 1px solid #c0c0c0; border-spacing: 0; border-radius: 3px;}
-table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
-table.cal thead th.week-number {width: auto;}
-table.cal tbody tr {height: 100px;}
-table.cal td .icon {padding-top: 2px; padding-bottom: 3px;}
-table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em; border-bottom: 0; border-right: 0;}
-table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
-table.cal td p.day-num {font-size: 1.1em; text-align:right;}
-table.cal td.odd p.day-num {color: #bbb;}
-table.cal td.today {background:#ffffdd;}
-table.cal td.today p.day-num {font-weight: bold;}
-table.cal td.nwday:not(.odd) {background-color:#f1f1f1;}
-table.cal .starting a.issue, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
-table.cal .ending a.issue, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
-table.cal .starting.ending a.issue, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
+ul.cal {
+  list-style: none;
+  width: 100%;
+  padding: 0;
+  display: grid;
+  grid-template-columns: 2rem repeat(7, 1fr);
+  margin: 0;
+  border: 1px solid #c0c0c0;
+  border-spacing: 0;
+  border-radius: 3px;
+}
+
+.cal .calhead {
+  background-color:#eee;
+  text-align: center;
+  font-weight: bold;
+  padding: 4px
+}
+
+.cal .week-number {
+  background-color:#eee;
+  border:none;
+  font-size: 1em;
+  padding: 4px;
+  text-align: center;
+}
+
+.cal .week-number .label-week {
+  display: none;
+}
+
+.cal .calbody {
+  border: 1px solid #d7d7d7;
+  vertical-align: top;
+  font-size: 0.9em;
+  border-bottom: 0;
+  border-right: 0;
+  line-height: 1.2;
+  min-height: calc(1.2em * 6);
+  padding: 2px;
+}
+
+.cal .calbody p.day-num {font-size: 1.1em; text-align:right;}
+.cal .calbody .day-letter {display:none}
+.cal .calbody.odd p.day-num {color: #bbb;}
+.cal .calbody.today {background:#ffd;}
+.cal .calbody.today p.day-num {font-weight: bold;}
+
+.cal .calbody .icon {padding-top: 2px; padding-bottom: 3px;}
+.cal .calbody.nwday:not(.odd) {background-color:#f1f1f1;}
+.cal .starting a.issue, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
+.cal .ending a.issue, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
+.cal .starting.ending a.issue, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
+
 p.cal.legend span {display:block;}
 .controller-calendars p.buttons {margin-top: unset;}
 
diff --git a/public/stylesheets/responsive.css b/public/stylesheets/responsive.css
index d6282046d..99a1e00c3 100644
--- a/public/stylesheets/responsive.css
+++ b/public/stylesheets/responsive.css
@@ -819,6 +819,38 @@
     margin-left: 0;
     width: 100%;
   }
+
+  /* Calendar */
+  ul.cal {
+    display: block
+  }
+  .cal .calhead {
+    display: none
+  }
+
+  .cal .calbody {
+    min-height: calc(1.2em * 3);
+  }
+
+  .cal .calbody .day-letter {
+    display: inline;
+  }
+
+  .cal .week-number {
+    border: 1px solid #c0c0c0;
+    text-align: left;
+    font-weight: bold;
+    background-color: #def;
+  }
+
+  .cal .week-number .label-week {
+    display: inline;
+  }
+
+  .cal .calbody p.day-num {
+    font-size: 1.1em;
+    text-align: left;
+  }
 }
 
 @media all and (max-width: 599px) {
-- 
2.39.2

