mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
fix(calendar): align week-view event times with local display time
Use local/display-time helpers for week-view event placement, editing, drag, and resize so timezone-aware events line up with what the user sees.
This commit is contained in:
+57
-26
@@ -1141,13 +1141,13 @@ function _wkEventTopHeight(ev, dayStr) {
|
|||||||
// Date math if the string isn't shaped as expected.
|
// Date math if the string isn't shaped as expected.
|
||||||
const _toMin = (iso, fallbackDate) => {
|
const _toMin = (iso, fallbackDate) => {
|
||||||
if (!iso) return null;
|
if (!iso) return null;
|
||||||
const m = iso.match(/T(\d{2}):(\d{2})/);
|
const mins = _timeToMin(iso);
|
||||||
if (m) {
|
if (mins !== null && iso.includes('T')) {
|
||||||
// If the event spans into a previous/next day, clamp to today's bounds.
|
// If the event spans into a previous/next day, clamp to today's bounds.
|
||||||
const evDate = iso.slice(0, 10);
|
const evDate = _localDateOf(iso);
|
||||||
if (evDate < fallbackDate) return 0; // event started before today
|
if (evDate < fallbackDate) return 0; // event started before today
|
||||||
if (evDate > fallbackDate) return 24 * 60; // event ends after today
|
if (evDate > fallbackDate) return 24 * 60; // event ends after today
|
||||||
return parseInt(m[1], 10) * 60 + parseInt(m[2], 10);
|
return mins;
|
||||||
}
|
}
|
||||||
// All-day or date-only — treat as start of day.
|
// All-day or date-only — treat as start of day.
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1286,12 +1286,17 @@ async function _renderWeek() {
|
|||||||
if (!ev) return;
|
if (!ev) return;
|
||||||
const cols = Array.from(body.querySelectorAll('.cal-wk-grid'));
|
const cols = Array.from(body.querySelectorAll('.cal-wk-grid'));
|
||||||
if (!cols.length) return;
|
if (!cols.length) return;
|
||||||
// Original timing
|
// Local/display timing
|
||||||
const m1 = (ev.dtstart || '').match(/T(\d{2}):(\d{2})/);
|
const startMin0 = _timeToMin(ev.dtstart) ?? 0;
|
||||||
const m2 = (ev.dtend || '').match(/T(\d{2}):(\d{2})/);
|
const endMin0 = _timeToMin(ev.dtend) ?? startMin0 + 60;
|
||||||
const startMin0 = m1 ? parseInt(m1[1], 10) * 60 + parseInt(m1[2], 10) : 0;
|
|
||||||
const endMin0 = m2 ? parseInt(m2[1], 10) * 60 + parseInt(m2[2], 10) : startMin0 + 60;
|
let durationMin = endMin0 - startMin0;
|
||||||
const durationMin = Math.max(15, endMin0 - startMin0);
|
const startDs = _localDateOf(ev.dtstart);
|
||||||
|
const endDs = ev.dtend ? _localDateOf(ev.dtend) : startDs;
|
||||||
|
if (endDs > startDs && endMin0 <= startMin0) {
|
||||||
|
durationMin += 24 * 60;
|
||||||
|
}
|
||||||
|
durationMin = Math.max(15, durationMin);
|
||||||
|
|
||||||
// Where did the cursor grab the block? (offset from block-top in px)
|
// Where did the cursor grab the block? (offset from block-top in px)
|
||||||
const blockRect = block.getBoundingClientRect();
|
const blockRect = block.getBoundingClientRect();
|
||||||
@@ -1365,7 +1370,7 @@ async function _renderWeek() {
|
|||||||
// a plain click (no movement) must still open the event.
|
// a plain click (no movement) must still open the event.
|
||||||
if (moved) block.dataset.justResized = '1';
|
if (moved) block.dataset.justResized = '1';
|
||||||
// Decide whether anything actually moved.
|
// Decide whether anything actually moved.
|
||||||
const oldDs = (ev.dtstart || '').slice(0, 10);
|
const oldDs = _localDateOf(ev.dtstart);
|
||||||
if (!nextDs) return;
|
if (!nextDs) return;
|
||||||
if (nextDs === oldDs && nextStartMin === startMin0) return;
|
if (nextDs === oldDs && nextStartMin === startMin0) return;
|
||||||
// Snapshot the original times so we can offer an Undo.
|
// Snapshot the original times so we can offer an Undo.
|
||||||
@@ -1374,11 +1379,10 @@ async function _renderWeek() {
|
|||||||
const newEndMin = nextStartMin + durationMin;
|
const newEndMin = nextStartMin + durationMin;
|
||||||
const hh = String(Math.floor(nextStartMin / 60)).padStart(2, '0');
|
const hh = String(Math.floor(nextStartMin / 60)).padStart(2, '0');
|
||||||
const mm = String(nextStartMin % 60).padStart(2, '0');
|
const mm = String(nextStartMin % 60).padStart(2, '0');
|
||||||
const hh2 = String(Math.floor(newEndMin / 60)).padStart(2, '0');
|
const newDtstartDate = new Date(`${nextDs}T${hh}:${mm}:00`);
|
||||||
const mm2 = String((newEndMin) % 60).padStart(2, '0');
|
const _tz = _tzOffsetForDate(newDtstartDate);
|
||||||
const _tz = _tzOffset();
|
|
||||||
const newDtstart = `${nextDs}T${hh}:${mm}:00${_tz}`;
|
const newDtstart = `${nextDs}T${hh}:${mm}:00${_tz}`;
|
||||||
const newDtend = `${nextDs}T${hh2}:${mm2}:00${_tz}`;
|
const newDtend = _addMinutesToLocalIso(newDtstart, durationMin);
|
||||||
try {
|
try {
|
||||||
await _updateEvent(uid, { dtstart: newDtstart, dtend: newDtend });
|
await _updateEvent(uid, { dtstart: newDtstart, dtend: newDtend });
|
||||||
_render();
|
_render();
|
||||||
@@ -1410,10 +1414,7 @@ async function _renderWeek() {
|
|||||||
const uid = block.dataset.uid;
|
const uid = block.dataset.uid;
|
||||||
const ev = _events.find(x => x.uid === uid);
|
const ev = _events.find(x => x.uid === uid);
|
||||||
if (!ev || !grid || !ds) return;
|
if (!ev || !grid || !ds) return;
|
||||||
const startMin = (() => {
|
const startMin = _timeToMin(ev.dtstart) ?? 0;
|
||||||
const m = (ev.dtstart || '').match(/T(\d{2}):(\d{2})/);
|
|
||||||
return m ? parseInt(m[1], 10) * 60 + parseInt(m[2], 10) : 0;
|
|
||||||
})();
|
|
||||||
const initialTop = parseFloat(block.style.top || '0');
|
const initialTop = parseFloat(block.style.top || '0');
|
||||||
const gridRect = grid.getBoundingClientRect();
|
const gridRect = grid.getBoundingClientRect();
|
||||||
let newEndMin = startMin;
|
let newEndMin = startMin;
|
||||||
@@ -1438,9 +1439,8 @@ async function _renderWeek() {
|
|||||||
if (resized) block.dataset.justResized = '1';
|
if (resized) block.dataset.justResized = '1';
|
||||||
if (newEndMin === startMin) return;
|
if (newEndMin === startMin) return;
|
||||||
const prevDtend = ev.dtend;
|
const prevDtend = ev.dtend;
|
||||||
const hh = String(Math.floor(newEndMin / 60)).padStart(2, '0');
|
const durationMin = newEndMin - startMin;
|
||||||
const mm = String(newEndMin % 60).padStart(2, '0');
|
const newDtend = _addMinutesToLocalIso(ev.dtstart, durationMin);
|
||||||
const newDtend = `${ds}T${hh}:${mm}:00${_tzOffset()}`;
|
|
||||||
try {
|
try {
|
||||||
await _updateEvent(uid, { dtend: newDtend });
|
await _updateEvent(uid, { dtend: newDtend });
|
||||||
_render();
|
_render();
|
||||||
@@ -1966,10 +1966,10 @@ function _wireAll(body) {
|
|||||||
const ad = document.getElementById('cal-f-allday');
|
const ad = document.getElementById('cal-f-allday');
|
||||||
if (ad && !ad.checked) { ad.checked = true; ad.dispatchEvent(new Event('change')); }
|
if (ad && !ad.checked) { ad.checked = true; ad.dispatchEvent(new Event('change')); }
|
||||||
} else {
|
} else {
|
||||||
const t1 = (ev.dtstart || '').match(/T(\d{2}:\d{2})/);
|
const t1 = _fmtTime(ev.dtstart);
|
||||||
const t2 = (ev.dtend || '').match(/T(\d{2}:\d{2})/);
|
const t2 = _fmtTime(ev.dtend);
|
||||||
if (t1) set('cal-f-start', t1[1]);
|
if (t1) set('cal-f-start', t1);
|
||||||
if (t2) set('cal-f-end', t2[1]);
|
if (t2) set('cal-f-end', t2);
|
||||||
document.getElementById('cal-f-start')?.dispatchEvent(new Event('input'));
|
document.getElementById('cal-f-start')?.dispatchEvent(new Event('input'));
|
||||||
}
|
}
|
||||||
// Make sure the details panel is open so the user can verify time.
|
// Make sure the details panel is open so the user can verify time.
|
||||||
@@ -3215,6 +3215,37 @@ function _fmtTime(s) {
|
|||||||
}
|
}
|
||||||
return s.slice(11, 16);
|
return s.slice(11, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _timeToMin(iso) {
|
||||||
|
const hm = _fmtTime(iso);
|
||||||
|
if (!hm) return null;
|
||||||
|
const m = hm.match(/^(\d{1,2}):(\d{2})$/);
|
||||||
|
if (!m) return null;
|
||||||
|
const h = parseInt(m[1], 10);
|
||||||
|
const min = parseInt(m[2], 10);
|
||||||
|
if (h < 0 || h > 23 || min < 0 || min > 59) return null;
|
||||||
|
return h * 60 + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _tzOffsetForDate(d) {
|
||||||
|
const off = -d.getTimezoneOffset();
|
||||||
|
const sign = off >= 0 ? '+' : '-';
|
||||||
|
const abs = Math.abs(off);
|
||||||
|
const hh = String(Math.floor(abs / 60)).padStart(2, '0');
|
||||||
|
const mm = String(abs % 60).padStart(2, '0');
|
||||||
|
return `${sign}${hh}:${mm}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _addMinutesToLocalIso(baseIso, addMinutes) {
|
||||||
|
const d = new Date(new Date(baseIso).getTime() + addMinutes * 60000);
|
||||||
|
const y = d.getFullYear();
|
||||||
|
const mo = String(d.getMonth() + 1).padStart(2, '0');
|
||||||
|
const da = String(d.getDate()).padStart(2, '0');
|
||||||
|
const h = String(d.getHours()).padStart(2, '0');
|
||||||
|
const m = String(d.getMinutes()).padStart(2, '0');
|
||||||
|
return `${y}-${mo}-${da}T${h}:${m}:00${_tzOffsetForDate(d)}`;
|
||||||
|
}
|
||||||
|
|
||||||
function _e(s) { return uiModule.esc ? uiModule.esc(s || '') : (s || '').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
|
function _e(s) { return uiModule.esc ? uiModule.esc(s || '') : (s || '').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
|
||||||
|
|
||||||
// Linkify a location string: URLs become clickable, plain addresses get a Maps link.
|
// Linkify a location string: URLs become clickable, plain addresses get a Maps link.
|
||||||
|
|||||||
Reference in New Issue
Block a user