Discourse Events

No errors appear in the console. But I recorded a video for reference.
This happens on chrome, firefox and safari. On mobile safari it doesn’t happen.

1 Like

Your video link is broken!

Excuse me, I didn’t quite understand that. Indeed, you’re right: when clicking on a date in the calendar, the event creation modal opens, but with the “Do you want to abandon?” button. Yes, indeed, there’s a little bug :sweat_smile:

2 Likes

For your information, I’ve added a “+ More Info” button for my users who don’t think to click the title to access the topic. I borrowed the idea from @nathank’s “Add to Calendar” button.

Place the script in a theme component under the General /head section.

<script>
(() => {
    const eventInfoSelector = ".event-header .event-info";
    const eventCardSelector = ".fc-popover, .event-preview, .discourse-post-event";
    // Clean CSS injected and adapted for Discourse themes (Light/Dark)
    const style = document.createElement("style");
    style.innerHTML = `
    .custom-topic-info-btn {
        display: flex !important;
        align-items: center;
        justify-content: center;
        width: 40%; /* Takes full width for a clean look under the title */
        box-sizing: border-box;
        margin: 12px 0 6px 0;
        padding: 10px 16px;
        
        /* Using native Discourse variables */
        background-color: var(--tertiary) !important; /* Accent color (e.g., blue) */
        color: var(--secondary) !important;          /* Contrasting text color */
        
        border-radius: 8px;
        font-weight: 700;
        text-decoration: none !important;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
        transition: background-color 0.2s ease, transform 0.1s ease;
    }
    /* Hover effect */
    .custom-topic-info-btn:hover {
        background-color: var(--tertiary-hover) !important;
        color: var(--secondary) !important;
        transform: translateY(-1px);
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
    }
    /* Click effect */
    .custom-topic-info-btn:active {
        transform: translateY(0);
    }
    /* SVG icon style */
    .custom-topic-info-btn .btn-icon-svg {
        margin-right: 8px;
        width: 16px;
        height: 16px;
        fill: currentColor;
    }
    `;
    document.head.appendChild(style);
    const findTopicLink = (container) => {
        if (!container) return null;
        const links = [...container.querySelectorAll('a[href*="/t/"]')];
        if (links.length) return links[0].href;
        return null;
    };
    const createTopicInfoButton = () => {
        const eventInfo = document.querySelector(eventInfoSelector);
        if (!eventInfo || eventInfo.querySelector(".custom-topic-info-btn")) return;
        const card = eventInfo.closest(eventCardSelector) || document;
        const topicUrl = findTopicLink(card);
        if (!topicUrl) return;
        const button = document.createElement("a");
        button.className = "btn custom-topic-info-btn";
        button.title = "View full topic";
        button.href = topicUrl;
        
        // Add a harmonized SVG Info icon
        button.innerHTML = `
            <svg class="btn-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                <path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V240h-24c-13.3 0-24-10.7-24-24s10.7-24 24-24h40c13.3 0 24 10.7 24 24v120h24c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-144a32 32 0 1 1 0-64 32 32 0 1 1 0 64z"/>
            </svg>
            <span class="d-button-label">More info</span>
        `;
        if (eventInfo.firstElementChild) {
            eventInfo.insertBefore(button, eventInfo.firstElementChild.nextSibling);
        } else {
            eventInfo.appendChild(button);
        }
    };
    const observer = new MutationObserver(() => createTopicInfoButton());
    observer.observe(document.body, { childList: true, subtree: true });
    createTopicInfoButton();
})();
</script>

Here’s a preview:

Or alternatively, this code:

<script>
(() => {
    const eventCardSelector = ".fc-popover, .event-preview, .discourse-post-event";

    // Injected CSS for a clean and modern look
    const style = document.createElement("style");
    style.innerHTML = `
    .custom-topic-info-wrapper {
        margin-top: 16px;
        padding-top: 16px;
        border-top: 1px solid var(--primary-low); /* Subtle separator line */
        width: 100%;
        display: flex;
        justify-content: center;
    }

    .custom-topic-info-btn {
        display: flex !important;
        align-items: center;
        justify-content: center;
        width: 100%;
        box-sizing: border-box;
        padding: 8px 16px;
        
        /* Elegant Outline style */
        background-color: transparent !important;
        color: var(--tertiary) !important;
        border: 1px solid var(--tertiary) !important;
        
        border-radius: 6px;
        font-weight: 600;
        text-decoration: none !important;
        transition: all 0.2s ease;
        cursor: pointer;
    }

    /* Hover effect: button fills with color */
    .custom-topic-info-btn:hover {
        background-color: var(--tertiary) !important;
        color: var(--secondary) !important;
        border-color: var(--tertiary) !important;
    }

    /* Align the info icon */
    .custom-topic-info-btn .btn-icon-svg {
        margin-right: 8px;
        width: 16px;
        height: 16px;
        fill: currentColor;
    }
    `;
    document.head.appendChild(style);

    const findTopicLink = (container) => {
        if (!container) return null;
        const links = [...container.querySelectorAll('a[href*="/t/"]')];
        if (links.length) return links[0].href;
        return null;
    };

    const createTopicInfoButton = () => {
        const cards = document.querySelectorAll(eventCardSelector);
        
        cards.forEach(card => {
            if (card.querySelector(".custom-topic-info-btn")) return;

            const topicUrl = findTopicLink(card);
            if (!topicUrl) return;

            const wrapper = document.createElement("div");
            wrapper.className = "custom-topic-info-wrapper";

            const button = document.createElement("a");
            button.className = "btn custom-topic-info-btn";
            button.title = "View full topic";
            button.href = topicUrl;
            
            // "i" Info icon + New actionable text
            button.innerHTML = `
                <svg class="btn-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                    <path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V240h-24c-13.3 0-24-10.7-24-24s10.7-24 24-24h40c13.3 0 24 10.7 24 24v120h24c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-144a32 32 0 1 1 0-64 32 32 0 1 1 0 64z"/>
                </svg>
                <span class="d-button-label">View topic</span>
            `;

            wrapper.appendChild(button);

            // Position above the response options (Going, etc.)
            const actionsContainer = card.querySelector(".status-and-options, .event-actions");
            const infoContainer = card.querySelector(".event-info");
            
            if (actionsContainer && actionsContainer.parentNode) {
                actionsContainer.parentNode.insertBefore(wrapper, actionsContainer);
            } else if (infoContainer) {
                infoContainer.appendChild(wrapper);
            } else {
                card.appendChild(wrapper);
            }
        });
    };

    const observer = new MutationObserver(() => createTopicInfoButton());
    observer.observe(document.body, { childList: true, subtree: true });

    createTopicInfoButton();
})();
</script>

Here’s a preview of the result:

You have the choice :rofl:

4 Likes

You’ve really improved how easy it is to create an event since the latest updates :+1:

However, I wanted to know if there’s a setting that prevents creating an event when a user clicks in the calendar. Sometimes, instead of clicking on an event, they click nearby, which opens a modal to create a new event :sweat_smile:

Thank you.

2 Likes

I know I’m being a pain, but I have another question or suggestion regarding expired events. They could be deleted automatically since they’re no longer of any use, it seems the events plugin used to do this automatically.

Currently, I do it manually, so if there’s a way to save time :sweat_smile:

Thanks to the team

1 Like

Is there any way for the location in the event to do a lookup like is done in google calendar or facebook events, a) to aid the user in picking the location, and b) so the location is clickable to a map application?

(I wasn’t able to find any solution by searching but maybe my search prompts weren’t good)

2 Likes

The Locations Plugin 🌍 does have that sort of functionality, but only integrates with the alternative Events Plugin 📅.

The dev (@merefield) might be open to integrating it with the official plugin, but I imagine that would be quite a bit of work to establish and maintain - so you’d definitely need to sponsor the work for it to happen.

3 Likes

I’ve been trying to look at that Events Plugin, but get stuck at

as the site has been down for months.

1 Like

It might be worth putting in a formal feature request in Contribute > Feature (tagged calendar-and-events) for consideration. I can imagine it would be a feasible thing - and it has the potential to be high value for many.


later…
@lou has indeed done so here:

3 Likes

A post was merged into an existing topic: Event location map support

I plan to export the public events to a wordpress website using some wordpress plugin which knows how to read the ics feed from /discourse-post-event/events.ics. So far so good.

There are some pages on this website which would like to look at a subset of these events, each page wants to show a different subset.

I was hoping that I could tag the events with a tag associated with each page so this would show up in the CATEGORIES line of the ics file to allow filtering on the destination, but that doesn’t seem to be happening.

I would be willing to use ?tag=[tag] in the url and have the different pages use different urls as well, though I haven’t found that this is a feature from reading through what I could find.

Is there something I’m not considering?

No, you aren’t missing anything (as far as I’m aware).

Being able to filter the calendar .ics feed by category and/or tag would be a worthy Contribute > Feature request though!

1 Like

I’m ending up building my own tag filter, but I think I learned that “expired” events are not downloaded via /discourse-post-event/events.ics. I also don’t see any way to control when an event becomes “expired”. I may have to resort to doing a data explorer query instead of using /…/events.ics

[although I think I found a better way to do this using /discourse-post-event/event ]

1 Like

Hi, Looking for feedback on an aspect of having events integration with audio & live stream. See the last response here → https://meta.discourse.org/t/discourse-desktop-mac-app/406912/10?u=nicolsdennis

Has anyone had success getting VDO.Ninja implemented into the livestreaming of events? I see the zoom integration and I am thinking there might be a way to jury rig into it instead of zoom :thinking:

3 posts were split to a new topic: Questions about the Zoom Livestream integration

The Discourse Calendar / Events Upcoming Events routes appear to omit all Open Graph and Twitter-card metadata from the initial server-rendered HTML response.

This is reproducible on a hosted Discourse site where normal pages correctly emit social metadata and a valid site OpenGraph image is configured.

Example affected route:

https://www.mbacommons.com/upcoming-events/month/2026/8/1

Steps to reproduce

  1. Configure a valid site OpenGraph image.
  2. Confirm the homepage or a normal topic emits normal Open Graph metadata in raw HTML.
  3. Open /upcoming-events/month/2026/8/1.
  4. Inspect raw view-source: / initial server response, not the post-JavaScript DOM.

Actual result

The Upcoming Events route returns HTTP 200, but the raw HTML contains:

  • no og:image
  • no og:title
  • no og:description
  • no og:url
  • no twitter:card
  • no twitter:image

I also tested using LinkedInBot/1.0; the result is identical.

A raw-HTML production audit on the same site counted:

  • homepage: 11 social metadata tags
  • normal topic: 14
  • Upcoming Events: 0

The homepage and normal topics correctly use the configured site OpenGraph image, so the site-level branding configuration itself is working.

Expected result

Upcoming Events routes should emit crawler-visible, server-rendered social metadata, including at minimum:

  • og:title
  • og:description
  • og:url
  • og:image, falling back to the configured site OpenGraph image
  • corresponding Twitter-card metadata

This needs to be in the initial HTML because LinkedIn and similar social crawlers cannot rely on client-side theme JavaScript.

LinkedIn Post Inspector currently reports:

Add an og:image tag to the page to have control over the content's image on LinkedIn.

A global theme head_tag injection does not seem appropriate because normal pages already have correct metadata and it could create duplicate/conflicting tags. This appears to need route-aware server-side handling for Upcoming Events or use of Discourse’s normal social-metadata rendering path.

2 Likes

Peertube Live with Discourse Calendar and Events

This used to work fine with Peertube last Summer (2025), providing the chat interface with the topic (using the /embed/xxx video URL to display the live video, and accounting for the ~30s delay due to P2P in Peertube) when tagging with #livestream.

But I tried again today and the chat does not seem to be embedded in the layout as it used to (at least in my memory). Also there is no “Livestream button” on the event interface. Is this normal?

2 Likes

Have you seen the recently updated instructions? I think the UI might have changed a bit when it was all rolled into this plugin.

1 Like