FEATURE: adds support for timezone (recurring and non recurring) (#237)
This commit is contained in:
parent
c3a09e2ec5
commit
7d10944055
|
|
@ -282,6 +282,7 @@ module DiscoursePostEvent
|
|||
original_ends_at: event_params[:end],
|
||||
url: event_params[:url],
|
||||
recurrence: event_params[:recurrence],
|
||||
timezone: event_params[:timezone],
|
||||
status: event_params[:status].present? ? Event.statuses[event_params[:status].to_sym] : event.status,
|
||||
reminders: event_params[:reminders],
|
||||
raw_invitees: event_params[:"allowed-groups"] ? event_params[:"allowed-groups"].split(',') : nil
|
||||
|
|
@ -329,7 +330,12 @@ module DiscoursePostEvent
|
|||
end
|
||||
|
||||
def calculate_next_date
|
||||
return { starts_at: original_starts_at, ends_at: original_ends_at } if !original_ends_at || self.recurrence.blank? || original_starts_at > Time.current
|
||||
if !original_ends_at || self.recurrence.blank? || original_starts_at > Time.current
|
||||
return {
|
||||
starts_at: original_starts_at,
|
||||
ends_at: original_ends_at
|
||||
}
|
||||
end
|
||||
|
||||
recurrence = nil
|
||||
|
||||
|
|
@ -357,7 +363,7 @@ module DiscoursePostEvent
|
|||
recurrence = "FREQ=WEEKLY;BYDAY=#{byday}"
|
||||
end
|
||||
|
||||
next_starts_at = RRuleGenerator.generate(recurrence, original_starts_at)
|
||||
next_starts_at = RRuleGenerator.generate(recurrence, original_starts_at, tzid: self.timezone)
|
||||
difference = original_ends_at - original_starts_at
|
||||
next_ends_at = next_starts_at + difference.seconds
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ module DiscoursePostEvent
|
|||
attributes :watching_invitee
|
||||
attributes :starts_at
|
||||
attributes :ends_at
|
||||
attributes :timezone
|
||||
attributes :stats
|
||||
attributes :status
|
||||
attributes :raw_invitees
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import RestModel from "discourse/models/rest";
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
const ATTRIBUTES = {
|
||||
id: {},
|
||||
name: {},
|
||||
starts_at: {},
|
||||
ends_at: {},
|
||||
raw_invitees: {},
|
||||
url: {},
|
||||
id: null,
|
||||
name: null,
|
||||
starts_at: null,
|
||||
ends_at: null,
|
||||
raw_invitees: null,
|
||||
url: null,
|
||||
timezone: null,
|
||||
status: {
|
||||
transform(value) {
|
||||
return STATUSES[value];
|
||||
|
|
@ -51,7 +52,7 @@ const Event = RestModel.extend({
|
|||
const attributesKeys = Object.keys(ATTRIBUTES);
|
||||
attributesKeys.forEach((key) => {
|
||||
const attribute = ATTRIBUTES[key];
|
||||
if (attribute.transform) {
|
||||
if (attribute?.transform) {
|
||||
props[key] = attribute.transform(props[key]);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@
|
|||
}}
|
||||
{{/event-field}}
|
||||
|
||||
{{#event-field class="timezone" label="discourse_post_event.builder_modal.timezone.label"}}
|
||||
{{timezone-input
|
||||
value=model.eventModel.timezone
|
||||
onChange=(action (mut model.eventModel.timezone))
|
||||
class="input-xxlarge"
|
||||
none="discourse_post_event.builder_modal.timezone.remove_timezone"
|
||||
}}
|
||||
{{/event-field}}
|
||||
|
||||
{{#event-field label="discourse_post_event.builder_modal.status.label"}}
|
||||
<label class="radio-label">
|
||||
{{radio-button
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ function _attachWidget(api, cooked, eventModel) {
|
|||
eventContainer.appendChild(glueContainer);
|
||||
|
||||
const startsAt = moment(eventModel.starts_at);
|
||||
const timezone = eventModel.timezone || "UTC";
|
||||
const format = guessDateFormat(
|
||||
startsAt,
|
||||
eventModel.ends_at && moment(eventModel.ends_at)
|
||||
|
|
@ -129,7 +130,7 @@ function _attachWidget(api, cooked, eventModel) {
|
|||
.utc(eventModel.starts_at)
|
||||
.format("YYYY-MM-DD")} time=${moment
|
||||
.utc(eventModel.starts_at)
|
||||
.format("HH:mm")} format=${format}]`
|
||||
.format("HH:mm")} format=${format} timezone=${timezone}]`
|
||||
);
|
||||
|
||||
if (eventModel.ends_at) {
|
||||
|
|
@ -137,7 +138,7 @@ function _attachWidget(api, cooked, eventModel) {
|
|||
dates.push(
|
||||
`[date=${endsAt.format("YYYY-MM-DD")} time=${endsAt.format(
|
||||
"HH:mm"
|
||||
)} format=${format}]`
|
||||
)} format=${format} timezone=${timezone}]`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ export function buildParams(startsAt, endsAt, eventModel, siteSettings) {
|
|||
params.url = eventModel.url;
|
||||
}
|
||||
|
||||
if (eventModel.timezone) {
|
||||
params.timezone = eventModel.timezone;
|
||||
}
|
||||
|
||||
if (eventModel.recurrence) {
|
||||
params.recurrence = eventModel.recurrence;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -359,6 +359,9 @@ en:
|
|||
update: "Save"
|
||||
attach: "Create event"
|
||||
add_reminder: "Add reminder"
|
||||
timezone:
|
||||
label: Timezone
|
||||
remove_timezone: No timezone (UTC)
|
||||
reminders:
|
||||
label: "Reminders"
|
||||
recurrence:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddsTimezoneToDiscoursePostEventEvent < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :discourse_post_event_events, :timezone, :string
|
||||
end
|
||||
end
|
||||
|
|
@ -8,7 +8,8 @@ VALID_OPTIONS = [
|
|||
:url,
|
||||
:name,
|
||||
:reminders,
|
||||
:recurrence
|
||||
:recurrence,
|
||||
:timezone
|
||||
]
|
||||
|
||||
module DiscoursePostEvent
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@ module DiscoursePostEvent
|
|||
end
|
||||
end
|
||||
|
||||
if extracted_event[:timezone].present?
|
||||
if !ActiveSupport::TimeZone[extracted_event[:timezone]].present?
|
||||
@post.errors.add(:base, I18n.t("discourse_post_event.errors.models.event.invalid_timezone", timezone: extracted_event[:timezone]))
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
require 'rrule'
|
||||
|
||||
class RRuleGenerator
|
||||
def self.generate(base_rrule, starts_at)
|
||||
def self.generate(base_rrule, starts_at, tzid: 'UTC')
|
||||
rrule = generate_hash(base_rrule)
|
||||
rrule = set_mandatory_options(rrule, starts_at)
|
||||
|
||||
::RRule::Rule.new(stringify(rrule), dtstart: starts_at, exdate: [starts_at])
|
||||
::RRule::Rule.new(stringify(rrule), dtstart: starts_at, exdate: [starts_at], tzid: tzid)
|
||||
.between(Time.current, Time.current + 2.months)
|
||||
.first
|
||||
end
|
||||
|
|
|
|||
|
|
@ -551,8 +551,8 @@ after_initialize do
|
|||
fragment.css('.discourse-post-event').each do |event_node|
|
||||
starts_at = event_node['data-start']
|
||||
ends_at = event_node['data-end']
|
||||
dates = "#{starts_at} (UTC)"
|
||||
dates = "#{dates} → #{ends_at} (UTC)" if ends_at
|
||||
dates = "#{starts_at} (#{event_node['data-timezone'] || 'UTC'})"
|
||||
dates = "#{dates} → #{ends_at} (#{event_node['data-timezone'] || 'UTC'})" if ends_at
|
||||
|
||||
event_name = event_node['data-name'] || post.topic.title
|
||||
event_node.replace <<~TXT
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe 'discourse_post_event_recurrence' do
|
|||
|
||||
it 'delete previous notifications before creating a new one for invites' do
|
||||
going_user = Fabricate(:user)
|
||||
Invitee.create_attendance!(going_user.id, post_event_1.id, :going)
|
||||
DiscoursePostEvent::Invitee.create_attendance!(going_user.id, post_event_1.id, :going)
|
||||
post_event_1.update!(recurrence: 'every_month')
|
||||
|
||||
post_event_1.set_next_date
|
||||
|
|
@ -91,4 +91,20 @@ describe 'discourse_post_event_recurrence' do
|
|||
expect(post_event_1.starts_at).to eq_time(Time.zone.parse('2020-09-14 19:00'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'the event has a timezone' do
|
||||
context 'every_month' do
|
||||
before do
|
||||
post_event_1.update!(recurrence: 'every_month', timezone: 'America/New_York')
|
||||
end
|
||||
|
||||
it 'sets the next month at the same weekday' do
|
||||
freeze_time(starts_at + 1.day)
|
||||
|
||||
post_event_1.set_next_date
|
||||
|
||||
expect(post_event_1.starts_at).to eq_time(Time.zone.parse('2020-10-08 23:00'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,6 +57,21 @@ describe PrettyText do
|
|||
HTML
|
||||
end
|
||||
end
|
||||
|
||||
context 'The event has a timezone' do
|
||||
let(:post_1) { create_post_with_event(user_1, 'timezone="America/New_York"') }
|
||||
|
||||
it 'uses the timezone' do
|
||||
cooked = PrettyText.cook(post_1.raw)
|
||||
|
||||
expect(PrettyText.format_for_email(cooked, post_1)).to match_html(<<~HTML)
|
||||
<div style='border:1px solid #dedede'>
|
||||
<p><a href="#{Discourse.base_url}#{post_1.url}">#{post_1.topic.title}</a></p>
|
||||
<p>2018-06-05T18:39:50.000Z (America/New_York)</p>
|
||||
</div>
|
||||
HTML
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,6 +27,25 @@ describe RRuleGenerator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'tzid given' do
|
||||
let(:sample_rrule) { 'FREQ=WEEKLY;BYDAY=MO' }
|
||||
|
||||
it 'it correctly computes the next date using the timezone' do
|
||||
tzid = 'Europe/Paris'
|
||||
time = Time.utc(2020, 1, 25, 15, 36)
|
||||
|
||||
freeze_time DateTime.parse('2020-02-25 15:36')
|
||||
|
||||
rrule = RRuleGenerator.generate(sample_rrule, time, tzid: tzid)
|
||||
expect(rrule.to_s).to eq('2020-03-02 15:36:00 +0100')
|
||||
|
||||
freeze_time DateTime.parse('2020-09-25 15:36')
|
||||
|
||||
rrule = RRuleGenerator.generate(sample_rrule, time, tzid: tzid)
|
||||
expect(rrule.to_s).to eq('2020-09-28 15:36:00 +0200')
|
||||
end
|
||||
end
|
||||
|
||||
context 'every day' do
|
||||
let(:sample_rrule) { 'FREQ=DAILY' }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue