Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
665cb75
Update liquidity-on-stellar-sdex-liquidity-pools.mdx
JFWooten4 Oct 25, 2023
fa6c87a
Merge branch 'main' of https://github.com/JFWooten4/stellar-docs
JFWooten4 Jun 15, 2024
162977b
Merge branch 'main' of https://github.com/JFWooten4/stellar-docs
JFWooten4 Dec 18, 2025
5400790
Merge branch 'main' of https://github.com/JFWooten4/stellar-docs
JFWooten4 Dec 22, 2025
b79fb02
Merge branch 'main' of https://github.com/JFWooten4/stellar-docs
JFWooten4 Feb 7, 2026
39733b9
Merge https://github.com/stellar/stellar-docs
JFWooten4 Mar 4, 2026
981f76a
Merge branch 'main' of https://github.com/JFWooten4/stellar-docs
JFWooten4 Apr 7, 2026
a9f6587
Merge https://github.com/stellar/stellar-docs
JFWooten4 Apr 15, 2026
01fe659
Add meetings index intro card
JFWooten4 Apr 15, 2026
bbfeddc
Clamp meeting previews on blog list pages
JFWooten4 Apr 15, 2026
76b26ec
Fix meeting time fallback and next-meeting calculation
JFWooten4 Apr 22, 2026
56f375f
Refactor meeting time fallback formatting
JFWooten4 Apr 22, 2026
adf9a13
prettier `lint`
JFWooten4 Apr 22, 2026
632b25f
Refactor meeting time formatting helpers
JFWooten4 Apr 22, 2026
0fc5221
Update developer meeting Discord invite URL
JFWooten4 Apr 22, 2026
4d81494
🗓️ Centralize developer meeting details
JFWooten4 Jul 14, 2026
f6624f9
🔗 Correct invite link
JFWooten4 Jul 14, 2026
c2f6fc0
🎟️ Correct event ID
JFWooten4 Jul 14, 2026
e06618f
🕒 Use localized timezone
JFWooten4 Jul 14, 2026
1c56896
♻️ Wrap meeting post items
JFWooten4 Jul 14, 2026
e4523bc
🎨 Use theme token for meeting buttons
JFWooten4 Jul 15, 2026
3024a5b
🗓️ Derive meeting weekday consistently
JFWooten4 Jul 15, 2026
2080049
♻️ Simplify meeting time conversion
JFWooten4 Jul 15, 2026
e9a91d0
🔗 Point intro card at X and YouTube, drop mailing list
kaankacar Jul 16, 2026
83654fb
Merge remote-tracking branch 'upstream' into 2145-pt-4
ElliotFriend Jul 16, 2026
58368f5
🕒 Use UTC for meeting schedule
JFWooten4 Jul 17, 2026
ecce463
🧹 Remove unused transcript CSS
JFWooten4 Jul 20, 2026
3edcb99
⚡ Defer localized meeting time
JFWooten4 Jul 20, 2026
fae0be7
Rename meeting intro component to MeetingsIntro
JFWooten4 Jul 24, 2026
e97fc0b
Update src/components/MeetingsIntro/index.tsx
JFWooten4 Jul 24, 2026
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
9 changes: 9 additions & 0 deletions meetings/schedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const MEETINGS_INFO = {
eventLink: "https://discord.com/invite/stellardev?event=1496911886321713163",
xLink: "https://x.com/BuildOnStellar",
youtubeLink: "https://www.youtube.com/@StellarDevelopmentFoundation",
timeZone: "America/Los_Angeles",
weekday: "Thursday", // must capitalize
hour: 13,
minute: 0,
} as const;
243 changes: 243 additions & 0 deletions src/components/MeetingIndexCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import React from "react";
import Link from "@docusaurus/Link";

import { MEETINGS_INFO } from "../../meetings/schedule";

type DateParts = {
year: number;
month: number;
day: number;
hour?: number;
minute?: number;
second?: number;
weekday?: string;
};

