fix: modal onClose callback, calendar popup truncation, datetime validation

- modal.js: add onClose callback to openModal(), fix _initialFormTimeout
  cleanup, deduplicate escape/overlay-click handlers in confirmModal,
  promptModal, selectModal using the new callback
- calendar.js: replace popup.innerHTML with insertAdjacentHTML (constraint),
  add truncateDescription() to cap long event descriptions at 500 chars
- validate.js: extend DATETIME_RE to cover ISO 8601 with ms/timezone,
  normalise datetime values to YYYY-MM-DDTHH:MM on input
- index.js: include app version in startup log message
- docker-compose.yml / .env.example: switch from named volume to
  host-mounted DATA_DIR/BACKUP_DIR with sane defaults
- docs/installation.md: document host-mount data paths

Co-Authored-By: Rafael Foster <rafaelfoster@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ulas Kalayci
2026-05-06 07:00:07 +02:00
parent 56b2fd471d
commit b545d83f64
7 changed files with 101 additions and 172 deletions
+1 -1
View File
@@ -268,7 +268,7 @@ async function runSync() {
// Server starten
// --------------------------------------------------------
app.listen(PORT, () => {
logOikos.info(`Server running on port ${PORT}`);
logOikos.info(`Server running on port ${PORT} | Version ${APP_VERSION}`);
logOikos.info(`Environment: ${process.env.NODE_ENV || 'development'}`);
// Erster Sync nach 10 Sekunden (warten bis DB vollständig initialisiert)
+10 -2
View File
@@ -13,7 +13,7 @@ const MAX_RRULE = 300;
// Regex-Muster
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
const TIME_RE = /^\d{2}:\d{2}$/;
const DATETIME_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?Z?)?$/;
const DATETIME_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
const COLOR_RE = /^#[0-9A-Fa-f]{6}$/;
const MONTH_RE = /^\d{4}-\d{2}$/;
const RRULE_RE = /^(FREQ=(DAILY|WEEKLY|MONTHLY)(;INTERVAL=\d{1,2})?(;BYDAY=[A-Z,]{2,}(,[A-Z]{2})*)?(;UNTIL=\d{8}(T\d{6}Z)?)?)?$/;
@@ -120,7 +120,15 @@ function datetime(val, field, required = false) {
}
if (!DATETIME_RE.test(String(val)))
return { value: null, error: `${field} must be in YYYY-MM-DD or YYYY-MM-DDTHH:MM format.` };
return { value: String(val), error: null };
const raw = String(val).trim();
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) return { value: raw, error: null };
const match = raw.match(
/^(\d{4}-\d{2}-\d{2})[T ](\d{2}):(\d{2})(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?$/
);
if (!match) {
return { value: null, error: `${field} must be in YYYY-MM-DD or YYYY-MM-DDTHH:MM format.` };
}
return { value: `${match[1]}T${match[2]}:${match[3]}`, error: null };
}
/**