Skip to content

Commit 7d10707

Browse files
Dale KunceDale Kunce
authored andcommitted
OSMcal functionality
1 parent a3d17b4 commit 7d10707

4 files changed

Lines changed: 419 additions & 104 deletions

File tree

app/_includes/events.html

Lines changed: 6 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
<div id="site-canvas">
55
<div class="header-image header-events">
66
<div class="dark-overlay center-text">
7-
<h1 class="title feature-header">{{site.data[locale].events.title}}</h1>
7+
<h1 class="feature-header">{{site.data[locale].events.title}}</h1>
88
</div>
99
</div>
1010
<div class="row center-text">
11+
<p style="margin-bottom:30px; max-width: 600px; margin-left: auto; margin-right: auto;">
12+
This is a selection of OSM events from OSMCal.org. For more OSM events in your community be sure to visit OSMCal.org.
13+
</p>
1114
<p style="margin-bottom:20px;">
12-
<a class="btn btn-blue arw" href="https://forms.gle/Y9YogcXtrdTBxWjh6">
15+
<a class="btn btn-blue arw" href="https://osmcal.org/event/add/">
1316
{{site.data[locale].events.register_event}}
1417
<img src="{{ site.baseurl }}/assets/graphics/meta/Arrow.svg" alt="{{site.data[locale].img-alt.Arrow}}" />
1518
</a>
@@ -22,105 +25,5 @@ <h1 class="title feature-header">{{site.data[locale].events.title}}</h1>
2225

2326
</div>
2427

