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],
|
original_ends_at: event_params[:end],
|
||||||
url: event_params[:url],
|
url: event_params[:url],
|
||||||
recurrence: event_params[:recurrence],
|
recurrence: event_params[:recurrence],
|
||||||
|
timezone: event_params[:timezone],
|
||||||
status: event_params[:status].present? ? Event.statuses[event_params[:status].to_sym] : event.status,
|
status: event_params[:status].present? ? Event.statuses[event_params[:status].to_sym] : event.status,
|
||||||
reminders: event_params[:reminders],
|
reminders: event_params[:reminders],
|
||||||
raw_invitees: event_params[:"allowed-groups"] ? event_params[:"allowed-groups"].split(',') : nil
|
raw_invitees: event_params[:"allowed-groups"] ? event_params[:"allowed-groups"].split(',') : nil
|
||||||
|
|
@ -329,7 +330,12 @@ module DiscoursePostEvent
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_next_date
|
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
|
recurrence = nil
|
||||||
|
|
||||||
|
|
@ -357,7 +363,7 @@ module DiscoursePostEvent
|
||||||
recurrence = "FREQ=WEEKLY;BYDAY=#{byday}"
|
recurrence = "FREQ=WEEKLY;BYDAY=#{byday}"
|
||||||
end
|
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
|
difference = original_ends_at - original_starts_at
|
||||||
next_ends_at = next_starts_at + difference.seconds
|
next_ends_at = next_starts_at + difference.seconds
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ module DiscoursePostEvent
|
||||||
attributes :watching_invitee
|
attributes :watching_invitee
|
||||||
attributes :starts_at
|
attributes :starts_at
|
||||||
attributes :ends_at
|
attributes :ends_at
|
||||||
|
attributes :timezone
|
||||||
attributes :stats
|
attributes :stats
|
||||||
attributes :status
|
attributes :status
|
||||||
attributes :raw_invitees
|
attributes :raw_invitees
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ import RestModel from "discourse/models/rest";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
const ATTRIBUTES = {
|
const ATTRIBUTES = {
|
||||||
id: {},
|
id: null,
|
||||||
name: {},
|
name: null,
|
||||||
starts_at: {},
|
starts_at: null,
|
||||||
ends_at: {},
|
ends_at: null,
|
||||||
raw_invitees: {},
|
raw_invitees: null,
|
||||||
url: {},
|
url: null,
|
||||||
|
timezone: null,
|
||||||
status: {
|
status: {
|
||||||
transform(value) {
|
transform(value) {
|
||||||
return STATUSES[value];
|
return STATUSES[value];
|
||||||
|
|
@ -51,7 +52,7 @@ const Event = RestModel.extend({
|
||||||
const attributesKeys = Object.keys(ATTRIBUTES);
|
const attributesKeys = Object.keys(ATTRIBUTES);
|
||||||
attributesKeys.forEach((key) => {
|
attributesKeys.forEach((key) => {
|
||||||
const attribute = ATTRIBUTES[key];
|
const attribute = ATTRIBUTES[key];
|
||||||
if (attribute.transform) {
|
if (attribute?.transform) {
|
||||||
props[key] = attribute.transform(props[key]);
|
props[key] = attribute.transform(props[key]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,15 @@
|
||||||
}}
|
}}
|
||||||
{{/event-field}}
|
{{/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"}}
|
{{#event-field label="discourse_post_event.builder_modal.status.label"}}
|
||||||
<label class="radio-label">
|
<label class="radio-label">
|
||||||
{{radio-button
|
{{radio-button
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ function _attachWidget(api, cooked, eventModel) {
|
||||||
eventContainer.appendChild(glueContainer);
|
eventContainer.appendChild(glueContainer);
|
||||||
|
|
||||||
const startsAt = moment(eventModel.starts_at);
|
const startsAt = moment(eventModel.starts_at);
|
||||||
|
const timezone = eventModel.timezone || "UTC";
|
||||||
const format = guessDateFormat(
|
const format = guessDateFormat(
|
||||||
startsAt,
|
startsAt,
|
||||||
eventModel.ends_at && moment(eventModel.ends_at)
|
eventModel.ends_at && moment(eventModel.ends_at)
|
||||||
|
|
@ -129,7 +130,7 @@ function _attachWidget(api, cooked, eventModel) {
|
||||||
.utc(eventModel.starts_at)
|
.utc(eventModel.starts_at)
|
||||||
.format("YYYY-MM-DD")} time=${moment
|
.format("YYYY-MM-DD")} time=${moment
|
||||||
.utc(eventModel.starts_at)
|
.utc(eventModel.starts_at)
|
||||||
.format("HH:mm")} format=${format}]`
|
.format("HH:mm")} format=${format} timezone=${timezone}]`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (eventModel.ends_at) {
|
if (eventModel.ends_at) {
|
||||||
|
|
@ -137,7 +138,7 @@ function _attachWidget(api, cooked, eventModel) {
|
||||||
dates.push(
|
dates.push(
|
||||||
`[date=${endsAt.format("YYYY-MM-DD")} time=${endsAt.format(
|
`[date=${endsAt.format("YYYY-MM-DD")} time=${endsAt.format(
|
||||||
"HH:mm"
|
"HH:mm"
|
||||||
)} format=${format}]`
|
)} format=${format} timezone=${timezone}]`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ export function buildParams(startsAt, endsAt, eventModel, siteSettings) {
|
||||||
params.url = eventModel.url;
|
params.url = eventModel.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventModel.timezone) {
|
||||||
|
params.timezone = eventModel.timezone;
|
||||||
|
}
|
||||||
|
|
||||||
if (eventModel.recurrence) {
|
if (eventModel.recurrence) {
|
||||||
params.recurrence = eventModel.recurrence;
|
params.recurrence = eventModel.recurrence;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -359,6 +359,9 @@ en:
|
||||||
update: "Save"
|
update: "Save"
|
||||||
attach: "Create event"
|
attach: "Create event"
|
||||||
add_reminder: "Add reminder"
|
add_reminder: "Add reminder"
|
||||||
|
timezone:
|
||||||
|
label: Timezone
|
||||||
|
remove_timezone: No timezone (UTC)
|
||||||
reminders:
|
reminders:
|
||||||
label: "Reminders"
|
label: "Reminders"
|
||||||
recurrence:
|
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,
|
:url,
|
||||||
:name,
|
:name,
|
||||||
:reminders,
|
:reminders,
|
||||||
:recurrence
|
:recurrence,
|
||||||
|
:timezone
|
||||||
]
|
]
|
||||||
|
|
||||||
module DiscoursePostEvent
|
module DiscoursePostEvent
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,12 @@ module DiscoursePostEvent
|
||||||
end
|
end
|
||||||
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
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
require 'rrule'
|
require 'rrule'
|
||||||
|
|
||||||
class RRuleGenerator
|
class RRuleGenerator
|
||||||
def self.generate(base_rrule, starts_at)
|
def self.generate(base_rrule, starts_at, tzid: 'UTC')
|
||||||
rrule = generate_hash(base_rrule)
|
rrule = generate_hash(base_rrule)
|
||||||
rrule = set_mandatory_options(rrule, starts_at)
|
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)
|
.between(Time.current, Time.current + 2.months)
|
||||||
.first
|
.first
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -551,8 +551,8 @@ after_initialize do
|
||||||
fragment.css('.discourse-post-event').each do |event_node|
|
fragment.css('.discourse-post-event').each do |event_node|
|
||||||
starts_at = event_node['data-start']
|
starts_at = event_node['data-start']
|
||||||
ends_at = event_node['data-end']
|
ends_at = event_node['data-end']
|
||||||
dates = "#{starts_at} (UTC)"
|
dates = "#{starts_at} (#{event_node['data-timezone'] || 'UTC'})"
|
||||||
dates = "#{dates} → #{ends_at} (UTC)" if ends_at
|
dates = "#{dates} → #{ends_at} (#{event_node['data-timezone'] || 'UTC'})" if ends_at
|
||||||
|
|
||||||
event_name = event_node['data-name'] || post.topic.title
|
event_name = event_node['data-name'] || post.topic.title
|
||||||
event_node.replace <<~TXT
|
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
|
it 'delete previous notifications before creating a new one for invites' do
|
||||||
going_user = Fabricate(:user)
|
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.update!(recurrence: 'every_month')
|
||||||
|
|
||||||
post_event_1.set_next_date
|
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'))
|
expect(post_event_1.starts_at).to eq_time(Time.zone.parse('2020-09-14 19:00'))
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,21 @@ describe PrettyText do
|
||||||
HTML
|
HTML
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,25 @@ describe RRuleGenerator do
|
||||||
end
|
end
|
||||||
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
|
context 'every day' do
|
||||||
let(:sample_rrule) { 'FREQ=DAILY' }
|
let(:sample_rrule) { 'FREQ=DAILY' }
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue