mirror of
https://github.com/fabrice404/olympics-calendar.git
synced 2026-02-06 14:19:54 +00:00
add non-team sports
This commit is contained in:
37
src/ics.js
Normal file
37
src/ics.js
Normal file
@ -0,0 +1,37 @@
|
||||
const fs = require("fs");
|
||||
|
||||
/**
|
||||
* generateICS generates the calendar for given events on ICS format
|
||||
* @param {string} title
|
||||
* @param {string} key
|
||||
* @param {object[]} events
|
||||
*/
|
||||
const generateICS = (title, key, events) => {
|
||||
const lines = [];
|
||||
lines.push("BEGIN:VCALENDAR");
|
||||
lines.push("VERSION:2.0");
|
||||
lines.push(`PRODID:-//fabrice404//olympics-calendar//${key}//EN`);
|
||||
lines.push(`X-WR-CALNAME:${title}`);
|
||||
lines.push(`NAME:${title}`);
|
||||
|
||||
events.forEach((event) => {
|
||||
lines.push("BEGIN:VEVENT");
|
||||
lines.push(
|
||||
...Object.entries(event)
|
||||
.filter(([key]) => !key.startsWith("_"))
|
||||
.map(([key, value]) => `${key}:${value}`),
|
||||
);
|
||||
lines.push("END:VEVENT");
|
||||
});
|
||||
|
||||
lines.push("END:VCALENDAR");
|
||||
|
||||
const calendarPath = `${__dirname}/../docs/${key}.ics`;
|
||||
const folder = calendarPath.split("/").slice(0, -1).join("/");
|
||||
fs.mkdirSync(folder, { recursive: true });
|
||||
fs.writeFileSync(calendarPath, lines.join("\r\n"));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
generateICS,
|
||||
};
|
||||
225
src/index.js
Normal file
225
src/index.js
Normal file
@ -0,0 +1,225 @@
|
||||
const cheerio = require("cheerio");
|
||||
const fs = require("fs");
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const postcss = require("postcss");
|
||||
const tailwindcss = require("tailwindcss");
|
||||
|
||||
const { getSportIcon } = require("./sports");
|
||||
const { isValidNOC, getNOCName, getNOCFlag } = require("./nocs");
|
||||
const { generateICS } = require("./ics");
|
||||
|
||||
const downloadSchedule = async (sportKey) => {
|
||||
const cacheFile = `${__dirname}/../cache/${sportKey}.html`;
|
||||
|
||||
if (!fs.existsSync(cacheFile)) {
|
||||
const response = await fetch(`https://olympics.com/en/paris-2024/schedule/${sportKey}`);
|
||||
const content = await response.text();
|
||||
fs.writeFileSync(cacheFile, content);
|
||||
}
|
||||
|
||||
const html = fs.readFileSync(cacheFile, "utf-8");
|
||||
const $ = cheerio.load(html);
|
||||
return JSON.parse($("#__NEXT_DATA__").text());
|
||||
};
|
||||
|
||||
const EVENTS = [];
|
||||
const NOCS = [];
|
||||
const SPORTS = [];
|
||||
|
||||
const addNOC = (noc) => {
|
||||
if (!NOCS.includes(noc)) {
|
||||
NOCS.push(noc);
|
||||
}
|
||||
};
|
||||
|
||||
const addSport = (sportKey, sportName) => {
|
||||
if (!SPORTS.find((sport) => sport.key === sportKey)) {
|
||||
SPORTS.push({ key: sportKey, name: sportName, NOCS: [] });
|
||||
}
|
||||
};
|
||||
|
||||
const addSportNOC = (sportKey, sportName, noc) => {
|
||||
addSport(sportKey, sportName);
|
||||
const sport = SPORTS.find((sport) => sport.key === sportKey);
|
||||
if (!sport.NOCS.includes(noc)) {
|
||||
sport.NOCS.push(noc);
|
||||
}
|
||||
};
|
||||
|
||||
const generateCalendars = () => {
|
||||
SPORTS
|
||||
.sort((a, b) => a.name > b.name ? 1 : -1)
|
||||
.forEach((sport) => {
|
||||
let events = EVENTS
|
||||
.filter((event) => event._SPORT === sport.key)
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
let key = `${sport.key}/general`;
|
||||
let title = `${getSportIcon(sport.key)} ${sport.name} | Paris 2024`;
|
||||
generateICS(title, key, events);
|
||||
|
||||
sport.NOCS.forEach((noc) => {
|
||||
events = EVENTS
|
||||
.filter((event) => event._SPORT === sport.key && event._NOCS.includes(noc))
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
key = `${sport.key}/${noc}`;
|
||||
title = `${getNOCFlag(noc)} ${getNOCName(noc)} ${sport.name} | Paris 2024`;
|
||||
generateICS(title, key, events);
|
||||
});
|
||||
});
|
||||
|
||||
NOCS.sort()
|
||||
.forEach((noc) => {
|
||||
const events = EVENTS
|
||||
.filter((event) => event._NOCS.includes(noc))
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
const key = `general/${noc}`;
|
||||
const title = `${getNOCFlag(noc)} ${getNOCName(noc)} | Paris 2024`;
|
||||
generateICS(title, key, events);
|
||||
});
|
||||
};
|
||||
|
||||
const extractSportCalendar = async (sportKey) => {
|
||||
const data = await downloadSchedule(sportKey);
|
||||
const sportName = data.query.pDisciplineLabel;
|
||||
const sportIcon = getSportIcon(sportKey);
|
||||
addSport(sportKey, sportName);
|
||||
|
||||
data.props.pageProps.scheduleDataSource.initialSchedule.units.forEach(unit => {
|
||||
unit.startDateTimeUtc = new Date(unit.startDate).toISOString().replace(".000", "");
|
||||
unit.endDateTimeUtc = new Date(unit.endDate).toISOString().replace(".000", "");
|
||||
|
||||
const event = {
|
||||
UID: `${sportKey}-${unit.startDateTimeUtc.replace(/[:-]/g, "")}`,
|
||||
DTSTAMP: unit.startDateTimeUtc.replace(/[:-]/g, ""),
|
||||
DTSTART: unit.startDateTimeUtc.replace(/[:-]/g, ""),
|
||||
DTEND: unit.endDateTimeUtc.replace(/[:-]/g, ""),
|
||||
DESCRIPTION: `${sportName} - ${unit.eventUnitName}`,
|
||||
SUMMARY: `${sportIcon} ${unit.eventUnitName}`.trim(),
|
||||
LOCATION: unit.venueDescription,
|
||||
_SPORT: sportKey,
|
||||
_NOCS: [],
|
||||
};
|
||||
|
||||
if (unit.competitors) {
|
||||
const competitors = unit.competitors
|
||||
.filter((competitor) => competitor.noc && isValidNOC(competitor.noc))
|
||||
.sort((a, b) => a.order > b.order ? 1 : -1);
|
||||
event._NOCS = competitors.map((competitor) => {
|
||||
addSportNOC(sportKey, sportName, competitor.noc);
|
||||
addNOC(competitor.noc);
|
||||
return competitor.noc;
|
||||
});
|
||||
|
||||
// two competitors, we put them in the summary
|
||||
if (competitors.length === 2) {
|
||||
const competitor1 = competitors.shift();
|
||||
const competitor2 = competitors.shift();
|
||||
|
||||
event.UID += `-${competitor1.noc}-${competitor2.noc}`;
|
||||
if (competitor1.name !== getNOCName(competitor1.noc)) {
|
||||
event.SUMMARY = `${sportIcon} ${competitor1.name} ${getNOCFlag(competitor1.noc)} - ${getNOCFlag(competitor2.noc)} ${competitor2.name}`;
|
||||
} else {
|
||||
event.SUMMARY = `${sportIcon} ${competitor1.noc} ${getNOCFlag(competitor1.noc)} - ${getNOCFlag(competitor2.noc)} ${competitor2.noc}`;
|
||||
}
|
||||
} else {
|
||||
// more than two, we put them in the description
|
||||
competitors.forEach((competitor) => {
|
||||
if (competitor.name !== getNOCName(competitor.noc)) {
|
||||
event.DESCRIPTION += `\n${getNOCFlag(competitor.noc)} ${competitor.name}`;
|
||||
} else {
|
||||
event.DESCRIPTION += `\n${getNOCFlag(competitor.noc)} ${competitor.noc}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
EVENTS.push(event);
|
||||
});
|
||||
};
|
||||
|
||||
const generateOutputPage = () => {
|
||||
const html = [];
|
||||
|
||||
const linkClass = "inline-block bg-slate-400 hover:bg-blue-400 text-white px-2 py-1 my-px rounded-lg text-base";
|
||||
|
||||
html.push("<table>");
|
||||
SPORTS.map((sport) => {
|
||||
html.push(`<tr class="even:bg-slate-200">`);
|
||||
html.push(`<th class="font-bold text-left">${sport.name}</td>`);
|
||||
html.push(`<td>`);
|
||||
html.push(`<a href="${sport.key}/general.ics" class="${linkClass}">Full schedule</a>`);
|
||||
sport.NOCS.sort().forEach((noc) => {
|
||||
html.push(`<a href="${sport.key}/${noc}.ics" class="${linkClass}">${getNOCFlag(noc)} ${noc}</a>`);
|
||||
});
|
||||
html.push("</td>");
|
||||
html.push("</tr>");
|
||||
});
|
||||
html.push("</table>");
|
||||
|
||||
const template = fs.readFileSync(`${__dirname}/template.html`, "utf-8");
|
||||
const output = template.replace("{{calendars}}", html.join("\n"));
|
||||
fs.writeFileSync("docs/index.html", output);
|
||||
|
||||
postcss([autoprefixer, tailwindcss])
|
||||
.process(fs.readFileSync(`${__dirname}/template.css`, "utf-8"), { from: "template.css", to: "docs/style.css" })
|
||||
.then((result) => {
|
||||
fs.writeFileSync("docs/style.css", result.css);
|
||||
});
|
||||
;
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
await Promise.all(
|
||||
[
|
||||
"3x3-basketball",
|
||||
"archery",
|
||||
"artistic-gymnastics",
|
||||
"artistic-swimming",
|
||||
"athletics",
|
||||
"badminton",
|
||||
"basketball",
|
||||
"beach-volleyball",
|
||||
"boxing",
|
||||
"breaking",
|
||||
"canoe-slalom",
|
||||
"canoe-sprint",
|
||||
"cycling-bmx-freestyle",
|
||||
"cycling-bmx-racing",
|
||||
"cycling-mountain-bike",
|
||||
"cycling-road",
|
||||
"cycling-track",
|
||||
"diving",
|
||||
"equestrian",
|
||||
"fencing",
|
||||
"football",
|
||||
"golf",
|
||||
"handball",
|
||||
"hockey",
|
||||
"judo",
|
||||
"marathon-swimming",
|
||||
"modern-pentathlon",
|
||||
"rhythmic-gymnastics",
|
||||
"rowing",
|
||||
"rugby-sevens",
|
||||
"sailing",
|
||||
"shooting",
|
||||
"skateboarding",
|
||||
"sport-climbing",
|
||||
"surfing",
|
||||
"swimming",
|
||||
"table-tennis",
|
||||
"taekwondo",
|
||||
"tennis",
|
||||
"trampoline-gymnastics",
|
||||
"triathlon",
|
||||
"volleyball",
|
||||
"water-polo",
|
||||
"weightlifting",
|
||||
"wrestling",
|
||||
]
|
||||
.map((key) => extractSportCalendar(key)),
|
||||
);
|
||||
generateCalendars();
|
||||
generateOutputPage();
|
||||
};
|
||||
|
||||
main();
|
||||
246
src/nocs.js
Normal file
246
src/nocs.js
Normal file
@ -0,0 +1,246 @@
|
||||
const NOCS = {
|
||||
AFG: { icon: "🇦🇫", name: "Afghanistan" },
|
||||
ALB: { icon: "🇦🇱", name: "Albania" },
|
||||
ALG: { icon: "🇩🇿", name: "Algeria" },
|
||||
AND: { icon: "🇦🇩", name: "Andorra" },
|
||||
ANG: { icon: "🇦🇴", name: "Angola" },
|
||||
ANT: { icon: "🇦🇬", name: "Antigua and Barbuda" },
|
||||
ARG: { icon: "🇦🇷", name: "Argentina" },
|
||||
ARM: { icon: "🇦🇲", name: "Armenia" },
|
||||
ARU: { icon: "🇦🇼", name: "Aruba" },
|
||||
ASA: { icon: "🇦🇸", name: "American Samoa" },
|
||||
AUS: { icon: "🇦🇺", name: "Australia" },
|
||||
AUT: { icon: "🇦🇹", name: "Austria" },
|
||||
AZE: { icon: "🇦🇿", name: "Azerbaijan" },
|
||||
BAH: { icon: "🇧🇸", name: "Bahamas" },
|
||||
BAN: { icon: "🇧🇩", name: "Bangladesh" },
|
||||
BAR: { icon: "🇧🇧", name: "Barbados" },
|
||||
BDI: { icon: "🇧🇮", name: "Burundi" },
|
||||
BEL: { icon: "🇧🇪", name: "Belgium" },
|
||||
BEN: { icon: "🇧🇯", name: "Benin" },
|
||||
BER: { icon: "🇧🇲", name: "Bermuda" },
|
||||
BHU: { icon: "🇧🇹", name: "Bhutan" },
|
||||
BIH: { icon: "🇧🇦", name: "Bosnia and Herzegovina" },
|
||||
BIZ: { icon: "🇧🇿", name: "Belize" },
|
||||
BOL: { icon: "🇧🇴", name: "Bolivia" },
|
||||
BOT: { icon: "🇧🇼", name: "Botswana" },
|
||||
BRA: { icon: "🇧🇷", name: "Brazil" },
|
||||
BRN: { icon: "🇧🇭", name: "Bahrain" },
|
||||
BRU: { icon: "🇧🇳", name: "Brunei" },
|
||||
BUL: { icon: "🇧🇬", name: "Bulgaria" },
|
||||
BUR: { icon: "🇧🇫", name: "Burkina Faso" },
|
||||
CAF: { icon: "🇨🇫", name: "Central African Republic" },
|
||||
CAM: { icon: "🇰🇭", name: "Cambodia" },
|
||||
CAN: { icon: "🇨🇦", name: "Canada" },
|
||||
CAY: { icon: "🇰🇾", name: "Cayman Islands" },
|
||||
CGO: { icon: "🇨🇬", name: "Congo" },
|
||||
CHA: { icon: "🇹🇩", name: "Chad" },
|
||||
CHI: { icon: "🇨🇱", name: "Chile" },
|
||||
CHN: { icon: "🇨🇳", name: "China" },
|
||||
CIV: { icon: "🇨🇮", name: "Côte d'Ivoire" },
|
||||
CMR: { icon: "🇨🇲", name: "Cameroon" },
|
||||
COD: { icon: "🇨🇩", name: "Democratic Republic of the Congo" },
|
||||
COK: { icon: "🇨🇰", name: "Cook Islands" },
|
||||
COL: { icon: "🇨🇴", name: "Colombia" },
|
||||
COM: { icon: "🇰🇲", name: "Comoros" },
|
||||
CPV: { icon: "🇨🇻", name: "Cabo Verde" },
|
||||
CRC: { icon: "🇨🇷", name: "Costa Rica" },
|
||||
CRO: { icon: "🇭🇷", name: "Croatia" },
|
||||
CUB: { icon: "🇨🇺", name: "Cuba" },
|
||||
CYP: { icon: "🇨🇾", name: "Cyprus" },
|
||||
CZE: { icon: "🇨🇿", name: "Czechia" },
|
||||
DEN: { icon: "🇩🇰", name: "Denmark" },
|
||||
DJI: { icon: "🇩🇯", name: "Djibouti" },
|
||||
DMA: { icon: "🇩🇲", name: "Dominica" },
|
||||
DOM: { icon: "🇩🇴", name: "Dominican Republic" },
|
||||
ECU: { icon: "🇪🇨", name: "Ecuador" },
|
||||
EGY: { icon: "🇪🇬", name: "Egypt" },
|
||||
EOR: { icon: "🏳️", name: "Refugee Olympic Team" },
|
||||
ERI: { icon: "🇪🇷", name: "Eritrea" },
|
||||
ESA: { icon: "🇸🇻", name: "El Salvador" },
|
||||
ESP: { icon: "🇪🇸", name: "Spain" },
|
||||
EST: { icon: "🇪🇪", name: "Estonia" },
|
||||
ETH: { icon: "🇪🇹", name: "Ethiopia" },
|
||||
FIJ: { icon: "🇫🇯", name: "Fiji" },
|
||||
FIN: { icon: "🇫🇮", name: "Finland" },
|
||||
FRA: { icon: "🇫🇷", name: "France" },
|
||||
FSM: { icon: "🇫🇲", name: "Federated States of Micronesia" },
|
||||
GAB: { icon: "🇬🇦", name: "Gabon" },
|
||||
GAM: { icon: "🇬🇲", name: "Gambia" },
|
||||
GBR: { icon: "🇬🇧", name: "Great Britain" },
|
||||
GBS: { icon: "🇬🇼", name: "Guinea-Bissau" },
|
||||
GEO: { icon: "🇬🇪", name: "Georgia" },
|
||||
GEQ: { icon: "🇬🇶", name: "Equatorial Guinea" },
|
||||
GER: { icon: "🇩🇪", name: "Germany" },
|
||||
GHA: { icon: "🇬🇭", name: "Ghana" },
|
||||
GRE: { icon: "🇬🇷", name: "Greece" },
|
||||
GRN: { icon: "🇬🇩", name: "Grenada" },
|
||||
GUA: { icon: "🇬🇹", name: "Guatemala" },
|
||||
GUI: { icon: "🇬🇳", name: "Guinea" },
|
||||
GUM: { icon: "🇬🇺", name: "Guam" },
|
||||
GUY: { icon: "🇬🇾", name: "Guyana" },
|
||||
HAI: { icon: "🇭🇹", name: "Haiti" },
|
||||
HKG: { icon: "🇭🇰", name: "Hong Kong" },
|
||||
HON: { icon: "🇭🇳", name: "Honduras" },
|
||||
HUN: { icon: "🇭🇺", name: "Hungary" },
|
||||
INA: { icon: "🇮🇩", name: "Indonesia" },
|
||||
IND: { icon: "🇮🇳", name: "India" },
|
||||
IRI: { icon: "🇮🇷", name: "Iran" },
|
||||
IRL: { icon: "🇮🇪", name: "Ireland" },
|
||||
IRQ: { icon: "🇮🇶", name: "Iraq" },
|
||||
ISL: { icon: "🇮🇸", name: "Iceland" },
|
||||
ISR: { icon: "🇮🇱", name: "Israel" },
|
||||
ISV: { icon: "🇻🇮", name: "U.S. Virgin Islands" },
|
||||
ITA: { icon: "🇮🇹", name: "Italy" },
|
||||
IVB: { icon: "🇻🇬", name: "British Virgin Islands" },
|
||||
JAM: { icon: "🇯🇲", name: "Jamaica" },
|
||||
JOR: { icon: "🇯🇴", name: "Jordan" },
|
||||
JPN: { icon: "🇯🇵", name: "Japan" },
|
||||
KAZ: { icon: "🇰🇿", name: "Kazakhstan" },
|
||||
KEN: { icon: "🇰🇪", name: "Kenya" },
|
||||
KGZ: { icon: "🇰🇬", name: "Kyrgyzstan" },
|
||||
KIR: { icon: "🇰🇮", name: "Kiribati" },
|
||||
KOR: { icon: "🇰🇷", name: "Korea" },
|
||||
KOS: { icon: "🇽🇰", name: "Kosovo" },
|
||||
KSA: { icon: "🇸🇦", name: "Saudi Arabia" },
|
||||
KUW: { icon: "🇰🇼", name: "Kuwait" },
|
||||
LAO: { icon: "🇱🇦", name: "Laos" },
|
||||
LAT: { icon: "🇱🇻", name: "Latvia" },
|
||||
LBA: { icon: "🇱🇾", name: "Libya" },
|
||||
LBN: { icon: "🇱🇧", name: "Lebanon" },
|
||||
LBR: { icon: "🇱🇷", name: "Liberia" },
|
||||
LCA: { icon: "🇱🇨", name: "Saint Lucia" },
|
||||
LES: { icon: "🇱🇸", name: "Lesotho" },
|
||||
LIE: { icon: "🇱🇮", name: "Liechtenstein" },
|
||||
LTU: { icon: "🇱🇹", name: "Lithuania" },
|
||||
LUX: { icon: "🇱🇺", name: "Luxembourg" },
|
||||
MAD: { icon: "🇲🇬", name: "Madagascar" },
|
||||
MAR: { icon: "🇲🇦", name: "Morocco" },
|
||||
MAS: { icon: "🇲🇾", name: "Malaysia" },
|
||||
MAW: { icon: "🇲🇼", name: "Malawi" },
|
||||
MDA: { icon: "🇲🇩", name: "Moldova" },
|
||||
MDV: { icon: "🇲🇻", name: "Maldives" },
|
||||
MEX: { icon: "🇲🇽", name: "Mexico" },
|
||||
MGL: { icon: "🇲🇳", name: "Mongolia" },
|
||||
MHL: { icon: "🇲🇭", name: "Marshall Islands" },
|
||||
MKD: { icon: "🇲🇰", name: "North Macedonia" },
|
||||
MLI: { icon: "🇲🇱", name: "Mali" },
|
||||
MLT: { icon: "🇲🇹", name: "Malta" },
|
||||
MNE: { icon: "🇲🇪", name: "Montenegro" },
|
||||
MON: { icon: "🇲🇨", name: "Monaco" },
|
||||
MOZ: { icon: "🇲🇿", name: "Mozambique" },
|
||||
MRI: { icon: "🇲🇺", name: "Mauritius" },
|
||||
MTN: { icon: "🇲🇷", name: "Mauritania" },
|
||||
MYA: { icon: "🇲🇲", name: "Myanmar" },
|
||||
NAM: { icon: "🇳🇦", name: "Namibia" },
|
||||
NCA: { icon: "🇳🇮", name: "Nicaragua" },
|
||||
NED: { icon: "🇳🇱", name: "Netherlands" },
|
||||
NEP: { icon: "🇳🇵", name: "Nepal" },
|
||||
NGR: { icon: "🇳🇬", name: "Nigeria" },
|
||||
NIG: { icon: "🇳🇪", name: "Niger" },
|
||||
NOR: { icon: "🇳🇴", name: "Norway" },
|
||||
NRU: { icon: "🇳🇷", name: "Nauru" },
|
||||
NZL: { icon: "🇳🇿", name: "New Zealand" },
|
||||
OMA: { icon: "🇴🇲", name: "Oman" },
|
||||
PAK: { icon: "🇵🇰", name: "Pakistan" },
|
||||
PAN: { icon: "🇵🇦", name: "Panama" },
|
||||
PAR: { icon: "🇵🇾", name: "Paraguay" },
|
||||
PER: { icon: "🇵🇪", name: "Peru" },
|
||||
PHI: { icon: "🇵🇭", name: "Philippines" },
|
||||
PLE: { icon: "🇵🇸", name: "Palestine" },
|
||||
PLW: { icon: "🇵🇼", name: "Palau" },
|
||||
PNG: { icon: "🇵🇬", name: "Papua New Guinea" },
|
||||
POL: { icon: "🇵🇱", name: "Poland" },
|
||||
POR: { icon: "🇵🇹", name: "Portugal" },
|
||||
PRK: { icon: "🇰🇵", name: "North Korea" },
|
||||
PUR: { icon: "🇵🇷", name: "Puerto Rico" },
|
||||
QAT: { icon: "🇶🇦", name: "Qatar" },
|
||||
ROU: { icon: "🇷🇴", name: "Romania" },
|
||||
RSA: { icon: "🇿🇦", name: "South Africa" },
|
||||
RWA: { icon: "🇷🇼", name: "Rwanda" },
|
||||
SAM: { icon: "🇼🇸", name: "Samoa" },
|
||||
SEN: { icon: "🇸🇳", name: "Senegal" },
|
||||
SEY: { icon: "🇸🇨", name: "Seychelles" },
|
||||
SGP: { icon: "🇸🇬", name: "Singapore" },
|
||||
SKN: { icon: "🇰🇳", name: "Saint Kitts and Nevis" },
|
||||
SLE: { icon: "🇸🇱", name: "Sierra Leone" },
|
||||
SLO: { icon: "🇸🇮", name: "Slovenia" },
|
||||
SMR: { icon: "🇸🇲", name: "San Marino" },
|
||||
SOL: { icon: "🇸🇧", name: "Solomon Islands" },
|
||||
SOM: { icon: "🇸🇴", name: "Somalia" },
|
||||
SRB: { icon: "🇷🇸", name: "Serbia" },
|
||||
SRI: { icon: "🇱🇰", name: "Sri Lanka" },
|
||||
SSD: { icon: "🇸🇸", name: "South Sudan" },
|
||||
STP: { icon: "🇸🇹", name: "Sao Tome and Principe" },
|
||||
SUD: { icon: "🇸🇩", name: "Sudan" },
|
||||
SUI: { icon: "🇨🇭", name: "Switzerland" },
|
||||
SUR: { icon: "🇸🇷", name: "Suriname" },
|
||||
SVK: { icon: "🇸🇰", name: "Slovakia" },
|
||||
SWE: { icon: "🇸🇪", name: "Sweden" },
|
||||
SWZ: { icon: "🇸🇿", name: "Eswatini" },
|
||||
SYR: { icon: "🇸🇾", name: "Syria" },
|
||||
TAN: { icon: "🇹🇿", name: "Tanzania" },
|
||||
TGA: { icon: "🇹🇴", name: "Tonga" },
|
||||
THA: { icon: "🇹🇭", name: "Thailand" },
|
||||
TJK: { icon: "🇹🇯", name: "Tajikistan" },
|
||||
TKM: { icon: "🇹🇲", name: "Turkmenistan" },
|
||||
TLS: { icon: "🇹🇱", name: "Timor-Leste" },
|
||||
TOG: { icon: "🇹🇬", name: "Togo" },
|
||||
TPE: { icon: "🇹🇼", name: "Chinese Taipei" },
|
||||
TTO: { icon: "🇹🇹", name: "Trinidad and Tobago" },
|
||||
TUN: { icon: "🇹🇳", name: "Tunisia" },
|
||||
TUR: { icon: "🇹🇷", name: "Türkiye" },
|
||||
TUV: { icon: "🇹🇻", name: "Tuvalu" },
|
||||
UAE: { icon: "🇦🇪", name: "United Arab Emirates" },
|
||||
UGA: { icon: "🇺🇬", name: "Uganda" },
|
||||
UKR: { icon: "🇺🇦", name: "Ukraine" },
|
||||
URU: { icon: "🇺🇾", name: "Uruguay" },
|
||||
USA: { icon: "🇺🇸", name: "United States" },
|
||||
UZB: { icon: "🇺🇿", name: "Uzbekistan" },
|
||||
VAN: { icon: "🇻🇺", name: "Vanuatu" },
|
||||
VEN: { icon: "🇻🇪", name: "Venezuela" },
|
||||
VIE: { icon: "🇻🇳", name: "Vietnam" },
|
||||
VIN: { icon: "🇻🇨", name: "Saint Vincent and the Grenadines" },
|
||||
YEM: { icon: "🇾🇪", name: "Yemen" },
|
||||
ZAM: { icon: "🇿🇲", name: "Zambia" },
|
||||
ZIM: { icon: "🇿🇼", name: "Zimbabwe" },
|
||||
};
|
||||
|
||||
/**
|
||||
* isValidNOC checks if the NOC code is in the NOCS list
|
||||
* @param {string} noc National Olympic Committee code
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isValidNOC = (noc) => NOCS[noc] !== undefined;
|
||||
|
||||
/**
|
||||
* getNOC returns the NOC name and icon from the NOC code
|
||||
* @param {string} noc National Olympic Committee code
|
||||
* @returns {object}
|
||||
*/
|
||||
const getNOC = (noc) => {
|
||||
if (isValidNOC(noc)) {
|
||||
return NOCS[noc];
|
||||
}
|
||||
throw new Error(`NOC code ${noc} not found`);
|
||||
};
|
||||
|
||||
/**
|
||||
* getNOCFlag returns the NOC icon from the NOC code
|
||||
* @param {string} noc National Olympic Committee code
|
||||
* @returns {string}
|
||||
*/
|
||||
const getNOCFlag = (noc) => getNOC(noc).icon;
|
||||
|
||||
/**
|
||||
* getNOCName returns the NOC name from the NOC code
|
||||
* @param {string} noc National Olympic Committee code
|
||||
* @returns
|
||||
*/
|
||||
const getNOCName = (noc) => getNOC(noc).name;
|
||||
|
||||
module.exports = {
|
||||
isValidNOC,
|
||||
getNOCFlag,
|
||||
getNOCName,
|
||||
};
|
||||
59
src/sports.js
Normal file
59
src/sports.js
Normal file
@ -0,0 +1,59 @@
|
||||
const SPORTS = {
|
||||
"3x3-basketball": "🏀",
|
||||
"archery": "🏹",
|
||||
"artistic-gymnastics": "🤸",
|
||||
"artistic-swimming": "🏊",
|
||||
"athletics": "🏃",
|
||||
"badminton": "🏸",
|
||||
"basketball": "🏀",
|
||||
"beach-volleyball": "🏐",
|
||||
"boxing": "🥊",
|
||||
"breaking": "🤸",
|
||||
"canoe-slalom": "🛶",
|
||||
"canoe-sprint": "🛶",
|
||||
"cycling-bmx-freestyle": "🚴",
|
||||
"cycling-bmx-racing": "🚴",
|
||||
"cycling-mountain-bike": "🚴",
|
||||
"cycling-road": "🚴",
|
||||
"cycling-track": "🚴",
|
||||
"diving": "🏊",
|
||||
"equestrian": "🏇",
|
||||
"fencing": "🤺",
|
||||
"football": "⚽",
|
||||
"golf": "⛳",
|
||||
"handball": "🤾",
|
||||
"hockey": "🏑",
|
||||
"judo": "🥋",
|
||||
"marathon-swimming": "🏊",
|
||||
"modern-pentathlon": "🤺",
|
||||
"rhythmic-gymnastics": "🤸",
|
||||
"rowing": "🚣",
|
||||
"rugby-sevens": "🏉",
|
||||
"sailing": "⛵",
|
||||
"shooting": "🔫",
|
||||
"skateboarding": "🛹",
|
||||
"sport-climbing": "🧗",
|
||||
"surfing": "🏄",
|
||||
"swimming": "🏊",
|
||||
"table-tennis": "🏓",
|
||||
"taekwondo": "🥋",
|
||||
"tennis":"🎾",
|
||||
"trampoline-gymnastics": "🤸",
|
||||
"triathlon": "🏊",
|
||||
"volleyball": "🏐",
|
||||
"water-polo": "🤽",
|
||||
"weightlifting": "🏋",
|
||||
"wrestling": "🤼",
|
||||
};
|
||||
|
||||
const getSportIcon = (sport) => {
|
||||
if (SPORTS[sport]) {
|
||||
return SPORTS[sport];
|
||||
}
|
||||
console.error(`No icon set for ${sport}`);
|
||||
return "";
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getSportIcon,
|
||||
};
|
||||
7
src/template.css
Normal file
7
src/template.css
Normal file
@ -0,0 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
font-size: 12px;
|
||||
}
|
||||
57
src/template.html
Normal file
57
src/template.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Paris 2024 Summer Olympic Games calendars</title>
|
||||
<link href="./style.css" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Paris 2024 Summer Olympic Games calendars">
|
||||
<meta name="keywords" content="Paris 2024, Summer Olympic Games, calendars">
|
||||
<meta name="author" content="Fabrice LAMANT">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center justify-between border-b pb-4 border-slate-900/10">
|
||||
<h1 class="text-4xl">Paris 2024 Summer Olympic Games calendars</h1>
|
||||
|
||||
<div>
|
||||
<a href="https://github.com/fabrice404/olympics-calendar" target="_blank"
|
||||
class="ml-6 block text-slate-200 hover:text-sky-500 w-10 h-10">
|
||||
<svg viewBox="0 0 16 16" class="w-10 h-10" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z">
|
||||
</path>
|
||||
</svg></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl pb-4 pt-8">How to add a calendar to your Google calendar</h2>
|
||||
<ol class="list-decimal ml-10">
|
||||
<li>Right click on a calendar and select "Copy link address"</li>
|
||||
<li>Open your Google calendar</li>
|
||||
<li>Click the plus sign next to “Other Calendars” on the left menu</li>
|
||||
<li>Click "From URL"</li>
|
||||
<li>Paste your calendar URL</li>
|
||||
<li>Click "Add calendar"</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{calendars}}
|
||||
</div>
|
||||
<div class="text-sm my-10 text-center">
|
||||
This webiste is not affiliated with the International Olympic Committee.
|
||||
All trademarks, logos and brand names are the property of their respective owners.
|
||||
</div>
|
||||
</div>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-0KQC1F1K4H"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() { dataLayer.push(arguments); }
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-0KQC1F1K4H');
|
||||
</script>
|
||||
</body>
|
||||
0
src/template.js
Normal file
0
src/template.js
Normal file
Reference in New Issue
Block a user