Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
7 changes: 5 additions & 2 deletions .github/ISSUE_TEMPLATE/add-to-calendar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,19 @@ body:
type: input
attributes:
label: Start time in UTC
description: 'Use HH:MM format (e.g., 14:00). Enter "TBD" if time is not yet confirmed, or "all-day" for all-day events.'
placeholder: '14:00'
validations:
required: true
required: false

- id: UTCEndTime
type: input
attributes:
label: End time in UTC
description: 'Use HH:MM format (e.g., 15:00). Leave blank if start time is TBD or all-day.'
placeholder: '15:00'
validations:
required: true
required: false

- id: type
type: dropdown
Expand All @@ -61,6 +63,7 @@ body:
- talk
- meetup
- fundraising
- workshop
- misc
validations:
required: true
Expand Down
10 changes: 6 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Need to run the website locally for testing? → [Jump to Development Guidelines
### Adding a New Event

1. Navigate to the `content/events/` folder
2. Create a new markdown file with a descriptive name (e.g., `2025-05-20-your-event-name.md`)
2. Create a new markdown file with a descriptive name (e.g., `2026-05-20-your-event-name.md`)
3. Use the following template:

```markdown
Expand All @@ -51,6 +51,7 @@ title: 'Your Event Title'
metaTitle: 'Your Event Title'
metaDesc: 'A brief description of your event'
date: 'MM/DD'
endDate: 'MM/DD'
UTCStartTime: 'HH:MM'
UTCEndTime: 'HH:MM'
type: 'meetup'
Expand All @@ -64,7 +65,7 @@ linkUrl: 'https://link-to-event.com'
Detailed description of your event goes here. You can use markdown formatting.
```

4. All frontmatter fields (between `---`) are mandatory
> **Note:** `UTCStartTime` and `UTCEndTime` are optional. If omitted, the event will display "TBD" for the time. You can also set them to `'TBD'` explicitly or use `'all-day'` for events that span an entire day. Use `endDate` for multi-day events.
5. Submit a PR with your changes

### Adding a New Resource
Expand Down Expand Up @@ -175,8 +176,9 @@ For significant changes like:
- `metaTitle`: Title for SEO meta tags
- `metaDesc`: Description for SEO meta tags
- `date`: Event date in `MM/DD` format
- `UTCStartTime`: Start time in UTC, in `HH:MM` format
- `UTCEndTime`: End time in UTC, in `HH:MM` format
- `endDate`: _(optional)_ End date in `MM/DD` format, for multi-day events (e.g., a conference running May 16-18 would use `date: '05/16'` and `endDate: '05/18'`). Multi-day events display a date range and remain listed as upcoming until the end date passes.
- `UTCStartTime`: _(optional)_ Start time in UTC, in `HH:MM` format. Omit or set to `'TBD'` if the time is not yet confirmed. Set to `'all-day'` for events without a specific start time.
- `UTCEndTime`: _(optional)_ End time in UTC, in `HH:MM` format. Same rules as `UTCStartTime`.
- `type`: One of: `podcast`, `stream`, `talk`, `meetup`, `fundraising`, `conference`, `misc`
- `language`: Primary language of the event
- `location`: `Virtual` or physical location
Expand Down
4 changes: 2 additions & 2 deletions Participation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
...a month for open source maintainers to gather, share, and be celebrated.
Open source runs the world, but who runs open source? Open source maintainers are behind the software we use everyday, but they don't always have the community or support they need. That's why we're celebrating open source maintainers all month long.

The 2025 Maintainer Month is, of course, in **May**! You're invited to participate in whatever ways you'd like, and list what you're doing on the public [Maintainer Month site](https://maintainermonth.github.com/).
The 2026 Maintainer Month is, of course, in **May**! You're invited to participate in whatever ways you'd like, and list what you're doing on the public [Maintainer Month site](https://maintainermonth.github.com/).

## Themes for 2025
## Themes for 2026

**Theme for Maintainers**

Expand Down
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Repository for the official GitHub Maintainer Month website. You can access the

**Add your event!** See the [contributing guide](CONTRIBUTING.md) for details on how.

**<p align="center"> ❇️ May 2025 ❇️ </p>**
**<p align="center"> ❇️ May 2026 ❇️ </p>**

## Table of Contents

Expand All @@ -20,32 +20,30 @@ Repository for the official GitHub Maintainer Month website. You can access the

## Getting Started

### Installation
Requires [Node.js](https://nodejs.org/) v18.18 or later.

Run the following command before any other to install all the project's dependencies.
### Installation

```
npm install
```

### Usage

To start application in development mode at [http://localhost:3000](http://localhost:3000) run the following command.
Start the development server at [http://localhost:3000](http://localhost:3000):

```
npm start
npm run dev
```

### Build

To generate the application build run the following command
Generate a production build (static export to the `out` folder):

```
npm run build
```

This will create an `out` folder in the repository root with the static files.

## Contributing

See the [contributing guide](CONTRIBUTING.md) for more details.
Expand Down
82 changes: 82 additions & 0 deletions api/calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createEvents } from 'ics'

const EVENT_YEAR = 2026

const isTBDValue = (value) =>
!value || (typeof value === 'string' && value.toUpperCase() === 'TBD')

const isAllDayValue = (value) =>
typeof value === 'string' &&
value.toLowerCase().replace(/[\s\-_]/g, '') === 'allday'

function parseDateParts(dateStr) {
const [month, day] = dateStr.split('/').map(Number)
return { month, day }
}

function toICSEvent(event) {
const { month: startMonth, day: startDay } = parseDateParts(event.date)

const hasTime =
!isTBDValue(event.UTCStartTime) && !isAllDayValue(event.UTCStartTime)

const icsEvent = {
title: event.title,
description: event.description || '',
location: event.location || '',
url: event.linkUrl || '',
calName: 'Maintainer Month 2026',
}
Comment on lines +23 to +29

if (hasTime) {
const [startHour, startMinute] = event.UTCStartTime.split(':').map(Number)
icsEvent.start = [EVENT_YEAR, startMonth, startDay, startHour, startMinute]
icsEvent.startInputType = 'utc'

if (!isTBDValue(event.UTCEndTime) && !isAllDayValue(event.UTCEndTime)) {
const [endHour, endMinute] = event.UTCEndTime.split(':').map(Number)

if (event.endDate && event.endDate !== event.date) {
const { month: endMonth, day: endDay } = parseDateParts(event.endDate)
icsEvent.end = [EVENT_YEAR, endMonth, endDay, endHour, endMinute]
} else {
icsEvent.end = [EVENT_YEAR, startMonth, startDay, endHour, endMinute]
}
icsEvent.startOutputType = 'utc'
icsEvent.endInputType = 'utc'
icsEvent.endOutputType = 'utc'
} else {
icsEvent.duration = { hours: 1 }
}
} else {
// All-day or TBD: treat as all-day event
icsEvent.start = [EVENT_YEAR, startMonth, startDay]
icsEvent.startInputType = 'utc'

if (event.endDate && event.endDate !== event.date) {
const { month: endMonth, day: endDay } = parseDateParts(event.endDate)
// ICS all-day end date is exclusive, so add one day
const endDate = new Date(Date.UTC(EVENT_YEAR, endMonth - 1, endDay + 1))
icsEvent.end = [
endDate.getUTCFullYear(),
endDate.getUTCMonth() + 1,
endDate.getUTCDate(),
]
} else {
icsEvent.end = [EVENT_YEAR, startMonth, startDay + 1]
}
}

return icsEvent
}

export function generateICS(events) {
const icsEvents = events.map(toICSEvent)
const { error, value } = createEvents(icsEvents)

if (error) {
throw new Error(`Failed to generate ICS: ${error.message}`)
}

return value
}
47 changes: 35 additions & 12 deletions api/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ dayjs.extend(utc)
dayjs.extend(timezone)

const TBD = 'TBD'
const ALL_DAY = 'All Day'

const isTBDValue = (value) =>
!value || (typeof value === 'string' && value.toUpperCase() === 'TBD')

const isAllDayValue = (value) =>
typeof value === 'string' &&
value.toLowerCase().replace(/[\s\-_]/g, '') === 'allday'

export const getEvents = (year) => {
const events_path = year ? `content/${year}/events` : 'content/events'
Expand Down Expand Up @@ -108,27 +116,38 @@ const formatEventDateTime = (
formattedEndDate = endUTCDate.format('MMM D')
}

// All-day events
if (isAllDayValue(startTime) || isAllDayValue(endTime)) {
return {
date: formattedDate,
startTime: { utc: ALL_DAY, pt: ALL_DAY },
endTime: { utc: ALL_DAY, pt: ALL_DAY },
endDate: formattedEndDate,
timeDisplay: 'all-day',
}
}

// Start time
let formattedStartTime = {
utc: TBD,
pt: TBD,
}

if (startTime) {
let hasSpecificStartTime = false

if (!isTBDValue(startTime)) {
const [startHour, startMinute] = startTime.split(':')

const UTCStartTime = UTCDate.hour(startHour).minute(startMinute)

if (!isNaN(UTCStartTime)) {
const PTStartTime = UTCStartTime.tz('America/Los_Angeles')

const formattedUTCStartTime = UTCStartTime.format('HH:mm a')
const formattedPTStartTime = PTStartTime.format('HH:mm a')

formattedStartTime = {
utc: formattedUTCStartTime,
pt: formattedPTStartTime,
utc: UTCStartTime.format('HH:mm a'),
pt: PTStartTime.format('HH:mm a'),
}
Comment on lines 146 to 149
hasSpecificStartTime = true
}
}

Expand All @@ -138,28 +157,32 @@ const formatEventDateTime = (
pt: TBD,
}

if (endTime && endTime !== TBD) {
let hasSpecificEndTime = false

if (!isTBDValue(endTime)) {
const [endHour, endMinute] = endTime.split(':')

const UTCEndTime = UTCDate.hour(endHour).minute(endMinute)

if (!isNaN(UTCEndTime)) {
const PTEndTime = UTCEndTime.tz('America/Los_Angeles')

const formattedUTCEndTime = UTCEndTime.format('HH:mm a')
const formattedPTEndTime = PTEndTime.format('HH:mm a')

formattedEndTime = {
utc: formattedUTCEndTime,
pt: formattedPTEndTime,
utc: UTCEndTime.format('HH:mm a'),
pt: PTEndTime.format('HH:mm a'),
}
hasSpecificEndTime = true
}
}

const timeDisplay =
hasSpecificStartTime || hasSpecificEndTime ? 'specific' : 'tbd'

return {
date: formattedDate,
startTime: formattedStartTime,
endTime: formattedEndTime,
endDate: formattedEndDate,
timeDisplay,
}
}
2 changes: 2 additions & 0 deletions common/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const Y2024 = makePath('/2024')

export const NEWS = makePath("/news")

export const SHIPS = makePath('/ships')

export const PARTNER_PACK = makePath('/partner-pack')

export const SECURITY_CHALLENGE = makePath('/security-challenge')
4 changes: 3 additions & 1 deletion components/chip/chip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
display: inline-flex;
align-items: center;
column-gap: 6px;
flex-shrink: 0;
white-space: nowrap;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;

background-color: $white;
@extend %subtitle-1;
Expand Down
36 changes: 29 additions & 7 deletions components/date-time-chip/DateTimeChip.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
import { getLiteral } from '../../common/i18n'
import IconClock from '../../public/icons/clock'

const DateTimeChip = ({ startTime, endTime }) => {
const DateTimeChip = ({ startTime, endTime, timeDisplay }) => {
if (timeDisplay === 'all-day') {
return (
<div className="datetime-chip">
<p className="datetime-chip__time">
<span className="datetime-chip__icon">
<IconClock />
</span>
{getLiteral('message:all-day')}
</p>
</div>
)
}

if (timeDisplay === 'tbd') {
return (
<div className="datetime-chip">
<p className="datetime-chip__time">
<span className="datetime-chip__icon">
<IconClock />
</span>
{getLiteral('message:tbd')}
</p>
</div>
)
}

return (
<div className="datetime-chip">
{startTime ? (
<p className="datetime-chip__time">
<span className="datetime-chip__icon">
<IconClock />
</span>
{startTime.utc && endTime.utc
? `${startTime.utc} - ${endTime.utc}`
: getLiteral('message:tbd')}
{`${startTime.utc} - ${endTime.utc}`}
<span className="datetime-chip__timezone">
{getLiteral('timezone:utc')}
</span>
Expand All @@ -23,9 +47,7 @@ const DateTimeChip = ({ startTime, endTime }) => {
<span className="datetime-chip__icon">
<IconClock />
</span>
{startTime.pt && endTime.pt
? `${startTime.pt} - ${endTime.pt}`
: getLiteral('message:tbd')}
{`${startTime.pt} - ${endTime.pt}`}
<span className="datetime-chip__timezone">
{getLiteral('timezone:pt')}
</span>
Expand Down
Loading
Loading