Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 47 additions & 3 deletions assets/css/public.css
Original file line number Diff line number Diff line change
Expand Up @@ -2110,7 +2110,8 @@

.mayo-expand-all-button,
.mayo-print-button,
.mayo-shortcode-button {
.mayo-shortcode-button,
.mayo-cal-subscribe-button {
background: none;
border: none;
cursor: pointer;
Expand All @@ -2124,14 +2125,16 @@

.mayo-expand-all-button:hover,
.mayo-print-button:hover,
.mayo-shortcode-button:hover {
.mayo-shortcode-button:hover,
.mayo-cal-subscribe-button:hover {
color: #333;
}

.mayo-expand-all-button .dashicons,
.mayo-print-button .dashicons,
.mayo-rss-link .dashicons,
.mayo-shortcode-button .dashicons {
.mayo-shortcode-button .dashicons,
.mayo-cal-subscribe-button .dashicons {
font-size: 1.25rem;
width: 1.25rem;
height: 1.25rem;
Expand Down Expand Up @@ -2270,6 +2273,47 @@
padding: 0;
}

.mayo-subscribe-actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.75rem;
}

.mayo-subscribe-action {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.4rem 0.75rem;
background: #f0f0f1;
color: #2271b1;
border: 1px solid #c3c4c7;
border-radius: 3px;
text-decoration: none;
font-size: 0.9em;
transition: background-color 0.2s ease, border-color 0.2s ease;
}

.mayo-subscribe-action:hover {
background: #e0e0e1;
border-color: #8c8f94;
color: #135e96;
text-decoration: none;
}

.mayo-subscribe-action .dashicons {
font-size: 1rem;
width: 1rem;
height: 1rem;
}

.mayo-subscribe-help {
margin: 0.75rem 0 0;
font-size: 0.85em;
color: #555;
line-height: 1.4;
}

/* =============================================
Announcement Banner Styles
============================================= */
Expand Down
84 changes: 77 additions & 7 deletions assets/js/src/components/public/EventList.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const EventList = ({ widget = false, settings = {} }) => {
const [isInitialLoad, setIsInitialLoad] = useState(true);
const [autoexpand, setAutoexpand] = useState(false);
const [showShortcode, setShowShortcode] = useState(false);
const [showSubscribe, setShowSubscribe] = useState(false);
const [subscribeCopyState, setSubscribeCopyState] = useState('idle');
const [viewMode, setViewMode] = useState(settings?.defaultView || 'list'); // 'list' or 'calendar'
const [calendarDate, setCalendarDate] = useState(new Date()); // Current month for calendar view
const [calendarEvents, setCalendarEvents] = useState([]); // Events for calendar view
Expand Down Expand Up @@ -248,6 +250,31 @@ const EventList = ({ widget = false, settings = {} }) => {
}
};

const getWebcalUrl = () => getIcsUrl().replace(/^https?:\/\//i, 'webcal://');

const handleCopySubscribeUrl = async () => {
const url = getIcsUrl();
try {
await navigator.clipboard.writeText(url);
setSubscribeCopyState('copied');
setTimeout(() => setSubscribeCopyState('idle'), 2000);
} catch (err) {
const textArea = document.createElement('textarea');
textArea.value = url;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
setSubscribeCopyState('copied');
setTimeout(() => setSubscribeCopyState('idle'), 2000);
} catch (fallbackErr) {
console.error('Failed to copy subscribe URL:', fallbackErr);
}
document.body.removeChild(textArea);
}
};

// Process events to add validation flags and move invalid dates to the end
// Trust the order from the REST API - don't re-sort valid dates
const processEvents = (eventList) => {
Expand Down Expand Up @@ -704,15 +731,14 @@ const EventList = ({ widget = false, settings = {} }) => {
>
<span className="dashicons dashicons-printer"></span>
</button>
<a
href={getIcsUrl()}
className="mayo-rss-link"
target="_blank"
rel="noopener noreferrer"
title={__('Calendar Feed (ICS)', 'mayo-events-manager')}
<button
className="mayo-cal-subscribe-button"
onClick={() => setShowSubscribe(!showSubscribe)}
title={showSubscribe ? __('Hide Calendar Subscription', 'mayo-events-manager') : __('Subscribe to Calendar', 'mayo-events-manager')}
aria-expanded={showSubscribe}
>
<span className="dashicons dashicons-calendar"></span>
</a>
</button>
<a
href={getRssUrl()}
className="mayo-rss-link"
Expand Down Expand Up @@ -749,6 +775,50 @@ const EventList = ({ widget = false, settings = {} }) => {
</div>
</div>
)}
{showSubscribe && (
<div className="mayo-shortcode-display mayo-subscribe-display">
<div className="mayo-shortcode-header">
<strong>{__('Subscribe to this calendar:', 'mayo-events-manager')}</strong>
<button
className="mayo-copy-shortcode"
onClick={handleCopySubscribeUrl}
title={__('Copy to Clipboard', 'mayo-events-manager')}
>
<span className="dashicons dashicons-clipboard"></span>
{subscribeCopyState === 'copied'
? __('Copied', 'mayo-events-manager')
: __('Copy', 'mayo-events-manager')}
</button>
</div>
<div className="mayo-shortcode-text">
<code>{getIcsUrl()}</code>
</div>
<div className="mayo-subscribe-actions">
<a
href={getWebcalUrl()}
className="mayo-subscribe-action"
title={__('Open in your default calendar app (Apple Calendar, Outlook, etc.)', 'mayo-events-manager')}
>
<span className="dashicons dashicons-calendar-alt"></span>
{__('Subscribe in default calendar app', 'mayo-events-manager')}
</a>
<a
href={getIcsUrl()}
className="mayo-subscribe-action"
target="_blank"
rel="noopener noreferrer"
download="mayo_events.ics"
title={__('Download a one-time snapshot (.ics file)', 'mayo-events-manager')}
>
<span className="dashicons dashicons-download"></span>
{__('Download .ics file', 'mayo-events-manager')}
</a>
</div>
<p className="mayo-subscribe-help">
{__('To subscribe in Google Calendar: open Other calendars → From URL, then paste the link above. New events will sync automatically.', 'mayo-events-manager')}
</p>
</div>
)}
{viewMode === 'calendar' ? (
<CalendarView
events={calendarEvents}
Expand Down
Loading