25-
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"
26-
integrity="sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nWfLxhC9dHQY+J8MFvs5Z3lqlOGSN8P0Fdb8M7Lxf+DFLVwGjvA=="
27-
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
2828
<script src="https://cdn.jsdelivr.net/npm/luxon@3.5.0/build/global/luxon.min.js"
29-
integrity="sha512-rsFCO7RWaAo2L8ByT6EYpURoXngG8Vr8kIrUfYxZ3aDMkIe2j8KKj3D1gfHxw3xyYFhOkSbBGgCXnRJOIcFQ2Q=="
30-
crossorigin="anonymous"></script>
31-
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"
32-
integrity="sha512-dfX5uYVXzyU8+KHqj8bjo2A6+5rGYOKGuv5Q+ywXd1PGKhVFHCJqR5gCKJhKlmKJRxbqYu6v6eQ3s6q8dAJmE2Q=="
33-
crossorigin="anonymous"></script>
34-
<script>
35-
36-
37-
// Modern ES6+ constants for event data keys
38-
const EVENT_KEYS = {
39-
name: "What is the name of your event?",
40-
location: "Where will your event be held?",
41-
placeUrl: "Please insert a link to your venue on openstreetmap.org",
42-
signup: "Please insert a link where people can sign up",
43-
date: "What date is your event taking place? (as YYYY-MM-DD)",
44-
start: "What time does your event start? ",
45-
end: "What time does your event end?",
46-
country: "Country?",
47-
eventType: "What will the event that you are hosting include? Check all that apply:"
48-
};
49-
50-
const today = luxon.DateTime.local().startOf('day');
51-
const siteBaseUrl = "{{ site.baseurl }}";
52-
53-
const init = () => {
54-
Papa.parse('{{ site.baseurl }}/assets/google-sheets/events.csv', {
55-
download: true,
56-
header: true,
57-
complete: (results) => {
58-
const data = results.data
59-
.filter(d => {
60-
const eventDate = luxon.DateTime.fromISO(d[EVENT_KEYS.date]);
61-
return eventDate.isValid && eventDate >= today;
62-
})
63-
.sort((x, y) => d3.ascending(x[EVENT_KEYS.date], y[EVENT_KEYS.date]));
64-
65-
console.log('Filtered events:', data);
66-
buildEvents(data);
67-
}
68-
});
69-
};
70-
window.addEventListener('DOMContentLoaded', init)
71-
72-
const buildEvents = (data) => {
73-
console.log('Building events display:', data);
74-
75-
d3.select('#eventList').selectAll('div').data(data)
76-
.enter().append('div').classed('column', true)
77-
.html(d => {
78-
const countryIso = d[EVENT_KEYS.country]
79-
.substring(d[EVENT_KEYS.country].indexOf("[") + 1, d[EVENT_KEYS.country].indexOf("]"))
80-
.toLowerCase();
81-
82-
const signupHtml = d[EVENT_KEYS.signup]
83-
? `<a class="btn btn-grn" href="${d[EVENT_KEYS.signup]}" target="">{{site.data[locale].events.sign_up}}</a>`
84-
: "";
85-
86-
const placeHtml = d[EVENT_KEYS.placeUrl]
87-
? `<a href="${d[EVENT_KEYS.placeUrl]}"><b>${d[EVENT_KEYS.location]}</b></a>`
88-
: `<b>${d[EVENT_KEYS.location]}</b>`;
89-
90-
const eventType = d[EVENT_KEYS.eventType]
91-
.replace(" (buildings in iD Editor)", "")
92-
.replace(" (roads, waterways, JOSM)", "")
93-
.replace(" (review tasks as a group)", "")
94-
.replace(" (map-walks, map-drives, map-cycling map-dogsledding in your own community)", "")
95-
.replace(" (tool share)", "");
96-
97-
const eventHtml = `<div class="event-sub-container">
98-
<div class="event-top-section clearfix">
99-
<div class="sub-head">
100-
<img class="event-images" src="${siteBaseUrl}/assets/graphics/flags/4x3/${countryIso}.svg" width="24px" />
101-
<h3 class="event-header">${d[EVENT_KEYS.name]}</h3>
102-
${signupHtml}
103-
</div>
104-
</div>
105-
<div class="event-maindetails clearfix">
106-
<div class="event-details-left">
107-
<span class="emphasizedNumber">
108-
${luxon.DateTime.fromISO(d[EVENT_KEYS.date]).day}
109-
</span>
110-
<p><b>${luxon.DateTime.fromISO(d[EVENT_KEYS.date]).setLocale("{{locale}}").toLocaleString({ month: 'long'})}</b></p>
111-
</div>
112-
<div class="event-details-right">
113-
<div class="textbox" style="padding-top:8px">
114-
<p>${placeHtml}</p>
115-
<p class="event-details">${luxon.DateTime.fromISO(d[EVENT_KEYS.date]).setLocale("{{locale}}").toLocaleString({ weekday: 'long'})}, ${d[EVENT_KEYS.start].replace(":00 ","")} - ${d[EVENT_KEYS.end].replace(":00 ","")} UTC</p>
116-
<p class="event-details">${eventType}</p>
117-
</div>
118-
</div>
119-
</div>
120-
</div>`;
121-
122-
return eventHtml;
123-
});
124-
};
125-
126-
</script>
29+
crossorigin="anonymous"></script>

