Skip to content

Commit 30016d7

Browse files
tomvannuenenclaude
andcommitted
Add EventSpark integration for real-time workshop availability
- Added JavaScript to fetch and display upcoming workshops - Created workshop title mappings for matching - Added sample upcoming workshops data - Workshop cards now show session dates and registration buttons Note: GitHub Action workflow to be added separately 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 6236d91 commit 30016d7

File tree

5 files changed

+360
-0
lines changed

5 files changed

+360
-0
lines changed

_data/upcoming_workshops.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"last_updated": "2025-07-30T22:00:00Z",
3+
"total_workshops": 3,
4+
"workshops": [
5+
{
6+
"title": "R FUNDAMENTALS: PARTS 1-4",
7+
"date": "Aug 19, 2025",
8+
"time": "10:00 AM - 12:00 PM (GMT-7:00)",
9+
"datetime_iso": "2025-08-19T17:00:00+00:00",
10+
"registration_url": "https://dlab.my.salesforce-sites.com/events/event/home/rfundamentals20250819",
11+
"scraped_at": "2025-07-30T22:00:00Z"
12+
},
13+
{
14+
"title": "PYTHON FUNDAMENTALS: PARTS 1-3",
15+
"date": "Aug 19, 2025",
16+
"time": "01:00 PM - 03:00 PM (GMT-7:00)",
17+
"datetime_iso": "2025-08-19T20:00:00+00:00",
18+
"registration_url": "https://dlab.my.salesforce-sites.com/events/event/home/pythonfun20250819",
19+
"scraped_at": "2025-07-30T22:00:00Z"
20+
},
21+
{
22+
"title": "PYTHON APIS FOR LARGE LANGUAGE MODELS",
23+
"date": "Aug 22, 2025",
24+
"time": "01:00 PM - 03:00 PM (GMT-7:00)",
25+
"datetime_iso": "2025-08-22T20:00:00+00:00",
26+
"registration_url": "https://dlab.my.salesforce-sites.com/events/event/home/pythonllm20250822",
27+
"scraped_at": "2025-07-30T22:00:00Z"
28+
}
29+
]
30+
}

_data/workshop_mappings.yml

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Workshop Title Mappings
2+
# Maps Jekyll workshop titles to EventSpark titles
3+
# Use lowercase for comparison
4+
5+
mappings:
6+
# Python workshops
7+
"python fundamentals: parts 1-3":
8+
- "python fundamentals: parts 1-3"
9+
- "python fundamentals"
10+
- "python fundamentals (parts 1-3)"
11+
12+
"python fundamentals: parts 4-6":
13+
- "python fundamentals: parts 4-6"
14+
- "python fundamentals (parts 4-6)"
15+
16+
"python data wrangling and manipulation with pandas: parts 1-2":
17+
- "python data wrangling"
18+
- "python data wrangling: parts 1-2"
19+
- "data wrangling with pandas"
20+
21+
"python data visualization: parts 1-2":
22+
- "python data visualization"
23+
- "python visualization"
24+
- "data visualization with python"
25+
26+
"python machine learning fundamentals: parts 1-2":
27+
- "python machine learning"
28+
- "machine learning with python"
29+
- "python ml fundamentals"
30+
31+
"python web apis":
32+
- "python apis"
33+
- "python web apis"
34+
- "apis with python"
35+
36+
"python apis for large language models":
37+
- "python apis for large language models"
38+
- "python llm apis"
39+
- "llm apis with python"
40+
41+
# R workshops
42+
"r fundamentals: parts 1-4":
43+
- "r fundamentals: parts 1-4"
44+
- "r fundamentals"
45+
- "r fundamentals (parts 1-4)"
46+
47+
"r data wrangling and manipulation: parts 1-2":
48+
- "r data wrangling"
49+
- "data wrangling with r"
50+
- "r tidyverse"
51+
52+
"r data visualization":
53+
- "r visualization"
54+
- "data visualization with r"
55+
- "r ggplot"
56+
57+
"r machine learning with tidymodels: parts 1-2":
58+
- "r machine learning"
59+
- "machine learning with r"
60+
- "r tidymodels"
61+
62+
# AI workshops
63+
"ai-assisted coding with r":
64+
- "ai-assisted coding with r"
65+
- "r copilot"
66+
- "copilot with r"
67+
- "ai coding r"
68+
69+
"ai-assisted coding with python":
70+
- "ai-assisted coding with python"
71+
- "python copilot"
72+
- "copilot with python"
73+
- "ai coding python"
74+
75+
"python gpt fundamentals":
76+
- "gpt fundamentals"
77+
- "chatgpt fundamentals"
78+
- "llm fundamentals"
79+
80+
"prompt engineering":
81+
- "prompt engineering"
82+
- "prompting workshop"
83+
- "ai prompting"
84+
85+
# Other workshops
86+
"git fundamentals":
87+
- "git fundamentals"
88+
- "git basics"
89+
- "introduction to git"
90+
91+
"github fundamentals":
92+
- "github fundamentals"
93+
- "github basics"
94+
- "introduction to github"
95+
96+
"command line fundamentals":
97+
- "command line"
98+
- "bash fundamentals"
99+
- "terminal basics"

