search field visibility changed, tournament import fixed
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 5m4s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 5m4s
This commit is contained in:
@@ -8,6 +8,48 @@ const MY_TT_BASE_URL = 'https://www.mytischtennis.de';
|
||||
const cleanText = (value = '') => String(value).replace(/<[^>]*>/g, ' ').replace(/ /gi, ' ').replace(/&/gi, '&').replace(/\s+/g, ' ').trim();
|
||||
const absoluteUrl = (href) => href.startsWith('http') ? href : `${MY_TT_BASE_URL}${href.startsWith('/') ? '' : '/'}${href}`;
|
||||
|
||||
function extractJsonArray(html, propertyName) {
|
||||
const propertyIndex = String(html).indexOf(`"${propertyName}":[`);
|
||||
if (propertyIndex < 0) return [];
|
||||
const start = String(html).indexOf('[', propertyIndex);
|
||||
let depth = 0;
|
||||
let quoted = false;
|
||||
let escaped = false;
|
||||
for (let index = start; index < String(html).length; index += 1) {
|
||||
const character = String(html)[index];
|
||||
if (quoted) {
|
||||
if (escaped) escaped = false;
|
||||
else if (character === '\\') escaped = true;
|
||||
else if (character === '"') quoted = false;
|
||||
continue;
|
||||
}
|
||||
if (character === '"') quoted = true;
|
||||
else if (character === '[') depth += 1;
|
||||
else if (character === ']') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
try { return JSON.parse(String(html).slice(start, index + 1)); }
|
||||
catch (_error) { return []; }
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function parseTournamentWorldCalendar(html, federation) {
|
||||
const tournaments = extractJsonArray(html, 'tournaments');
|
||||
return tournaments
|
||||
.filter((tournament) => tournament?.id && tournament?.name)
|
||||
.map((tournament) => ({
|
||||
title: cleanText(tournament.name).slice(0, 255),
|
||||
eventDate: tournament.formattedStartDate || null,
|
||||
organizer: cleanText(tournament.host || '').slice(0, 255) || null,
|
||||
location: cleanText([tournament.location_zip, tournament.location].filter(Boolean).join(' ')).slice(0, 255) || null,
|
||||
sourceUrl: `${MY_TT_BASE_URL}/turnierwelt/turniere/${encodeURIComponent(tournament.id)}`,
|
||||
federation: tournament.organization_short || federation,
|
||||
}));
|
||||
}
|
||||
|
||||
function parseCalendar(html, federation) {
|
||||
const items = [];
|
||||
const anchors = [...String(html).matchAll(/<a\b[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi)];
|
||||
@@ -31,9 +73,23 @@ class TournamentSuggestionService {
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (!club) throw new Error('Verein nicht gefunden.');
|
||||
const federation = String(club.myTischtennisFedNickname || 'HeTTV').trim();
|
||||
const calendarUrl = `${MY_TT_BASE_URL}/click-tt/${encodeURIComponent(federation)}/turnierkalender`;
|
||||
const startDate = new Date();
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
const endDate = new Date(startDate);
|
||||
endDate.setMonth(endDate.getMonth() + 6);
|
||||
const calendarParams = new URLSearchParams({
|
||||
association: federation,
|
||||
date_start: startDate.toISOString().slice(0, 10),
|
||||
date_end: endDate.toISOString().slice(0, 10),
|
||||
});
|
||||
const calendarUrl = `${MY_TT_BASE_URL}/turnierwelt/kalender?${calendarParams.toString()}`;
|
||||
const response = await axios.get(calendarUrl, { timeout: 30000, headers: { 'User-Agent': 'Trainingstagebuch/1.0 (+turnierkalender)' } });
|
||||
const parsed = parseCalendar(response.data, federation);
|
||||
let parsed = parseTournamentWorldCalendar(response.data, federation);
|
||||
if (!parsed.length) {
|
||||
const legacyCalendarUrl = `${MY_TT_BASE_URL}/click-tt/${encodeURIComponent(federation)}/turnierkalender`;
|
||||
const legacyResponse = await axios.get(legacyCalendarUrl, { timeout: 30000, headers: { 'User-Agent': 'Trainingstagebuch/1.0 (+turnierkalender)' } });
|
||||
parsed = parseCalendar(legacyResponse.data, federation);
|
||||
}
|
||||
if (!parsed.length) throw new Error('Im Turnierkalender konnten keine Turniere erkannt werden.');
|
||||
|
||||
let newCount = 0;
|
||||
|
||||
@@ -2185,6 +2185,8 @@ export default {
|
||||
.panel-header h3 { margin: 0; color: #17366d; font-size: 1rem; }
|
||||
.panel-header p { margin: 0; color: #64748b; font-size: .9rem; }
|
||||
.panel-toolbar { display: flex; align-items: center; gap: .6rem; margin-bottom: .75rem; }
|
||||
.suggestion-toolbar .title-input { flex: 1 1 22rem; min-width: 16rem; width: auto; }
|
||||
.suggestion-toolbar select { flex: 0 0 15rem; width: 15rem; max-width: 30%; }
|
||||
.toolbar-checkbox { display: inline-flex; align-items: center; gap: .35rem; color: #556070; font-size: .9rem; white-space: nowrap; }
|
||||
.toolbar-meta { color: #64748b; font-size: .88rem; white-space: nowrap; }
|
||||
.event-list { list-style: none; padding: 0; margin: 0; }
|
||||
@@ -2451,6 +2453,14 @@ th, td { border-bottom: 1px solid var(--border-color); padding: 0.5rem; text-ali
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.suggestion-toolbar .title-input,
|
||||
.suggestion-toolbar select {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.overview-stats {
|
||||
grid-template-columns: repeat(2, minmax(120px, 1fr));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user