diff --git a/assets/javascripts/discourse/initializers/discourse-calendar.js b/assets/javascripts/discourse/initializers/discourse-calendar.js index 1945c234..37991589 100644 --- a/assets/javascripts/discourse/initializers/discourse-calendar.js +++ b/assets/javascripts/discourse/initializers/discourse-calendar.js @@ -47,7 +47,7 @@ function initializeDiscourseCalendar(api) { selector = `.topic-list:not(.shared-drafts) .${outletName}-outlet`; } - api.onPageChange((url) => { + api.onPageChange(async (url) => { const categoryCalendarNode = document.querySelector( `${selector}.category-calendar` ); @@ -98,28 +98,27 @@ function initializeDiscourseCalendar(api) { categoryCalendarNode.innerHTML = '
'; - loadFullCalendar().then(() => { - const options = [`postId=${postId}`]; + await loadFullCalendar(); - const optionals = ["weekends", "tzPicker", "defaultView"]; - optionals.forEach((optional) => { - if (isPresent(categorySetting[optional])) { - options.push( - `${optional}=${escapeExpression(categorySetting[optional])}` - ); - } - }); + const options = [`postId=${postId}`]; - const rawCalendar = `[calendar ${options.join(" ")}]\n[/calendar]`; - const cookRaw = cook(rawCalendar); - const loadPost = ajax(`/posts/${postId}.json`); - Promise.all([cookRaw, loadPost]).then((results) => { - const cooked = results[0]; - const post = results[1]; - categoryCalendarNode.innerHTML = cooked.toString(); - render($(".calendar"), post); - }); + const optionals = ["weekends", "tzPicker", "defaultView"]; + optionals.forEach((optional) => { + if (isPresent(categorySetting[optional])) { + options.push( + `${optional}=${escapeExpression(categorySetting[optional])}` + ); + } }); + + const rawCalendar = `[calendar ${options.join(" ")}]\n[/calendar]`; + const cookRaw = cook(rawCalendar); + const loadPost = ajax(`/posts/${postId}.json`); + + const [cooked, post] = await Promise.all([cookRaw, loadPost]); + + categoryCalendarNode.innerHTML = cooked.toString(); + render($(".calendar"), post); } else { if (!categoryEventNode) { return; @@ -131,97 +130,91 @@ function initializeDiscourseCalendar(api) { ); if (foundCategory) { - loadFullCalendar().then(() => { - let fullCalendar = new window.FullCalendar.Calendar( - categoryEventNode, - { - ...fullCalendarDefaultOptions(), - eventPositioned: (info) => { - if (siteSettings.events_max_rows === 0) { - return; - } - - let fcContent = info.el.querySelector(".fc-content"); - let computedStyle = window.getComputedStyle(fcContent); - let lineHeight = parseInt(computedStyle.lineHeight, 10); - - if (lineHeight === 0) { - lineHeight = 20; - } - let maxHeight = lineHeight * siteSettings.events_max_rows; - - if (fcContent) { - fcContent.style.maxHeight = `${maxHeight}px`; - } - - let fcTitle = info.el.querySelector(".fc-title"); - if (fcTitle) { - fcTitle.style.overflow = "hidden"; - fcTitle.style.whiteSpace = "pre-wrap"; - } - fullCalendar.updateSize(); - }, + await loadFullCalendar(); + let fullCalendar = new window.FullCalendar.Calendar(categoryEventNode, { + ...fullCalendarDefaultOptions(), + eventPositioned: (info) => { + if (siteSettings.events_max_rows === 0) { + return; } - ); - const params = { - category_id: browsedCategory.id, - include_subcategories: true, - }; - if (siteSettings.include_expired_events_on_calendar) { - params.include_expired = true; + + let fcContent = info.el.querySelector(".fc-content"); + let computedStyle = window.getComputedStyle(fcContent); + let lineHeight = parseInt(computedStyle.lineHeight, 10); + + if (lineHeight === 0) { + lineHeight = 20; + } + let maxHeight = lineHeight * siteSettings.events_max_rows; + + if (fcContent) { + fcContent.style.maxHeight = `${maxHeight}px`; + } + + let fcTitle = info.el.querySelector(".fc-title"); + if (fcTitle) { + fcTitle.style.overflow = "hidden"; + fcTitle.style.whiteSpace = "pre-wrap"; + } + fullCalendar.updateSize(); + }, + }); + const params = { + category_id: browsedCategory.id, + include_subcategories: true, + }; + if (siteSettings.include_expired_events_on_calendar) { + params.include_expired = true; + } + + const tagsColorsMap = JSON.parse(siteSettings.map_events_to_color); + + const discoursePostEventApiService = api.container.lookup( + "service:discourse-post-event-api" + ); + + const events = await discoursePostEventApiService.events(params); + addRecurrentEvents(events).forEach((event) => { + const { startsAt, endsAt, post, categoryId } = event; + + let backgroundColor; + + if (post.topic.tags) { + const tagColorEntry = tagsColorsMap.find( + (entry) => + entry.type === "tag" && post.topic.tags.includes(entry.slug) + ); + backgroundColor = tagColorEntry ? tagColorEntry.color : null; } - const loadEvents = ajax(`/discourse-post-event/events`, { - data: params, - }); - const tagsColorsMap = JSON.parse(siteSettings.map_events_to_color); + if (!backgroundColor) { + const categoryColorFromMap = tagsColorsMap.find( + (entry) => + entry.type === "category" && + entry.slug === post.topic.category_slug + )?.color; + backgroundColor = + categoryColorFromMap || + `#${Category.findById(categoryId)?.color}`; + } - Promise.all([loadEvents]).then((results) => { - const [{ events }] = results; + let classNames; + if (moment(endsAt || startsAt).isBefore(moment())) { + classNames = "fc-past-event"; + } - addRecurrentEvents(events).forEach((event) => { - const { starts_at, ends_at, post, category_id } = event; - - let backgroundColor; - - if (post.topic.tags) { - const tagColorEntry = tagsColorsMap.find( - (entry) => - entry.type === "tag" && post.topic.tags.includes(entry.slug) - ); - backgroundColor = tagColorEntry ? tagColorEntry.color : null; - } - - if (!backgroundColor) { - const categoryColorFromMap = tagsColorsMap.find( - (entry) => - entry.type === "category" && - entry.slug === post.topic.category_slug - )?.color; - backgroundColor = - categoryColorFromMap || - `#${Category.findById(category_id)?.color}`; - } - - let classNames; - if (moment(ends_at || starts_at).isBefore(moment())) { - classNames = "fc-past-event"; - } - - fullCalendar.addEvent({ - title: formatEventName(event), - start: starts_at, - end: ends_at || starts_at, - allDay: !isNotFullDayEvent(moment(starts_at), moment(ends_at)), - url: getURL(`/t/-/${post.topic.id}/${post.post_number}`), - backgroundColor, - classNames, - }); - }); - - fullCalendar.render(); + fullCalendar.addEvent({ + title: formatEventName(event), + start: startsAt, + end: endsAt || startsAt, + allDay: !isNotFullDayEvent(moment(startsAt), moment(endsAt)), + url: getURL(`/t/-/${post.topic.id}/${post.post_number}`), + backgroundColor, + classNames, }); }); + + fullCalendar.render(); } } }); diff --git a/assets/javascripts/discourse/lib/add-recurrent-events.js b/assets/javascripts/discourse/lib/add-recurrent-events.js index 4b53ff80..0e0c6a5c 100644 --- a/assets/javascripts/discourse/lib/add-recurrent-events.js +++ b/assets/javascripts/discourse/lib/add-recurrent-events.js @@ -1,12 +1,17 @@ +import DiscoursePostEventEvent from "../models/discourse-post-event-event"; + export default function addRecurrentEvents(events) { return events.flatMap((event) => { const upcomingEvents = - event.upcoming_dates?.map((upcomingDate) => ({ - ...event, - starts_at: upcomingDate.starts_at, - ends_at: upcomingDate.ends_at, - upcoming_dates: [], - })) || []; + event.upcomingDates?.map((upcomingDate) => + DiscoursePostEventEvent.create({ + name: event.name, + post: event.post, + category_id: event.categoryId, + starts_at: upcomingDate.starts_at, + ends_at: upcomingDate.ends_at, + }) + ) || []; return [event, ...upcomingEvents]; }); diff --git a/test/javascripts/acceptance/upcoming-events-calendar-test.js b/test/javascripts/acceptance/upcoming-events-calendar-test.js index 944eff76..b734eff3 100644 --- a/test/javascripts/acceptance/upcoming-events-calendar-test.js +++ b/test/javascripts/acceptance/upcoming-events-calendar-test.js @@ -1,10 +1,11 @@ import { visit } from "@ember/test-helpers"; import { test } from "qunit"; -import { tomorrow } from "discourse/lib/time-utils"; +import { tomorrow, twoDays } from "discourse/lib/time-utils"; import { acceptance, exists, query, + queryAll, } from "discourse/tests/helpers/qunit-helpers"; acceptance("Discourse Calendar - Upcoming Events Calendar", function (needs) { @@ -38,7 +39,7 @@ acceptance("Discourse Calendar - Upcoming Events Calendar", function (needs) { events: [ { id: 67501, - starts_at: tomorrow(), + starts_at: tomorrow().add(1, "hour"), ends_at: null, timezone: "Asia/Calcutta", post: { @@ -51,6 +52,12 @@ acceptance("Discourse Calendar - Upcoming Events Calendar", function (needs) { }, }, name: "Awesome Event", + upcoming_dates: [ + { + starts_at: twoDays().format("YYYY-MM-DDT15:14:00.000Z"), + ends_at: twoDays().format("YYYY-MM-DDT16:14:00.000Z"), + }, + ], category_id: 1, }, { @@ -101,4 +108,21 @@ acceptance("Discourse Calendar - Upcoming Events Calendar", function (needs) { "Event item uses the proper color from category 2" ); }); + + test("upcoming events calendar shows recurrent events", async (assert) => { + await visit("/upcoming-events"); + + const [, first, second] = queryAll(".fc-event .fc-title"); + assert.equal(first.textContent, "Awesome Event"); + assert.equal(second.textContent, "Awesome Event"); + + const firstCell = first.closest("td"); + const secondCell = second.closest("td"); + + assert.notEqual( + firstCell, + secondCell, + "events should be in different days" + ); + }); });