Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
b7f40b8
DEV: full calendar v6
jjaffeux Jul 21, 2025
521b250
improves recurrence computation
jjaffeux Aug 8, 2025
f3373c1
linting
jjaffeux Aug 11, 2025
37fd1b9
tests
jjaffeux Aug 11, 2025
ecac242
fix all js tests
jjaffeux Aug 11, 2025
7bcebc7
fix event preview
jjaffeux Aug 11, 2025
1827b61
linting
jjaffeux Aug 12, 2025
054e9d3
correct event_finder specs
jjaffeux Aug 12, 2025
e16df96
one serializer to rule them all
jjaffeux Aug 12, 2025
b87b258
fix remaining specs
jjaffeux Aug 12, 2025
88a0283
wrong selector
jjaffeux Aug 12, 2025
fc95ebb
remove unused spec
jjaffeux Aug 12, 2025
241ca64
bail out if no setting
jjaffeux Aug 12, 2025
7257cf5
close event UI/dtstart event/local dates in calendar
jjaffeux Aug 13, 2025
1409be5
correctly handle mine
jjaffeux Aug 13, 2025
5ada292
downcase invites
jjaffeux Aug 13, 2025
5b91464
optimize perf
jjaffeux Aug 13, 2025
957ddde
UX: adjust z-index of for mobile devices post-event-more-menu
chapoi Aug 13, 2025
05fc416
typo
jjaffeux Aug 13, 2025
16a231e
simpler event preview
jjaffeux Aug 13, 2025
bc8a914
no loading indicator to avoid jumping
jjaffeux Aug 13, 2025
a619725
s/list/year
jjaffeux Aug 13, 2025
24332b6
buttons state
jjaffeux Aug 13, 2025
9f88dbd
more persistent routing
jjaffeux Aug 13, 2025
9b69f11
better state
jjaffeux Aug 13, 2025
62115ea
better query params
jjaffeux Aug 13, 2025
0ee0deb
ensures we clear finished at when setting new dates
jjaffeux Aug 14, 2025
cfc5a55
do not rely on event dates for event finder
jjaffeux Aug 14, 2025
45d640b
default to show description
jjaffeux Aug 14, 2025
8362c92
fix category calendar spec
jjaffeux Aug 14, 2025
15afeaf
better event display
jjaffeux Aug 14, 2025
852e24b
lock
jjaffeux Aug 18, 2025
6dcb0aa
linting
jjaffeux Aug 18, 2025
e30e0d1
refactor event.rb
jjaffeux Aug 18, 2025
667c665
linting
jjaffeux Aug 18, 2025
a41470d
fix spec
jjaffeux Aug 18, 2025
c99ac9d
fixing specs
jjaffeux Aug 18, 2025
b416b6d
fixing specs
jjaffeux Aug 18, 2025
78fb890
fix spec
jjaffeux Aug 18, 2025
1b1f65e
minor tweaks
jjaffeux Aug 18, 2025
c6fed4d
only one at a time
jjaffeux Aug 18, 2025
1001f51
more reliable tooltip/menu
jjaffeux Aug 19, 2025
c26973f
wip
jjaffeux Aug 19, 2025
ec3a14b
fixing dates
jjaffeux Aug 19, 2025
3a6071f
Update plugins/discourse-calendar/app/serializers/discourse_post_even…
jjaffeux Aug 19, 2025
c0c2570
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
53e2204
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
5058561
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
2d38333
Update plugins/discourse-calendar/assets/javascripts/discourse/initia…
jjaffeux Aug 19, 2025
6fbcb3a
Update plugins/discourse-calendar/assets/javascripts/discourse/initia…
jjaffeux Aug 19, 2025
bb4117c
Update plugins/discourse-calendar/lib/discourse_post_event/event_find…
jjaffeux Aug 19, 2025
4d13b7f
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
6cce7dd
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
dd4306d
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
830e818
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
d1635f5
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
a3f9faa
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
bbaea2d
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
0535c11
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
b6159d0
Update plugins/discourse-calendar/assets/javascripts/discourse/compon…
jjaffeux Aug 19, 2025
feb5cda
remove site settings
jjaffeux Aug 19, 2025
ab7617d
fixes
jjaffeux Aug 19, 2025
c43c7b1
removing code
jjaffeux Aug 19, 2025
d033094
better UX
jjaffeux Aug 19, 2025
5c886a1
linting
jjaffeux Aug 19, 2025
44b1592
not used
jjaffeux Aug 19, 2025
2d50a55
skip test
jjaffeux Aug 19, 2025
8627df8
fix timezone
jjaffeux Aug 19, 2025
e6734a1
tweak loading state
jjaffeux Aug 19, 2025
ba2531d
center on mobile
jjaffeux Aug 19, 2025
3f1c021
linting
jjaffeux Aug 20, 2025
0c9430c
use route for loading events
jjaffeux Aug 20, 2025
cfbd1bd
DEV: full calendar v6
jjaffeux Jul 21, 2025
2794795
improves recurrence computation
jjaffeux Aug 8, 2025
7219c30
linting
jjaffeux Aug 12, 2025
3557402
one serializer to rule them all
jjaffeux Aug 12, 2025
893cad1
correctly handle mine
jjaffeux Aug 13, 2025
ffd3f9c
simpler event preview
jjaffeux Aug 13, 2025
d7dd763
buttons state
jjaffeux Aug 13, 2025
ab743d5
better query params
jjaffeux Aug 13, 2025
46b8391
do not rely on event dates for event finder
jjaffeux Aug 14, 2025
3ec8041
linting
jjaffeux Aug 18, 2025
6a16074
refactor event.rb
jjaffeux Aug 18, 2025
a9b8793
wip
jjaffeux Aug 19, 2025
d0da3ff
fixes
jjaffeux Aug 19, 2025
5690b1b
better UX
jjaffeux Aug 19, 2025
29f1d06
linting
jjaffeux Aug 19, 2025
cdc4165
tweak loading state
jjaffeux Aug 19, 2025
fde22b9
center on mobile
jjaffeux Aug 19, 2025
e614be1
import service
jjaffeux Aug 20, 2025
6d71fff
fix arrow
jjaffeux Aug 20, 2025
2e052ec
show invitees/no route history
jjaffeux Aug 20, 2025
11193e1
writing specs
jjaffeux Aug 21, 2025
c866bd2
more specs
jjaffeux Aug 21, 2025
bb9f7e1
linting
jjaffeux Aug 21, 2025
03cba30
fixes
jjaffeux Aug 21, 2025
cfec3ee
fixes infinite loop
jjaffeux Aug 21, 2025
7088666
fix tests
jjaffeux Aug 21, 2025
0419dc2
duplicate
jjaffeux Aug 21, 2025
99d225d
mouseover and margin
jjaffeux Aug 21, 2025
8a89ca2
better order
jjaffeux Aug 21, 2025
a4bcd8a
no event source
jjaffeux Aug 21, 2025
44d467d
fix spec
jjaffeux Aug 22, 2025
b5cd86a
dead code
jjaffeux Aug 22, 2025
0608f85
cleaning
jjaffeux Aug 22, 2025
e50ea56
fix spec
jjaffeux Aug 22, 2025
3910999
better urls
jjaffeux Aug 22, 2025
31ef143
linting
jjaffeux Aug 22, 2025
484f443
fix tests
jjaffeux Aug 22, 2025
da2afb0
minimum padding bottom
jjaffeux Aug 22, 2025
ba959dc
better change date heuristic
jjaffeux Aug 22, 2025
a8124b4
fixes infinite loop
jjaffeux Aug 22, 2025
2818653
test navigation
jjaffeux Aug 25, 2025
d099742
better copy
jjaffeux Aug 25, 2025
54d4e02
fix infinite loop
jjaffeux Aug 25, 2025
b97632f
fix month to week
jjaffeux Aug 26, 2025
d03f5a0
better navigation
jjaffeux Aug 26, 2025
5ec5caa
pass currentDay
jjaffeux Aug 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class DownloadCalendar extends Component {
this.args.model.calendar.title,
this.args.model.calendar.dates,
{
recurrenceRule: this.args.model.calendar.recurrenceRule,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just using the same rrule name everywhere

rrule: this.args.model.calendar.rrule,
location: this.args.model.calendar.location,
details: this.args.model.calendar.details,
}
Expand All @@ -40,7 +40,7 @@ export default class DownloadCalendar extends Component {
this.args.model.calendar.title,
this.args.model.calendar.dates,
{
recurrenceRule: this.args.model.calendar.recurrenceRule,
rrule: this.args.model.calendar.rrule,
location: this.args.model.calendar.location,
details: this.args.model.calendar.details,
}
Expand Down
8 changes: 4 additions & 4 deletions app/assets/javascripts/discourse/app/lib/download-calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ export function downloadGoogle(title, dates, options = {}) {
)}`
);

if (options.recurrenceRule) {
link.searchParams.append("recur", `RRULE:${options.recurrenceRule}`);
if (options.rrule) {
link.searchParams.append("recur", `RRULE:${options.rrule}`);
}

if (options.location) {
Expand Down Expand Up @@ -88,7 +88,7 @@ export function generateIcsData(title, dates, options = {}) {
`DTSTAMP:${moment().utc().format("YMMDDTHHmmss")}Z\n` +
`DTSTART:${startDate.utc().format("YMMDDTHHmmss")}Z\n` +
`DTEND:${endDate.utc().format("YMMDDTHHmmss")}Z\n` +
(options.recurrenceRule ? `RRULE:${options.recurrenceRule}\n` : ``) +
(options.rrule ? `RRULE:${options.rrule}\n` : ``) +
(options.location ? `LOCATION:${options.location}\n` : ``) +
(options.details ? `DESCRIPTION:${options.details}\n` : ``) +
`SUMMARY:${title}\n` +
Expand All @@ -106,7 +106,7 @@ function _displayModal(title, dates, options = {}) {
calendar: {
title,
dates,
recurrenceRule: options.recurrenceRule,
rrule: options.rrule,
location: options.location,
details: options.details,
},
Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/discourse/app/lib/plugin-api.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -2457,7 +2457,7 @@ class PluginApi {
* endsAt: "2021-10-12T16:00:00.000Z",
* },
* ],
* { recurrenceRule: "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR", location: "Paris", details: "Foo" }
* { rrule: "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR", location: "Paris", details: "Foo" }
* );
* ```
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { Calendar } from "@fullcalendar/core";
export { default as DayGrid } from "@fullcalendar/daygrid";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file allows to efficiently load the full calendar when needed

export { default as TimeGrid } from "@fullcalendar/timegrid";
export { default as List } from "@fullcalendar/list";
export { default as RRULE } from "@fullcalendar/rrule";
export { default as MomentTimezone } from "@fullcalendar/moment-timezone";
5 changes: 4 additions & 1 deletion app/assets/javascripts/discourse/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"@fullcalendar/core": "^6.1.18",
"@fullcalendar/daygrid": "^6.1.18",
"@fullcalendar/list": "^6.1.18",
"@fullcalendar/moment-timezone": "^6.1.19",
"@fullcalendar/rrule": "^6.1.18",
"@fullcalendar/timegrid": "^6.1.18",
"@glimmer/syntax": "0.93.1",
"@highlightjs/cdn-assets": "11.11.1",
Expand Down Expand Up @@ -58,7 +60,8 @@
"prosemirror-schema-list": "^1.5.1",
"prosemirror-state": "^1.4.3",
"prosemirror-transform": "^1.10.4",
"prosemirror-view": "^1.40.0"
"prosemirror-view": "^1.40.0",
"rrule": "^2.8.1"
},
"devDependencies": {
"@babel/core": "^7.28.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module("Unit | Utility | download-calendar", function (hooks) {
},
],
{
recurrenceRule: "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR",
rrule: "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR",
location: "Paris",
details: "Good soup",
}
Expand Down Expand Up @@ -74,7 +74,7 @@ END:VCALENDAR`
endsAt: "2021-10-12T16:00:00.000Z",
},
],
{ recurrenceRule: "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR" }
{ rrule: "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR" }
);
assert.strictEqual(
data,
Expand Down Expand Up @@ -121,7 +121,7 @@ END:VCALENDAR`
endsAt: "2021-10-12T16:00:00.000Z",
},
],
{ recurrenceRule: "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR" }
{ rrule: "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR" }
);
assert.true(
window.open.calledWith(
Expand Down
8 changes: 4 additions & 4 deletions app/assets/stylesheets/common/float-kit/d-tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@

&[data-placement^="top"] {
.arrow {
bottom: -10px;
bottom: -11px;
rotate: 180deg;
}
}
Expand All @@ -82,21 +82,21 @@

&[data-placement^="bottom"] {
.arrow {
top: -10px;
top: -11px;
}
}

&[data-placement^="right"] {
.arrow {
rotate: -90deg;
left: -10px;
left: -11px;
}
}

&[data-placement^="left"] {
.arrow {
rotate: 90deg;
right: -10px;
right: -11px;
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [1.24.0] - 2024-01-08

- Added `addAdminSidebarSectionLink` which is used to add a link to a specific admin sidebar section, as a replacement for the `admin-menu` plugin outlet.
- Added `addAdminSidebarSectionLink` which is used to add a link to a specific admin sidebar section, as a replacement for the `admin-menu` plugin outlet.

## [1.23.0] - 2024-01-03

Expand Down Expand Up @@ -154,7 +154,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added `recurrenceRule` option to `downloadCalendar`, this can be used to set recurring events in the calendar. Rule syntax can be found at https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10.
- Added `rrule` option to `downloadCalendar`, this can be used to set recurring events in the calendar. Rule syntax can be found at https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10.

## [1.15.0] - 2023-10-18

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ def index
)

# The detailed serializer is currently not used anywhere in the frontend, but available via API
serializer = params[:include_details] == "true" ? EventSerializer : EventSummarySerializer
serializer = params[:include_details] == "true" ? EventSerializer : BasicEventSerializer

render json:
ActiveModel::ArraySerializer.new(
@events,
each_serializer: serializer,
each_serializer: BasicEventSerializer,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

always using the same serializer for lists, technically that could break stuff, but I didn't find anything

Copy link
Contributor

@renato renato Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has potential to strip fields that our frontend isn't using, but customers are relying on externally (not sure, I didn't compare field-by-field yet). Context from Meta: /t/-/287566/14

scope: guardian,
).as_json
end
Expand Down Expand Up @@ -124,6 +124,8 @@ def filtered_events_params
:limit,
:before,
:attending_user,
:before,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only had before, I added after to be able to have a range

:after,
)
end
end
Expand Down
111 changes: 67 additions & 44 deletions plugins/discourse-calendar/app/models/discourse_post_event/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,40 +59,19 @@ def create_or_update_event_date

return if !starts_at_changed && !ends_at_changed

event_dates.update_all(finished_at: Time.current)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was fixing a bug introduced months ago which was causing recurring events to be marke as finished

set_next_date
end

def set_next_date
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a full refactoring of these methods

next_dates = calculate_next_date
return if !next_dates
next_date_result = calculate_next_date

date_args = { starts_at: next_dates[:starts_at], ends_at: next_dates[:ends_at] }
if next_dates[:ends_at] && next_dates[:ends_at] < Time.current
date_args[:finished_at] = next_dates[:ends_at]
end

existing_date = event_dates.find_by(starts_at: date_args[:starts_at])

if existing_date && existing_date.ends_at == date_args[:ends_at] &&
existing_date.finished_at == date_args[:finished_at]
# exact same state in DB, this is a dupe call, return early
return
end

if existing_date
existing_date.update!(date_args)
else
event_dates.create!(date_args)
end

invitees.where.not(status: Invitee.statuses[:going]).update_all(status: nil, notified: false)

if !next_dates[:rescheduled]
notify_invitees!
notify_missing_invitees!
end
return event_dates.update_all(finished_at: Time.current) if next_date_result.nil?

starts_at, ends_at = next_date_result
finish_previous_event_dates(starts_at) if dates_changed?
upsert_event_date(starts_at, ends_at)
reset_invitee_notifications
notify_if_new_event
publish_update!
end

Expand Down Expand Up @@ -400,27 +379,71 @@ def chat_channel_sync
end

def calculate_next_date
if self.recurrence.blank? || original_starts_at > Time.current
return { starts_at: original_starts_at, ends_at: original_ends_at, rescheduled: false }
if recurrence.blank? || original_starts_at > Time.current
return original_starts_at, original_ends_at
end

next_starts_at =
RRuleGenerator.generate(
starts_at: original_starts_at.in_time_zone(timezone),
timezone:,
recurrence:,
recurrence_until:,
).first
return unless next_starts_at

if original_ends_at
difference = original_ends_at - original_starts_at
next_ends_at = next_starts_at + difference.seconds
next_starts_at = calculate_next_recurring_date
return nil unless next_starts_at

next_ends_at = original_ends_at ? next_starts_at + event_duration : nil
[next_starts_at, next_ends_at]
end

private

def dates_changed?
saved_change_to_original_starts_at || saved_change_to_original_ends_at
end

def finish_previous_event_dates(current_starts_at)
existing_date = event_dates.find_by(starts_at: current_starts_at)
event_dates
.where.not(id: existing_date&.id)
.where(finished_at: nil)
.update_all(finished_at: Time.current)
end

def upsert_event_date(starts_at, ends_at)
finished_at = ends_at && ends_at < Time.current ? ends_at : nil

existing_date = event_dates.find_by(starts_at:)

if existing_date
# Only update if something actually changed
unless existing_date.ends_at == ends_at && existing_date.finished_at == finished_at
existing_date.update!(ends_at:, finished_at:)
end
else
next_ends_at = nil
event_dates.create!(starts_at:, ends_at:, finished_at:)
end
end

def reset_invitee_notifications
invitees.where.not(status: Invitee.statuses[:going]).update_all(status: nil, notified: false)
end

def notify_if_new_event
is_generating_future_recurrence = recurrence.present? && original_starts_at <= Time.current

unless is_generating_future_recurrence
notify_invitees!
notify_missing_invitees!
end
end

def calculate_next_recurring_date
RRuleGenerator.generate(
starts_at: original_starts_at.in_time_zone(timezone),
timezone: timezone,
recurrence: recurrence,
recurrence_until: recurrence_until,
dtstart: original_starts_at.in_time_zone(timezone),
).first
end

{ starts_at: next_starts_at, ends_at: next_ends_at, rescheduled: true }
def event_duration
original_ends_at - original_starts_at
end
end
end
Expand Down
Loading
Loading