about summary refs log tree commit diff
diff options
context:
space:
mode:
authorClaire <claire.github-309c@sitedethib.com>2022-10-28 12:56:32 +0200
committerGitHub <noreply@github.com>2022-10-28 12:56:32 +0200
commitd9d722d74b2cb9bf30fa4cc98af6ddbeca003ebc (patch)
tree54827827ec434c98ce2991431bd50ecaffcb115f
parent10922294ffd2c83e155f020896318d16f3764e8d (diff)
Change admin announcement edition interface to use datetime-local (#18321)
* Change admin announcement edition interface to use datetime-local

* Dynamically set announcement stop date as required if start date is set, set minimum date for stop date

* Change `all_day` to not be bound to presence of time-range

* Add pattern and placeholder as minimal fallback for browsers not supporting datetime-local

* Display datetime-local inputs as local time

Co-authored-by: Eugen Rochko <eugen@zeonfederated.com>
-rw-r--r--app/javascript/packs/admin.js54
-rw-r--r--app/javascript/styles/mastodon/forms.scss5
-rw-r--r--app/models/announcement.rb5
-rw-r--r--app/views/admin/announcements/edit.html.haml7
-rw-r--r--app/views/admin/announcements/new.html.haml9
5 files changed, 69 insertions, 11 deletions
diff --git a/app/javascript/packs/admin.js b/app/javascript/packs/admin.js
index b733d6b18..de86e0e11 100644
--- a/app/javascript/packs/admin.js
+++ b/app/javascript/packs/admin.js
@@ -2,6 +2,24 @@ import './public-path';
 import { delegate } from '@rails/ujs';
 import ready from '../mastodon/ready';
 
+const setAnnouncementEndsAttributes = (target) => {
+  const valid = target?.value && target?.validity?.valid;
+  const element = document.querySelector('input[type="datetime-local"]#announcement_ends_at');
+  if (valid) {
+    element.classList.remove('optional');
+    element.required = true;
+    element.min = target.value;
+  } else {
+    element.classList.add('optional');
+    element.removeAttribute('required');
+    element.removeAttribute('min');
+  }
+};
+
+delegate(document, 'input[type="datetime-local"]#announcement_starts_at', 'change', ({ target }) => {
+  setAnnouncementEndsAttributes(target);
+});
+
 const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
 
 const showSelectAll = () => {
@@ -141,6 +159,20 @@ const onChangeRegistrationMode = (target) => {
   });
 };
 
