Site setting for all-day event times (#13)
This commit is contained in:
parent
e735f87d5b
commit
fc7e79a07d
|
@ -397,6 +397,7 @@ function initializeDiscourseCalendar(api) {
|
|||
if ($timezonePicker.length) {
|
||||
$timezonePicker.on("change", function(event) {
|
||||
calendar.setOption("timeZone", event.target.value);
|
||||
_insertAddToCalendarLinks(calendar);
|
||||
});
|
||||
|
||||
moment.tz.names().forEach(timezone => {
|
||||
|
@ -412,30 +413,63 @@ function initializeDiscourseCalendar(api) {
|
|||
function _insertAddToCalendarLinks(info) {
|
||||
if (info.view.type !== "listNextYear") return;
|
||||
|
||||
let eventSegments = info.view.eventRenderer.segs;
|
||||
const eventSegments = info.view.eventRenderer.segs;
|
||||
const eventSegmentDefMap = _eventSegmentDefMap(info);
|
||||
|
||||
for (const event of eventSegments) {
|
||||
_insertAddToCalendarLinkForEvent(event);
|
||||
_insertAddToCalendarLinkForEvent(event, eventSegmentDefMap);
|
||||
}
|
||||
}
|
||||
|
||||
function _insertAddToCalendarLinkForEvent(event) {
|
||||
const container = event.el.querySelector(".fc-list-item-title");
|
||||
const startDate = _formatDateForGoogleApi(event.start);
|
||||
const endDate = _formatDateForGoogleApi(event.end);
|
||||
function _insertAddToCalendarLinkForEvent(event, eventSegmentDefMap) {
|
||||
const eventTitle = event.eventRange.def.title;
|
||||
let map = eventSegmentDefMap[event.eventRange.def.defId];
|
||||
let startDate = map.start;
|
||||
let endDate = map.end;
|
||||
|
||||
endDate = endDate
|
||||
? _formatDateForGoogleApi(endDate, event.eventRange.def.allDay)
|
||||
: _endDateForAllDayEvent(startDate, event.eventRange.def.allDay);
|
||||
startDate = _formatDateForGoogleApi(startDate, event.eventRange.def.allDay);
|
||||
|
||||
const link = document.createElement("a");
|
||||
const title = I18n.t("discourse_calendar.add_to_calendar");
|
||||
link.title = title;
|
||||
link.appendChild(document.createTextNode(title));
|
||||
link.href = `http://www.google.com/calendar/event?action=TEMPLATE&text=${encodeURIComponent(
|
||||
container.childNodes[0].innerHTML
|
||||
)}&dates=${startDate}/${endDate}`;
|
||||
link.href = `
|
||||
http://www.google.com/calendar/event?action=TEMPLATE&text=${encodeURIComponent(
|
||||
eventTitle
|
||||
)}&dates=${startDate}/${endDate}`;
|
||||
link.target = "_blank";
|
||||
link.classList.add("fc-list-item-add-to-calendar");
|
||||
container.appendChild(link);
|
||||
event.el.querySelector(".fc-list-item-title").appendChild(link);
|
||||
}
|
||||
|
||||
function _formatDateForGoogleApi(date) {
|
||||
return date.toISOString().replace(/-|:|\.\d\d\d/g, "");
|
||||
function _formatDateForGoogleApi(date, allDay = false) {
|
||||
if (!allDay) return date.toISOString().replace(/-|:|\.\d\d\d/g, "");
|
||||
|
||||
return moment(date)
|
||||
.utc()
|
||||
.format("YYYYMMDD");
|
||||
}
|
||||
|
||||
function _endDateForAllDayEvent(startDate, allDay) {
|
||||
const unit = allDay ? "days" : "hours";
|
||||
return _formatDateForGoogleApi(
|
||||
moment(startDate)
|
||||
.add(1, unit)
|
||||
.toDate(),
|
||||
allDay
|
||||
);
|
||||
}
|
||||
|
||||
function _eventSegmentDefMap(info) {
|
||||
let map = {};
|
||||
|
||||
for (let event of info.view.calendar.getEvents()) {
|
||||
map[event._instance.defId] = { start: event.start, end: event.end };
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ en:
|
|||
calendar_enabled: "Enable the discourse-calendar plugin. This will add support for a [calendar][/calendar] tag in the first post of a topic."
|
||||
holiday_calendar_topic_id: "Topic ID of staffs holiday / absence calendar."
|
||||
delete_expired_event_posts_after: "Posts with expired events will be automatically deleted after (n) hours. Set to -1 to disable deletion."
|
||||
all_day_event_start_time: "Events that do not have a start time specified will start at this time. Format is HH:mm. For 6:00 am, enter 06:00"
|
||||
all_day_event_end_time: "Events that do not have a end time specified will end at this time. Format is HH:mm. For 6:00 pm, enter 18:00"
|
||||
all_day_event_time_error: "Invalid time. Format needs to be HH:mm (ex: 08:00)."
|
||||
discourse_calendar:
|
||||
calendar_must_be_in_first_post: "Calendar tag can only be used in first post of a topic."
|
||||
more_than_one_calendar: "You can’t have more than one calendar in a post."
|
||||
|
|
|
@ -8,3 +8,12 @@ plugins:
|
|||
delete_expired_event_posts_after:
|
||||
min: -1
|
||||
default: 1
|
||||
all_day_event_start_time:
|
||||
default: ""
|
||||
client: true
|
||||
validator: "CalendarSettingsValidator"
|
||||
all_day_event_end_time:
|
||||
default: ""
|
||||
client: true
|
||||
validator: "CalendarSettingsValidator"
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ module Jobs
|
|||
business_days = 1..5
|
||||
load_until = 6.months.from_now
|
||||
today = Date.today
|
||||
holiday_hour = ::DiscourseCalendar::BEGINNING_OF_DAY_HOUR
|
||||
|
||||
users_in_region.keys.sort.each do |region|
|
||||
holidays = Holidays.between(today, load_until, [region]).filter do |h|
|
||||
|
@ -56,7 +55,8 @@ module Jobs
|
|||
holidays.each do |next_holiday|
|
||||
users_in_region[region].each do |user_id|
|
||||
date = if tz = user_timezones[user_id]
|
||||
datetime = next_holiday[:date].in_time_zone(tz).change(hour: holiday_hour)
|
||||
datetime = next_holiday[:date].in_time_zone(tz)
|
||||
datetime = datetime.change(holiday_hour_adjustment) if holiday_hour_adjustment
|
||||
datetime.iso8601
|
||||
else
|
||||
next_holiday[:date].to_s
|
||||
|
@ -73,5 +73,15 @@ module Jobs
|
|||
op.save_custom_fields(true)
|
||||
end
|
||||
end
|
||||
|
||||
def holiday_hour_adjustment
|
||||
@holiday_hour ||= begin
|
||||
return false if SiteSetting.all_day_event_start_time.empty? || SiteSetting.all_day_event_end_time.empty?
|
||||
{
|
||||
hour: SiteSetting.all_day_event_start_time.split(':').first,
|
||||
min: SiteSetting.all_day_event_start_time.split(':').second
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CalendarSettingsValidator
|
||||
|
||||
def initialize(opts = {})
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def valid_value?(val)
|
||||
return true if val == ""
|
||||
|
||||
split = val.split(':')
|
||||
return false if split.count != 2
|
||||
|
||||
hour = split.first
|
||||
return false if hour.length != 2
|
||||
return false unless hour.to_i >= 0 && hour.to_i < 24
|
||||
|
||||
minutes = split.second
|
||||
return false if minutes.length != 2
|
||||
return false unless minutes.to_i >= 0 && minutes.to_i < 60
|
||||
true
|
||||
end
|
||||
|
||||
def error_message
|
||||
I18n.t('site_settings.all_day_event_time_error')
|
||||
end
|
||||
end
|
|
@ -13,11 +13,15 @@ module DiscourseCalendar
|
|||
end
|
||||
|
||||
from = self.convert_to_date_time(dates[0])
|
||||
from = from.change(hour: DiscourseCalendar::BEGINNING_OF_DAY_HOUR) unless dates[0]['time']
|
||||
to = self.convert_to_date_time(dates[1]) if dates.count == 2
|
||||
|
||||
if dates.count == 2
|
||||
to = self.convert_to_date_time(dates[1])
|
||||
to = to.change(hour: DiscourseCalendar::END_OF_DAY_HOUR) unless dates[1]['time']
|
||||
unless SiteSetting.all_day_event_start_time.blank? || SiteSetting.all_day_event_end_time.blank?
|
||||
if !dates[0]['time']
|
||||
from = from.change(change_for_setting(SiteSetting.all_day_event_start_time))
|
||||
end
|
||||
if to && !dates[1]['time']
|
||||
to = to.change(change_for_setting(SiteSetting.all_day_event_end_time))
|
||||
end
|
||||
end
|
||||
|
||||
html = post.cooked
|
||||
|
@ -40,12 +44,16 @@ module DiscourseCalendar
|
|||
|
||||
def self.convert_to_date_time(value)
|
||||
timezone = value["timezone"] || "UTC"
|
||||
datetime = value['date'].to_s
|
||||
datetime << " #{value['time']}" if value['time']
|
||||
ActiveSupport::TimeZone[timezone].parse(datetime)
|
||||
end
|
||||
|
||||
if value['time']
|
||||
ActiveSupport::TimeZone[timezone].parse("#{value['date']} #{value['time']}")
|
||||
else
|
||||
ActiveSupport::TimeZone[timezone].parse(value['date'].to_s)
|
||||
end
|
||||
def self.change_for_setting(setting)
|
||||
{
|
||||
hour: setting.split(':').first,
|
||||
min: setting.split(':').second
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
19
plugin.rb
19
plugin.rb
|
@ -8,6 +8,10 @@
|
|||
|
||||
gem "holidays", "8.0.0", require: false
|
||||
|
||||
[
|
||||
"../lib/calendar_settings_validator.rb",
|
||||
].each { |path| load File.expand_path(path, __FILE__) }
|
||||
|
||||
enabled_site_setting :calendar_enabled
|
||||
|
||||
register_asset "stylesheets/vendor/fullcalendar.min.css"
|
||||
|
@ -33,9 +37,6 @@ after_initialize do
|
|||
|
||||
USER_OPTIONS_TIMEZONE_ENABLED = UserOption.column_names.include?('timezone') rescue nil
|
||||
|
||||
BEGINNING_OF_DAY_HOUR = 6
|
||||
END_OF_DAY_HOUR = 18
|
||||
|
||||
def self.users_on_holiday
|
||||
PluginStore.get(PLUGIN_NAME, USERS_ON_HOLIDAY_KEY)
|
||||
end
|
||||
|
@ -67,6 +68,18 @@ after_initialize do
|
|||
register_editable_user_custom_field(DiscourseCalendar::TIMEZONE_CUSTOM_FIELD)
|
||||
end
|
||||
|
||||
DiscourseEvent.on(:site_setting_changed) do |name, old_value, new_value|
|
||||
next unless [:all_day_event_start_time, :all_day_event_end_time].include? name
|
||||
|
||||
post_ids = PostCustomField.where(name: DiscourseCalendar::CALENDAR_DETAILS_CUSTOM_FIELD).pluck(:post_id)
|
||||
Post.where(id: post_ids).each do |topic_post|
|
||||
Post.where(topic_id: topic_post.topic_id).each do |post|
|
||||
DiscourseCalendar::EventUpdater.update(post)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class DiscourseCalendar::Calendar
|
||||
class << self
|
||||
def extract(post)
|
||||
|
|
|
@ -71,9 +71,33 @@ describe DiscourseCalendar::CheckNextRegionalHolidays do
|
|||
@op.reload
|
||||
|
||||
expect(@op.calendar_holidays[0]).to eq(
|
||||
["fr", "Assomption", "2019-08-15T06:00:00+02:00", frenchy.username]
|
||||
["fr", "Assomption", "2019-08-15T00:00:00+02:00", frenchy.username]
|
||||
)
|
||||
end
|
||||
|
||||
describe "with all day event start and end time" do
|
||||
before do
|
||||
SiteSetting.all_day_event_start_time = "06:00"
|
||||
SiteSetting.all_day_event_end_time = "18:00"
|
||||
end
|
||||
|
||||
it "uses the user TZ when available" do
|
||||
frenchy = Fabricate(:user)
|
||||
frenchy.custom_fields[DiscourseCalendar::REGION_CUSTOM_FIELD] = "fr"
|
||||
frenchy.user_option.timezone = "Europe/Paris"
|
||||
frenchy.user_option.save!
|
||||
frenchy.save!
|
||||
|
||||
freeze_time Time.zone.local(2019, 8, 1)
|
||||
|
||||
subject.execute(nil)
|
||||
@op.reload
|
||||
|
||||
expect(@op.calendar_holidays[0]).to eq(
|
||||
["fr", "Assomption", "2019-08-15T06:00:00+02:00", frenchy.username]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when user_options.timezone column does NOT exist" do
|
||||
|
@ -95,9 +119,32 @@ describe DiscourseCalendar::CheckNextRegionalHolidays do
|
|||
@op.reload
|
||||
|
||||
expect(@op.calendar_holidays[0]).to eq(
|
||||
["fr", "Assomption", "2019-08-15T06:00:00+02:00", frenchy.username]
|
||||
["fr", "Assomption", "2019-08-15T00:00:00+02:00", frenchy.username]
|
||||
)
|
||||
end
|
||||
|
||||
describe "with all day event start and end time" do
|
||||
before do
|
||||
SiteSetting.all_day_event_start_time = "06:00"
|
||||
SiteSetting.all_day_event_end_time = "18:00"
|
||||
end
|
||||
|
||||
it "uses the users custom fields" do
|
||||
frenchy = Fabricate(:user)
|
||||
frenchy.custom_fields[DiscourseCalendar::REGION_CUSTOM_FIELD] = "fr"
|
||||
frenchy.custom_fields[DiscourseCalendar::TIMEZONE_CUSTOM_FIELD] = "Europe/Paris"
|
||||
frenchy.save!
|
||||
|
||||
freeze_time Time.zone.local(2019, 8, 1)
|
||||
|
||||
subject.execute(nil)
|
||||
|
||||
@op.reload
|
||||
expect(@op.calendar_holidays[0]).to eq(
|
||||
["fr", "Assomption", "2019-08-15T06:00:00+02:00", frenchy.username]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "only takes into account active users" do
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
require "rails_helper"
|
||||
|
||||
describe CalendarSettingsValidator do
|
||||
def expect_invalid(val)
|
||||
expect(subject.valid_value?(val)).to eq(false)
|
||||
end
|
||||
|
||||
def expect_valid(val)
|
||||
expect(subject.valid_value?(val)).to eq(true)
|
||||
end
|
||||
|
||||
it "only allows valid HH:mm formats" do
|
||||
expect_invalid "markvanlan"
|
||||
expect_invalid "000:00"
|
||||
expect_invalid "00:000"
|
||||
expect_invalid "24:00"
|
||||
expect_invalid "00:60"
|
||||
expect_invalid "002:30"
|
||||
|
||||
expect_valid ""
|
||||
expect_valid "00:00"
|
||||
expect_valid "23:00"
|
||||
expect_valid "00:59"
|
||||
expect_valid "23:59"
|
||||
expect_valid "06:40"
|
||||
end
|
||||
end
|
|
@ -21,7 +21,7 @@ describe "Dynamic calendar" do
|
|||
|
||||
op.reload
|
||||
expect(op.calendar_details[p.post_number.to_s]).to eq([
|
||||
"Rome", "2018-06-05T06:00:00+02:00", nil, p.user.username, nil
|
||||
"Rome", "2018-06-05T00:00:00+02:00", nil, p.user.username, nil
|
||||
])
|
||||
end
|
||||
|
||||
|
@ -34,12 +34,13 @@ describe "Dynamic calendar" do
|
|||
])
|
||||
end
|
||||
|
||||
|
||||
it "adds an entry with a range event" do
|
||||
p = create_post(topic: op.topic, raw: 'Rome [date="2018-06-05" timezone="Europe/Paris"] → [date="2018-06-08" timezone="Europe/Paris"]')
|
||||
|
||||
op.reload
|
||||
expect(op.calendar_details[p.post_number.to_s]).to eq([
|
||||
"Rome", "2018-06-05T06:00:00+02:00", "2018-06-08T18:00:00+02:00", p.user.username, nil
|
||||
"Rome", "2018-06-05T00:00:00+02:00", "2018-06-08T00:00:00+02:00", p.user.username, nil
|
||||
])
|
||||
end
|
||||
|
||||
|
@ -74,4 +75,38 @@ describe "Dynamic calendar" do
|
|||
expect(op.calendar_details).to be_empty
|
||||
end
|
||||
|
||||
describe "with all day event start and end time" do
|
||||
before do
|
||||
SiteSetting.all_day_event_start_time = "07:00"
|
||||
SiteSetting.all_day_event_end_time = "18:00"
|
||||
end
|
||||
|
||||
it "adds an entry with a single date event" do
|
||||
p = create_post(topic: op.topic, raw: 'Rome [date="2018-06-05" timezone="Europe/Paris"]')
|
||||
|
||||
op.reload
|
||||
expect(op.calendar_details[p.post_number.to_s]).to eq([
|
||||
"Rome", "2018-06-05T07:00:00+02:00", nil, p.user.username, nil
|
||||
])
|
||||
end
|
||||
|
||||
it "adds an entry with a single date/time event" do
|
||||
p = create_post(topic: op.topic, raw: 'Rome [date="2018-06-05" time="12:34:56"]')
|
||||
|
||||
op.reload
|
||||
expect(op.calendar_details[p.post_number.to_s]).to eq([
|
||||
"Rome", "2018-06-05T12:34:56Z", nil, p.user.username, nil
|
||||
])
|
||||
end
|
||||
|
||||
|
||||
it "adds an entry with a range event" do
|
||||
p = create_post(topic: op.topic, raw: 'Rome [date="2018-06-05" timezone="Europe/Paris"] → [date="2018-06-08" timezone="Europe/Paris"]')
|
||||
|
||||
op.reload
|
||||
expect(op.calendar_details[p.post_number.to_s]).to eq([
|
||||
"Rome", "2018-06-05T07:00:00+02:00", "2018-06-08T18:00:00+02:00", p.user.username, nil
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe DiscourseCalendar::EventUpdater do
|
||||
before { SiteSetting.calendar_enabled = true }
|
||||
before do
|
||||
SiteSetting.calendar_enabled = true
|
||||
SiteSetting.all_day_event_start_time = ""
|
||||
SiteSetting.all_day_event_end_time = ""
|
||||
end
|
||||
|
||||
it "will correctly update the associated first post calendar details" do
|
||||
op = create_post(raw: "[calendar]\n[/calendar]")
|
||||
|
@ -53,8 +57,8 @@ describe DiscourseCalendar::EventUpdater do
|
|||
op.reload
|
||||
|
||||
_, from, to = op.calendar_details[post.post_number.to_s]
|
||||
expect(from).to eq("2018-06-05T06:00:00Z")
|
||||
expect(to).to eq("2018-06-11T18:00:00Z")
|
||||
expect(from).to eq("2018-06-05T00:00:00Z")
|
||||
expect(to).to eq("2018-06-11T00:00:00Z")
|
||||
end
|
||||
|
||||
it "will work with timezone" do
|
||||
|
@ -67,7 +71,7 @@ describe DiscourseCalendar::EventUpdater do
|
|||
op.reload
|
||||
|
||||
_, from, to = op.calendar_details[post.post_number.to_s]
|
||||
expect(from).to eq("2018-06-05T06:00:00+02:00")
|
||||
expect(from).to eq("2018-06-05T00:00:00+02:00")
|
||||
expect(to).to eq("2018-06-11T13:45:33-07:00")
|
||||
end
|
||||
|
||||
|
@ -82,4 +86,38 @@ describe DiscourseCalendar::EventUpdater do
|
|||
|
||||
expect(post).to be_valid
|
||||
end
|
||||
|
||||
describe "all day event site settings" do
|
||||
before do
|
||||
SiteSetting.all_day_event_start_time = "06:30"
|
||||
SiteSetting.all_day_event_end_time = "18:00"
|
||||
end
|
||||
|
||||
it "will work with no time date" do
|
||||
op = create_post(raw: "[calendar]\n[/calendar]")
|
||||
|
||||
raw = %{Rome [date="2018-06-05"] [date="2018-06-11"]}
|
||||
post = create_post(raw: raw, topic: op.topic)
|
||||
CookedPostProcessor.new(post).post_process
|
||||
|
||||
op.reload
|
||||
|
||||
_, from, to = op.calendar_details[post.post_number.to_s]
|
||||
expect(from).to eq("2018-06-05T06:30:00Z")
|
||||
expect(to).to eq("2018-06-11T18:00:00Z")
|
||||
end
|
||||
it "will work with timezone" do
|
||||
op = create_post(raw: "[calendar]\n[/calendar]")
|
||||
|
||||
raw = %{Rome [date="2018-06-05" timezone="Europe/Paris"] [date="2018-06-11" time="13:45:33" timezone="America/Los_Angeles"]}
|
||||
post = create_post(raw: raw, topic: op.topic)
|
||||
CookedPostProcessor.new(post).post_process
|
||||
|
||||
op.reload
|
||||
|
||||
_, from, to = op.calendar_details[post.post_number.to_s]
|
||||
expect(from).to eq("2018-06-05T06:30:00+02:00")
|
||||
expect(to).to eq("2018-06-11T13:45:33-07:00")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue