Discourse Calendar and 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:

3 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.

2 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