+const convertUTCDateTimeToLocal = (value) => {
+  const date = new Date(value + 'Z');
+  const twoChars = (x) => (x.toString().padStart(2, '0'));
+  return `${date.getFullYear()}-${twoChars(date.getMonth()+1)}-${twoChars(date.getDate())}T${twoChars(date.getHours())}:${twoChars(date.getMinutes())}`;
+};
+
+const convertLocalDatetimeToUTC = (value) => {
+  const re = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2})/;
+  const match = re.exec(value);
+  const date = new Date(match[1], match[2] - 1, match[3], match[4], match[5]);
+  const fullISO8601 = date.toISOString();
+  return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6);
+};
+
 delegate(document, '#form_admin_settings_registrations_mode', 'change', ({ target }) => onChangeRegistrationMode(target));
 
 ready(() => {
@@ -163,6 +195,28 @@ ready(() => {
     }
   });
 
+  [].forEach.call(document.querySelectorAll('input[type="datetime-local"]'), element => {
+    if (element.value) {
+      element.value = convertUTCDateTimeToLocal(element.value);
+    }
+    if (element.placeholder) {
+      element.placeholder = convertUTCDateTimeToLocal(element.placeholder);
+    }
+  });
+
+  delegate(document, 'form', 'submit', ({ target }) => {
+    [].forEach.call(target.querySelectorAll('input[type="datetime-local"]'), element => {
+      if (element.value && element.validity.valid) {
+        element.value = convertLocalDatetimeToUTC(element.value);
+      }
+    });
+  });
+
+  const announcementStartsAt = document.querySelector('input[type="datetime-local"]#announcement_starts_at');
+  if (announcementStartsAt) {
+    setAnnouncementEndsAttributes(announcementStartsAt);
+  }
+
   const React    = require('react');
   const ReactDOM = require('react-dom');
 
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 25c9c9fe5..a3ddc7636 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -405,6 +405,7 @@ code {
   input[type="email"],
   input[type="password"],
   input[type="url"],
+  input[type="datetime-local"],
   textarea {
     box-sizing: border-box;
     font-size: 16px;
@@ -445,7 +446,8 @@ code {
   input[type="text"],
   input[type="number"],
   input[type="email"],
-  input[type="password"] {
+  input[type="password"],
+  input[type="datetime-local"] {
     &:focus:invalid:not(:placeholder-shown),
     &:required:invalid:not(:placeholder-shown) {
       border-color: lighten($error-red, 12%);
@@ -461,6 +463,7 @@ code {
     input[type="number"],
     input[type="email"],
     input[type="password"],
+    input[type="datetime-local"],
     textarea,
     select {
       border-color: lighten($error-red, 12%);
diff --git a/app/models/announcement.rb b/app/models/announcement.rb
index bedced9de..4b2cb4c6d 100644
--- a/app/models/announcement.rb
+++ b/app/models/announcement.rb
@@ -31,7 +31,6 @@ class Announcement < ApplicationRecord
   validates :starts_at, presence: true, if: -> { ends_at.present? }
   validates :ends_at, presence: true, if: -> { starts_at.present? }
 
-  before_validation :set_all_day
   before_validation :set_published, on: :create
 
   def to_log_human_identifier
@@ -89,10 +88,6 @@ class Announcement < ApplicationRecord
 
   private
 
-  def set_all_day
-    self.all_day = false if starts_at.blank? || ends_at.blank?
-  end
-
   def set_published
     return unless scheduled_at.blank? || scheduled_at.past?
 
diff --git a/app/views/admin/announcements/edit.html.haml b/app/views/admin/announcements/edit.html.haml
index 5f56db5e7..e20a839b9 100644
--- a/app/views/admin/announcements/edit.html.haml
+++ b/app/views/admin/announcements/edit.html.haml
@@ -1,12 +1,15 @@
 - content_for :page_title do
   = t('.title')
 
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
 = simple_form_for @announcement, url: admin_announcement_path(@announcement) do |f|
   = render 'shared/error_messages', object: @announcement
 
   .fields-group
-    = f.input :starts_at, include_blank: true, wrapper: :with_block_label
-    = f.input :ends_at, include_blank: true, wrapper: :with_block_label
+    = f.input :starts_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
+    = f.input :ends_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
 
   .fields-group
     = f.input :all_day, as: :boolean, wrapper: :with_label
diff --git a/app/views/admin/announcements/new.html.haml b/app/views/admin/announcements/new.html.haml
index a5298c5f6..d5881b7aa 100644
--- a/app/views/admin/announcements/new.html.haml
+++ b/app/views/admin/announcements/new.html.haml
@@ -1,12 +1,15 @@
 - content_for :page_title do
   = t('.title')
 
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
 = simple_form_for @announcement, url: admin_announcements_path do |f|
   = render 'shared/error_messages', object: @announcement
 
   .fields-group
-    = f.input :starts_at, include_blank: true, wrapper: :with_block_label
-    = f.input :ends_at, include_blank: true, wrapper: :with_block_label
+    = f.input :starts_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
+    = f.input :ends_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
 
   .fields-group
     = f.input :all_day, as: :boolean, wrapper: :with_label
@@ -15,7 +18,7 @@
     = f.input :text, wrapper: :with_block_label
 
   .fields-group
-    = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label
+    = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}', placeholder: Time.now.strftime('%FT%R') }
 
   .actions
     = f.button :button, t('.create'), type: :submit