From 9e146e49a4f692f0fa9a61330fe5c9e536ad2b16 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 16 Jun 2025 17:42:10 +1000 Subject: [PATCH 01/25] WIP: allow seeing my events in the upcoming event list --- .../discourse_post_event/events_controller.rb | 1 + .../components/upcoming-events-calendar.gjs | 25 +++++++++ ...ourse-post-event-upcoming-events-index.gjs | 33 +++++++++++ lib/discourse_post_event/event_finder.rb | 27 +++++++++ .../discourse_post_event/event_finder_spec.rb | 56 +++++++++++++++++++ 5 files changed, 142 insertions(+) diff --git a/app/controllers/discourse_post_event/events_controller.rb b/app/controllers/discourse_post_event/events_controller.rb index fb7342556..a54b0dd3c 100644 --- a/app/controllers/discourse_post_event/events_controller.rb +++ b/app/controllers/discourse_post_event/events_controller.rb @@ -123,6 +123,7 @@ def filtered_events_params :include_expired, :limit, :before, + :attending_user, ) end end diff --git a/assets/javascripts/discourse/components/upcoming-events-calendar.gjs b/assets/javascripts/discourse/components/upcoming-events-calendar.gjs index b187a1772..64fb96434 100644 --- a/assets/javascripts/discourse/components/upcoming-events-calendar.gjs +++ b/assets/javascripts/discourse/components/upcoming-events-calendar.gjs @@ -34,6 +34,7 @@ export default class UpcomingEventsCalendar extends Component { async _renderCalendar() { const siteSettings = this.site.siteSettings; + const isMobileView = this.site.mobileView; const calendarNode = document.getElementById("upcoming-events-calendar"); if (!calendarNode) { @@ -48,12 +49,36 @@ export default class UpcomingEventsCalendar extends Component { ...fullCalendarDefaultOptions(), firstDay: 1, height: "auto", + defaultView: isMobileView ? "listNextYear" : "month", + views: { + listNextYear: { + type: "list", + duration: { days: 365 }, + buttonText: "list", + listDayFormat: { + month: "long", + year: "numeric", + day: "numeric", + weekday: "long", + }, + }, + }, + header: { + left: "prev,next today", + center: "title", + right: "month,basicWeek,listNextYear", + }, eventPositioned: (info) => { if (siteSettings.events_max_rows === 0) { return; } let fcContent = info.el.querySelector(".fc-content"); + + if (!fcContent) { + return; + } + let computedStyle = window.getComputedStyle(fcContent); let lineHeight = parseInt(computedStyle.lineHeight, 10); diff --git a/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-index.gjs b/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-index.gjs index 293ab3160..a9bc9b13a 100644 --- a/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-index.gjs +++ b/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-index.gjs @@ -1,9 +1,42 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { fn } from "@ember/helper"; +import { action } from "@ember/object"; import RouteTemplate from "ember-route-template"; +import { eq } from "truth-helpers"; +import DButton from "discourse/components/d-button"; import UpcomingEventsCalendar from "../components/upcoming-events-calendar"; +class UpcomingEventsIndex extends Component { + @tracked filter = "all"; + + @action + changeFilter(newFilter) { + this.filter = newFilter; + } + + +} + export default RouteTemplate( diff --git a/lib/discourse_post_event/event_finder.rb b/lib/discourse_post_event/event_finder.rb index 2ad879852..10f91a6b6 100644 --- a/lib/discourse_post_event/event_finder.rb +++ b/lib/discourse_post_event/event_finder.rb @@ -33,6 +33,33 @@ def self.search(user, params = {}) events = events.where(id: Array(params[:post_id])) if params[:post_id] + if params[:attending_user].present? + attending_user = User.find_by(username_lower: params[:attending_user].downcase) + if attending_user + events = + events.joins(:invitees).where( + discourse_post_event_invitees: { + user_id: attending_user.id, + status: DiscoursePostEvent::Invitee.statuses[:going], + }, + ) + + if !guardian.is_admin? + events = + events.where( + "discourse_post_event_events.status != ? OR discourse_post_event_events.status = ? AND EXISTS ( + SELECT 1 FROM discourse_post_event_invitees dpoei + WHERE dpoei.post_id = discourse_post_event_events.id + AND dpoei.user_id = ? + )", + DiscoursePostEvent::Event.statuses[:private], + DiscoursePostEvent::Event.statuses[:private], + user&.id, + ) + end + end + end + if params[:before].present? events = events.where("dcped.starts_at < ?", params[:before].to_datetime) end diff --git a/spec/lib/discourse_post_event/event_finder_spec.rb b/spec/lib/discourse_post_event/event_finder_spec.rb index 6dd88d5eb..dc2654d34 100644 --- a/spec/lib/discourse_post_event/event_finder_spec.rb +++ b/spec/lib/discourse_post_event/event_finder_spec.rb @@ -13,6 +13,62 @@ Group.refresh_automatic_groups! end + describe "by attending user" do + fab!(:attending_user) { Fabricate(:user) } + fab!(:public_event) { Fabricate(:event, status: DiscoursePostEvent::Event.statuses[:public]) } + fab!(:private_event) { Fabricate(:event, status: DiscoursePostEvent::Event.statuses[:private]) } + fab!(:another_event) { Fabricate(:event, status: DiscoursePostEvent::Event.statuses[:public]) } + + fab!(:attending_public_event) do + DiscoursePostEvent::Invitee.create!( + user: attending_user, + event: public_event, + status: DiscoursePostEvent::Invitee.statuses[:going], + ) + end + + fab!(:attending_private_event) do + DiscoursePostEvent::Invitee.create!( + user: attending_user, + event: private_event, + status: DiscoursePostEvent::Invitee.statuses[:going], + ) + end + + fab!(:not_attending_event) do + DiscoursePostEvent::Invitee.create!( + user: attending_user, + event: another_event, + status: DiscoursePostEvent::Invitee.statuses[:not_going], + ) + end + + it "returns only events the user is attending" do + expect( + finder.search(current_user, { attending_user: attending_user.username }), + ).to match_array([public_event]) + end + + it "includes private events for admin users" do + current_user.update!(admin: true) + expect( + finder.search(current_user, { attending_user: attending_user.username }), + ).to match_array([public_event, private_event]) + end + + it "includes private events if the searching user is also invited" do + DiscoursePostEvent::Invitee.create!( + user: current_user, + event: private_event, + status: DiscoursePostEvent::Invitee.statuses[:going], + ) + + expect( + finder.search(current_user, { attending_user: attending_user.username }), + ).to match_array([public_event, private_event]) + end + end + context "when the event is associated to a visible post" do let(:post1) do PostCreator.create!( From a59c7191db05df99e4f44450ca3f39f8c9293751 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 16 Jun 2025 17:52:29 +1000 Subject: [PATCH 02/25] localizations --- config/locales/client.en.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 19fd7b8d1..a199ac5ba 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -372,6 +372,8 @@ en: creator: "Creator" status: "Status" starts_at: "Starts at" + all_events: "All events" + my_events: "My events" upcoming_events_list: title: "Upcoming events" empty: "No upcoming events" From f1b930c94da320e4efda18df684cc26f006081bd Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 16 Jun 2025 14:58:10 +0200 Subject: [PATCH 03/25] allows for a free form location field To avoid a complex migration (essentially due to the fact that we store information in raw) we will keep the existing url field, and not show it in the UI as soon as the value is for is empty. The location field will always be present. --- app/models/discourse_post_event/event.rb | 2 ++ .../discourse_post_event/event_serializer.rb | 5 +++ .../components/discourse-post-event/index.gjs | 6 ++-- .../discourse-post-event/location.gjs | 14 ++++++++ .../components/modal/post-event-builder.gjs | 27 ++++++++++++--- .../discourse/lib/raw-event-helper.js | 4 +++ .../models/discourse-post-event-event.js | 3 ++ .../common/discourse-post-event.scss | 1 + .../common/post-event-builder.scss | 4 +++ config/locales/client.en.yml | 3 ++ .../20250616101944_add_location_to_event.rb | 7 ++++ lib/discourse_post_event/event_parser.rb | 1 + plugin.rb | 1 + .../discourse_calendar/post_event.rb | 9 +++++ .../discourse_calendar/post_event_form.rb | 30 ++++++++++++++++ spec/system/post_event_spec.rb | 34 ++++++++++++++++--- 16 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 assets/javascripts/discourse/components/discourse-post-event/location.gjs create mode 100644 db/migrate/20250616101944_add_location_to_event.rb create mode 100644 spec/system/page_objects/discourse_calendar/post_event_form.rb diff --git a/app/models/discourse_post_event/event.rb b/app/models/discourse_post_event/event.rb index 23fb940c5..8a9971cd9 100644 --- a/app/models/discourse_post_event/event.rb +++ b/app/models/discourse_post_event/event.rb @@ -309,6 +309,7 @@ def self.update_from_raw(post) original_starts_at: parsed_starts_at, original_ends_at: parsed_ends_at, url: event_params[:url], + location: event_params[:location], recurrence: event_params[:recurrence], recurrence_until: parsed_recurrence_until, timezone: event_params[:timezone], @@ -420,6 +421,7 @@ def calculate_next_date # raw_invitees :string is an Array # name :string # url :string(1000) +# location :string(1000) # custom_fields :jsonb not null # reminders :string # recurrence :string diff --git a/app/serializers/discourse_post_event/event_serializer.rb b/app/serializers/discourse_post_event/event_serializer.rb index 0ee1e1021..a442a1e8a 100644 --- a/app/serializers/discourse_post_event/event_serializer.rb +++ b/app/serializers/discourse_post_event/event_serializer.rb @@ -31,6 +31,7 @@ class EventSerializer < ApplicationSerializer attributes :timezone attributes :show_local_time attributes :url + attributes :location attributes :watching_invitee attributes :chat_enabled attributes :channel @@ -135,6 +136,10 @@ def category_id object.post.topic.category_id end + def include_url? + object.url.present? + end + def include_recurrence_rule? object.recurring? end diff --git a/assets/javascripts/discourse/components/discourse-post-event/index.gjs b/assets/javascripts/discourse/components/discourse-post-event/index.gjs index 1bd7b424c..5877cc50b 100644 --- a/assets/javascripts/discourse/components/discourse-post-event/index.gjs +++ b/assets/javascripts/discourse/components/discourse-post-event/index.gjs @@ -12,13 +12,12 @@ import Creator from "./creator"; import Dates from "./dates"; import EventStatus from "./event-status"; import Invitees from "./invitees"; +import Location from "./location"; import MoreMenu from "./more-menu"; import Status from "./status"; import Url from "./url"; -const StatusSeparator = ; +const StatusSeparator = ; const InfoSection =