app/assets/scripts/events.js

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
console.log('🚀 Events.js loading...');
2+
3+
// Simple constants
4+
const EVENTS_JSON_URL = '/assets/data/events.json';
5+
6+
// Simple build function - 2019 style
7+
const buildEvents = (events) => {
8+
console.log('📋 Building', events.length, 'events...');
9+
10+
const eventList = document.getElementById('eventList');
11+
if (!eventList) {
12+
console.error('❌ No eventList found!');
13+
return;
14+
}
15+
16+
// Clear and show progress
17+
eventList.innerHTML = '';
18+
19+
if (events.length === 0) {
20+
eventList.innerHTML = '<div class="column"><h3>No upcoming Missing Maps events found.</h3></div>';
21+
return;
22+
}
23+
24+
// Process each event using old template style in Foundation cards
25+
events.forEach((event, index) => {
26+
console.log('📅 Processing:', event.name);
27+
28+
try {
29+
// Parse date with Luxon
30+
const startDate = luxon.DateTime.fromISO(event.date.start);
31+
const endDate = event.date.end ? luxon.DateTime.fromISO(event.date.end) : null;
32+
33+
// Extract event ID from URL for join link
34+
const urlParts = event.url.split('/').filter(part => part.length > 0); // Remove empty parts
35+
const eventId = urlParts[urlParts.length - 1]; // Get last non-empty part
36+
const joinUrl = `https://osmcal.org/event/${eventId}/join`;
37+
38+
// Get country code for flag
39+
const getCountryCode = (locationString) => {
40+
const countryMap = {
41+
'United States': 'us', 'USA': 'us', 'US': 'us',
42+
'United Kingdom': 'gb', 'UK': 'gb', 'Britain': 'gb',
43+
'Germany': 'de', 'Deutschland': 'de',
44+
'France': 'fr', 'République française': 'fr',
45+
'Canada': 'ca', 'España': 'es', 'Spain': 'es',
46+
'Italy': 'it', 'Italia': 'it', 'Netherlands': 'nl',
47+
'Switzerland': 'ch', 'Czech Republic': 'cz', 'Czechia': 'cz',
48+
'Slovakia': 'sk', 'Norway': 'no', 'Norge': 'no'
49+
};
50+
51+
// Extract country from location string (last part after last comma)
52+
if (!locationString) return 'online'; // Default for online events
53+
const parts = locationString.split(',');
54+
const country = parts[parts.length - 1].trim();
55+
56+
return countryMap[country] || country.toLowerCase().substring(0, 2);
57+
};
58+
59+
// Handle events with or without location data
60+
const hasLocation = event.location && event.location.detailed;
61+
const countryCode = hasLocation ? getCountryCode(event.location.detailed) : 'online';
62+
63+
// Format times like the old template
64+
const startTime = startDate.toFormat('HH:mm');
65+
const endTime = endDate ? endDate.toFormat('HH:mm') : '';
66+
const timeDisplay = endTime ? `${startTime} - ${endTime}` : startTime;
67+
68+
// Create location string - handle online events
69+
const location = hasLocation
70+
? `${event.location.venue}, ${event.location.detailed}`
71+
: 'Online Event';
72+
73+
// Determine flag image or Missing Maps logo for online events
74+
const flagImage = hasLocation
75+
? `<img class="event-images" src="/assets/graphics/flags/4x3/${countryCode}.svg" width="72px" />`
76+
: '<img class="event-images mm-logo" src="/assets/graphics/content/MMlogo-Outlined.svg" width="72px" />';
77+
78+
// Create event container - Foundation card with old template style
79+
const eventContainer = document.createElement('div');
80+
eventContainer.className = 'column event-card';
81+
82+
eventContainer.innerHTML = `
83+
<div class="card-section">
84+
<div class="event-top-section clearfix">
85+
<div class="sub-head">
86+
<div class="event-header-container">
87+
<div class="event-flag">${flagImage}</div>
88+
<h3 class="event-header">${event.name}</h3>
89+
</div>
90+
</div>
91+
</div>
92+
<div class="event-maindetails clearfix">
93+
<div class="event-details-left">
94+
<span class="emphasizedNumber">${startDate.day}</span>
95+
<p><b>${startDate.toFormat('MMMM')}</b></p>
96+
</div>
97+
<div class="event-details-right">
98+
<div class="textbox" style="padding-top:8px">
99+
<p>${location}</p>
100+
<p class="event-details">${startDate.toFormat('cccc')}, ${timeDisplay}</p>
101+
<div class="event-buttons" style="display: flex; gap: 10px; flex-wrap: wrap;">
102+
<a href="${event.url}" target="_blank" rel="noopener noreferrer"
103+
class="btn">
104+
View Event
105+
</a>
106+
<a href="${joinUrl}" target="_blank" rel="noopener noreferrer"
107+
class="btn btn-grn">
108+
Attend
109+
</a>
110+
</div>
111+
</div>
112+
</div>
113+
</div>
114+
</div>
115+
`;
116+
117+
eventList.appendChild(eventContainer);
118+
console.log('✅ Added event:', event.name);
119+
} catch (error) {
120+
console.error('❌ Error processing event:', event.name, error);
121+
// Extract event ID for join link even in error case
122+
const urlParts = event.url.split('/').filter(part => part.length > 0);
123+
const eventId = urlParts[urlParts.length - 1];
124+
const joinUrl = `https://osmcal.org/event/${eventId}/join`;
125+
126+
// Still add a simple version using old template style
127+
const simpleContainer = document.createElement('div');
128+
simpleContainer.className = 'column event-card';
129+
simpleContainer.innerHTML = `
130+
<div class="card-section">
131+
<div class="event-top-section clearfix">
132+
<div class="sub-head">
133+
<h3 class="event-header">${event.name}</h3>
134+
</div>
135+
</div>
136+
<div class="event-maindetails clearfix">
137+
<div class="event-details-right">
138+
<div class="textbox" style="padding-top:8px">
139+
<p>Error loading event details</p>
140+
<div class="event-buttons" style="display: flex; gap: 10px; flex-wrap: wrap;">
141+
<a href="${event.url}" target="_blank" rel="noopener noreferrer"
142+
class="btn">
143+
View Event
144+
</a>
145+
<a href="${joinUrl}" target="_blank" rel="noopener noreferrer"
146+
class="btn btn-grn">
147+
Attend
148+
</a>
149+
</div>
150+
</div>
151+
</div>
152+
</div>
153+
</div>
154+
`;
155+
eventList.appendChild(simpleContainer);
156+
}
157+
});
158+
159+
console.log('✅ All events built with Foundation cards!');
160+
};
161+
162+
// Fetch events
163+
const fetchEvents = async () => {
164+
try {
165+
console.log('📡 Fetching events...');
166+
const response = await fetch(EVENTS_JSON_URL);
167+
if (!response.ok) throw new Error('HTTP ' + response.status);
168+
const events = await response.json();
169+
console.log('📦 Got', events.length, 'events');
170+
return events;
171+
} catch (error) {
172+
console.error('💥 Fetch error:', error);
173+
return [];
174+
}
175+
};
176+
177+
// Main init
178+
const init = async () => {
179+
console.log('🎯 Initializing...');
180+
181+
// Check Luxon
182+
if (typeof luxon === 'undefined') {
183+
console.error('💥 Luxon not available!');
184+
return;
185+
}
186+
console.log('✅ Luxon available');
187+
188+
const events = await fetchEvents();
189+
buildEvents(events);
190+
};
191+
192+
// Start when DOM ready
193+
document.addEventListener('DOMContentLoaded', () => {
194+
console.log('📄 DOM ready!');
195+
196+
const eventList = document.getElementById('eventList');
197+
if (!eventList) {
198+
console.error('💥 eventList not found!');
199+
return;
200+
}
201+
202+
eventList.innerHTML = '<div class="column"><h3>Loading Missing Maps events...</h3></div>';
203+
204+
init();
205+
});
206+
207+
console.log('✅ Events.js loaded!');

app/assets/styles/_base.scss

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ input[type="text"]{
596596
color: white;
597597
padding: 8px 14px 8px 14px;
598598
margin: 10px 8px 6px 0px;
599-
font-size: 0.8em;
599+
font-size: 1em; // Increased from 0.8em
600600
font-weight: 400;
601601
font-family: 'Lato';
602602
text-decoration:none;
@@ -605,6 +605,20 @@ input[type="text"]{
605605
transition-duration: 0.12s;
606606
transition-timing-function: linear;
607607
line-height: 23px;
608+
609+
&:hover{
610+
background-color: darken($ButtonColor, 12%);
611+
color: white;
612+
}
613+
&:active{
614+
background-color: darken($ButtonColor, 15%);
615+
color: white;
616+
}
617+
&:focus{
618+
background-color: darken($ButtonColor, 12%);
619+
color: white;
620+
}
621+
608622
a{
609623
color: white;
610624
font-size: 1em;

0 commit comments

Comments
 (0)