export default function MeetingIndexCard(): React.ReactElement {
const [localMeetingTime, setLocalMeetingTime] = React.useState<string | null>(
null,
);
const fallbackMeetingTime = formatMeetingTimeFallback();

React.useEffect(() => {
try {
const nextMeeting = getNextMeetingDate(new Date());
setLocalMeetingTime(formatMeetingDisplay(nextMeeting));
} catch {
// Keep the fallback display if timezone calculation fails.
}
}, []);

return (
<div className="card margin-bottom--lg">
<div className="card__body">
<h2 className="margin-bottom--sm">Developer Meetings</h2>
<p className="margin-bottom--sm" style={{ fontSize: "0.9rem" }}>
Comment thread
JFWooten4 marked this conversation as resolved.
Outdated
These are archived discussions of open Stellar meetings. Anyone can
attend them on {localMeetingTime ?? fallbackMeetingTime}. Join in the{" "}
<Link
href={MEETINGS_INFO.eventLink}
target="_blank"
rel="noreferrer noopener"
>
developer Discord
</Link>
, and follow{" "}
<Link
href={MEETINGS_INFO.xLink}
target="_blank"
rel="noreferrer noopener"
>
@BuildOnStellar

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My regulators don't like Discord, though I agree this is a chat out of scope for this venue. Just to answer the question, I've had my hand up throughout the entirety of past meetings without being brought up to speak. This leaves most of the latest meetings as just a few SDF team members mimicking their developments and viewpoints, without outside community input.

Suggested change
@BuildOnStellar
BuildOnStellar

For the display, I think it'd be a little cleaner without the @ prefix. We already explicitly follow this with "on X"—plus, it's a direct hyperlink. So there's no need to messy the view with a symbol that won't even be needed to copy over and find this username on the platform.

</Link>{" "}
on X or the{" "}
<Link
href={MEETINGS_INFO.youtubeLink}
target="_blank"
rel="noreferrer noopener"
>
Stellar Development Foundation
</Link>{" "}
on YouTube for streams and updates.
</p>
<div style={{ display: "flex", flexWrap: "wrap", gap: "0.75rem" }}>
<Link
className="button button--primary"
style={{ color: "var(--ifm-color-white)" }}
to="/meetings"
>
Recent
</Link>
<Link
className="button button--primary"
style={{ color: "var(--ifm-color-white)" }}
to="/meetings/tags/developer"
>
Developer
</Link>
<Link
className="button button--primary"
style={{ color: "var(--ifm-color-white)" }}
to="/meetings/tags/protocol"
>
Protocol
</Link>
</div>
</div>
</div>
);
}

function formatMeetingTimeFallback(): string {
return formatMeetingSchedule(
MEETINGS_INFO.weekday,
formatMeetingTime(makeDateInTimeZone(), MEETINGS_INFO.timeZone),
);
}

function formatMeetingDisplay(date: Date): string {
const formatter = new Intl.DateTimeFormat(undefined, {
weekday: "long",
});
const parts = formatter.formatToParts(date);
const weekday = parts.find((part) => part.type === "weekday")?.value;

if (!weekday) {
return formatMeetingTime(date);
}
return formatMeetingSchedule(weekday, formatMeetingTime(date));
}

function formatMeetingSchedule(weekday: string, time: string): string {
return `${weekday}s at ${time}`;
}

function formatMeetingTime(date: Date, timeZone?: string): string {
const options: Intl.DateTimeFormatOptions = {
hour: "numeric",
minute: "2-digit",
timeZoneName: timeZone ? "longGeneric" : "short",
};

if (timeZone) {
options.timeZone = timeZone;
}

const formatter = new Intl.DateTimeFormat(undefined, options);
return formatter.format(date);
}

function datePartsToUtc(parts: DateParts): number {
return Date.UTC(
parts.year,
parts.month - 1,
parts.day,
parts.hour ?? 0,
parts.minute ?? 0,
parts.second ?? 0,
);
}