data/upcoming_workshops.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"last_updated": "2025-07-30T22:00:00Z",
3+
"total_workshops": 3,
4+
"workshops": [
5+
{
6+
"title": "R FUNDAMENTALS: PARTS 1-4",
7+
"date": "Aug 19, 2025",
8+
"time": "10:00 AM - 12:00 PM (GMT-7:00)",
9+
"datetime_iso": "2025-08-19T17:00:00+00:00",
10+
"registration_url": "https://dlab.my.salesforce-sites.com/events/event/home/rfundamentals20250819",
11+
"scraped_at": "2025-07-30T22:00:00Z"
12+
},
13+
{
14+
"title": "PYTHON FUNDAMENTALS: PARTS 1-3",
15+
"date": "Aug 19, 2025",
16+
"time": "01:00 PM - 03:00 PM (GMT-7:00)",
17+
"datetime_iso": "2025-08-19T20:00:00+00:00",
18+
"registration_url": "https://dlab.my.salesforce-sites.com/events/event/home/pythonfun20250819",
19+
"scraped_at": "2025-07-30T22:00:00Z"
20+
},
21+
{
22+
"title": "PYTHON APIS FOR LARGE LANGUAGE MODELS",
23+
"date": "Aug 22, 2025",
24+
"time": "01:00 PM - 03:00 PM (GMT-7:00)",
25+
"datetime_iso": "2025-08-22T20:00:00+00:00",
26+
"registration_url": "https://dlab.my.salesforce-sites.com/events/event/home/pythonllm20250822",
27+
"scraped_at": "2025-07-30T22:00:00Z"
28+
}
29+
]
30+
}
-1.29 MB
Loading
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Workshop Status Updates
2+
// Fetches upcoming workshop data and updates the UI
3+
4+
(function() {
5+
'use strict';
6+
7+
// Configuration
8+
const UPCOMING_WORKSHOPS_URL = 'data/upcoming_workshops.json';
9+
const WORKSHOP_MAPPINGS_URL = 'data/workshop_mappings.yml';
10+
11+
// Cache for performance
12+
let workshopData = null;
13+
let mappingsData = null;
14+
15+
// Initialize when DOM is ready
16+
if (document.readyState === 'loading') {
17+
document.addEventListener('DOMContentLoaded', init);
18+
} else {
19+
init();
20+
}
21+
22+
function init() {
23+
// Add loading state to all badges
24+
document.querySelectorAll('.upcoming-session-badge').forEach(badge => {
25+
badge.classList.add('loading');
26+
});
27+
28+
// Fetch workshop data
29+
fetchWorkshopData();
30+
}
31+
32+
async function fetchWorkshopData() {
33+
try {
34+
// Fetch upcoming workshops JSON
35+
const response = await fetch(UPCOMING_WORKSHOPS_URL);
36+
if (response.ok) {
37+
workshopData = await response.json();
38+
updateAllWorkshopCards();
39+
} else {
40+
console.warn('No upcoming workshops data found');
41+
removeLoadingStates();
42+
}
43+
} catch (error) {
44+
console.error('Error fetching workshop data:', error);
45+
removeLoadingStates();
46+
}
47+
}
48+
49+
function updateAllWorkshopCards() {
50+
if (!workshopData || !workshopData.workshops) return;
51+
52+
// Get all workshop cards
53+
const workshopCards = document.querySelectorAll('[data-workshop-title]');
54+
55+
workshopCards.forEach(card => {
56+
const cardTitle = card.dataset.workshopTitle;
57+
const matchingWorkshop = findMatchingWorkshop(cardTitle);
58+
59+
if (matchingWorkshop) {
60+
updateWorkshopCard(card, matchingWorkshop);
61+
} else {
62+
showNoUpcomingSessions(card);
63+
}
64+
});
65+
}
66+
67+
function findMatchingWorkshop(cardTitle) {
68+
if (!workshopData || !workshopData.workshops) return null;
69+
70+
// Direct title match first
71+
let match = workshopData.workshops.find(w =>
72+
normalizeTitle(w.title) === normalizeTitle(cardTitle)
73+
);
74+
75+
if (match) return match;
76+
77+
// Try fuzzy matching
78+
for (const workshop of workshopData.workshops) {
79+
if (titlesMatch(cardTitle, workshop.title)) {
80+
return workshop;
81+
}
82+
}
83+
84+
return null;
85+
}
86+
87+
function normalizeTitle(title) {
88+
return title.toLowerCase()
89+
.replace(/[^a-z0-9\s]/g, '')
90+
.replace(/\s+/g, ' ')
91+
.trim();
92+
}
93+
94+
function titlesMatch(title1, title2) {
95+
const norm1 = normalizeTitle(title1);
96+
const norm2 = normalizeTitle(title2);
97+
98+
// Check if one contains the other
99+
if (norm1.includes(norm2) || norm2.includes(norm1)) {
100+
return true;
101+
}
102+
103+
// Check for common workshop patterns
104+
const patterns = [
105+
/python fundamentals/i,
106+
/r fundamentals/i,
107+
/data wrangling/i,
108+
/machine learning/i,
109+
/data visualization/i,
110+
/ai.?assisted coding/i,
111+
/copilot/i
112+
];
113+
114+
for (const pattern of patterns) {
115+
if (pattern.test(title1) && pattern.test(title2)) {
116+
return true;
117+
}
118+
}
119+
120+
return false;
121+
}
122+
123+
function updateWorkshopCard(card, workshop) {
124+
// Update badge
125+
if (card.classList.contains('upcoming-session-badge')) {
126+
card.classList.remove('loading');
127+
const badge = createSessionBadge(workshop);
128+
card.innerHTML = badge;
129+
}
130+
131+
// Update register button
132+
if (card.classList.contains('register-button-container') && workshop.registration_url) {
133+
const button = createRegisterButton(workshop);
134+
card.innerHTML = button;
135+
}
136+
}
137+
138+
function createSessionBadge(workshop) {
139+
const sessionDate = new Date(workshop.datetime_iso);
140+
const now = new Date();
141+
const daysUntil = Math.floor((sessionDate - now) / (1000 * 60 * 60 * 24));
142+
143+
let badgeClass = 'badge-upcoming';
144+
let badgeText = `Next: ${formatDate(sessionDate)}`;
145+
146+
if (daysUntil <= 7) {
147+
badgeClass = 'badge-upcoming';
148+
if (daysUntil === 0) {
149+
badgeText = `Today at ${formatTime(sessionDate)}`;
150+
} else if (daysUntil === 1) {
151+
badgeText = `Tomorrow at ${formatTime(sessionDate)}`;
152+
}
153+
} else if (daysUntil <= 30) {
154+
badgeClass = 'badge-upcoming-soon';
155+
}
156+
157+
return `<span class="badge badge-pill ${badgeClass}">
158+
<i class="fas fa-calendar-alt"></i> ${badgeText}
159+
</span>`;
160+
}
161+
162+
function createRegisterButton(workshop) {
163+
return `<a href="${workshop.registration_url}"
164+
class="btn btn-sm btn-register"
165+
target="_blank">
166+
<i class="fas fa-user-plus"></i> Register Now
167+
</a>`;
168+
}
169+
170+
function showNoUpcomingSessions(card) {
171+
if (card.classList.contains('upcoming-session-badge')) {
172+
card.classList.remove('loading');
173+
card.innerHTML = `<span class="badge badge-pill badge-no-sessions">
174+
<i class="fas fa-clock"></i> No scheduled sessions
175+
</span>`;
176+
}
177+
}
178+
179+
function removeLoadingStates() {
180+
document.querySelectorAll('.upcoming-session-badge.loading').forEach(badge => {
181+
badge.classList.remove('loading');
182+
});
183+
}
184+
185+
function formatDate(date) {
186+
const options = { month: 'short', day: 'numeric' };
187+
return date.toLocaleDateString('en-US', options);
188+
}
189+
190+
function formatTime(date) {
191+
const options = { hour: 'numeric', minute: '2-digit', hour12: true };
192+
return date.toLocaleTimeString('en-US', options);
193+
}
194+
195+
// Expose functions for debugging
196+
window.workshopStatus = {
197+
refresh: fetchWorkshopData,
198+
getData: () => workshopData
199+
};
200+
201+
})();

0 commit comments

Comments
 (0)