function getTimeZoneOffset(date: Date, timeZone: string): number {
const dtf = new Intl.DateTimeFormat("en-US", {
timeZone,
hour12: false,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
const parts = dtf.formatToParts(date);
const values = parts.reduce<Record<string, number>>((acc, part) => {
if (part.type !== "literal") {
acc[part.type] = Number(part.value);
}
return acc;
}, {});
const asUtc = datePartsToUtc({
year: values.year,
month: values.month,
day: values.day,
hour: values.hour,
minute: values.minute,
second: values.second,
});
return (asUtc - date.getTime()) / 60000;
}

function makeDateInTimeZone(
parts: DateParts = {
year: 2019,
month: 11,
day: 4,
hour: MEETINGS_INFO.hour,
minute: MEETINGS_INFO.minute,
},
): Date {
const utcDate = new Date(datePartsToUtc(parts));
const offsetMinutes = getTimeZoneOffset(utcDate, MEETINGS_INFO.timeZone);
return new Date(utcDate.getTime() - offsetMinutes * 60000);
}

function getTimeZoneParts(date: Date, timeZone: string): DateParts {
const dtf = new Intl.DateTimeFormat("en-US", {
timeZone,
weekday: "long",
hour12: false,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
const parts = dtf.formatToParts(date);
const values = parts.reduce<Record<string, string>>((acc, part) => {
if (part.type !== "literal") {
acc[part.type] = part.value;
}
return acc;
}, {});
return {
year: Number(values.year),
month: Number(values.month),
day: Number(values.day),
hour: Number(values.hour),
minute: Number(values.minute),
second: Number(values.second),
weekday: values.weekday,
};
}

function getNextMeetingDate(now: Date): Date {
const ptParts = getTimeZoneParts(now, MEETINGS_INFO.timeZone);
const weekdayOrder = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
const currentIndex = weekdayOrder.indexOf(ptParts.weekday ?? "");
const targetIndex = weekdayOrder.indexOf(MEETINGS_INFO.weekday);
const rawDaysAhead = (targetIndex - currentIndex + 7) % 7;
const hasMeetingPassedToday =
rawDaysAhead === 0 &&
((ptParts.hour ?? 0) > MEETINGS_INFO.hour ||
((ptParts.hour ?? 0) === MEETINGS_INFO.hour &&
(ptParts.minute ?? 0) >= MEETINGS_INFO.minute));
const daysAhead =
rawDaysAhead === 0 && hasMeetingPassedToday ? 7 : rawDaysAhead;
const nextMeetingPT = {
year: ptParts.year,
month: ptParts.month,
day: ptParts.day + daysAhead,
hour: MEETINGS_INFO.hour,
minute: MEETINGS_INFO.minute,
};
return makeDateInTimeZone(nextMeetingPT);
}
14 changes: 14 additions & 0 deletions src/css/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@
--docusaurus-highlighted-code-line-bg: rgba(255, 255, 0, 0.15);
}

/* Meetings Index */

html.blog-list-page article .markdown {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 9;
line-clamp: 9;
overflow: hidden;
}

html.blog-list-page article .markdown details {
display: none; // Hide transcripts
}

Comment thread
JFWooten4 marked this conversation as resolved.
Outdated
:root[data-theme="dark"] {
--docusaurus-highlighted-code-line-bg: rgba(255, 255, 255, 0.1);
}
Expand Down
22 changes: 22 additions & 0 deletions src/theme/BlogPostItems/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { type ReactNode } from "react";
import { useLocation } from "@docusaurus/router";
import type { WrapperProps } from "@docusaurus/types";
import OriginalBlogPostItems from "@theme-original/BlogPostItems";
import type BlogPostItemsType from "@theme/BlogPostItems";

import MeetingIndexCard from "../../components/MeetingIndexCard";
Comment thread
JFWooten4 marked this conversation as resolved.
Outdated

const MEETING_ROUTE = "/meetings";

type Props = WrapperProps<typeof BlogPostItemsType>;

export default function BlogPostItemsWrapper(props: Props): ReactNode {
const { pathname } = useLocation();

return (
<>
{pathname === MEETING_ROUTE ? <MeetingIndexCard /> : null}
<OriginalBlogPostItems {...props} />
</>
);
}