fix(mobile): Monatstitel sichtbar, KW-Bubble unten, Termine mit Text, Long-Press, Settings-Hamburger
- View-Switcher auf Mobile in Popup-Menü ausgelagert (neuer Icon-Button
rechts in der Topbar). Dadurch wird in der Topbar Platz frei für
prev/next + Monatstitel ("Mai 2026" usw.).
- Topbar-Settings-Icon auf Mobile ausgeblendet, dafür neuer
"Einstellungen"-Eintrag im User-Dropdown. "Heute" wandert ins
View-Popup.
- KW-Bubble: von oben-links nach unten-links verschoben — überlappt
jetzt nicht mehr die Tagesnummer.
- Termine in der Monatsansicht zeigen wieder ihren Text (kleinere
14px-Höhe, 9px Schrift) statt nur farbiger Punkte.
- Long-Press auf einen Tag öffnet das Kontextmenü "Termin erstellen"
(synthetisches contextmenu-Event nach 500 ms ohne Bewegung). Der
nachfolgende synthetische Click wird unterdrückt.
- Settings-Modal: Sidebar (Darstellung/Konten/Benutzerverwaltung) auf
Mobile als slide-in Overlay mit Hamburger-Toggle. Auf Desktop bleibt
sie immer sichtbar.
- Version v4 → v5 (auch SW-Cache)
This commit is contained in:
@@ -1218,6 +1218,12 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
/* Backdrop element exists in DOM but is hidden by default on desktop */
|
/* Backdrop element exists in DOM but is hidden by default on desktop */
|
||||||
.sidebar-backdrop { display: none; }
|
.sidebar-backdrop { display: none; }
|
||||||
|
|
||||||
|
/* Mobile-only UI elements: hidden on desktop ───────────── */
|
||||||
|
.view-mobile-wrapper { position: relative; display: none; }
|
||||||
|
.settings-nav-toggle { display: none; }
|
||||||
|
.settings-nav-backdrop { display: none; }
|
||||||
|
.dropdown-item-mobile-only { display: none; }
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
html, body { overflow-x: hidden; max-width: 100vw; }
|
html, body { overflow-x: hidden; max-width: 100vw; }
|
||||||
|
|
||||||
@@ -1291,16 +1297,14 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
.btn { min-height: 44px; }
|
.btn { min-height: 44px; }
|
||||||
.view-switcher .view-btn { min-height: 40px; }
|
.view-switcher .view-btn { min-height: 40px; }
|
||||||
|
|
||||||
/* ── Month view: dots instead of full event titles ───────── */
|
/* ── Month view: events keep text, just smaller ──────────── */
|
||||||
.month-span-event {
|
.month-span-event {
|
||||||
height: 6px !important;
|
height: 14px !important;
|
||||||
line-height: 0 !important;
|
line-height: 14px !important;
|
||||||
padding: 0 !important;
|
padding: 0 4px !important;
|
||||||
border-radius: 3px !important;
|
font-size: 9px !important;
|
||||||
font-size: 0 !important;
|
font-weight: 500;
|
||||||
text-overflow: clip !important;
|
|
||||||
}
|
}
|
||||||
.month-events-overlay { gap: 1px; }
|
|
||||||
.month-more {
|
.month-more {
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
@@ -1381,18 +1385,20 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
}
|
}
|
||||||
.month-kw-cell {
|
.month-kw-cell {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 3px; top: 3px;
|
/* Bottom-left: away from the day-number (top-left) */
|
||||||
|
left: 3px; bottom: 3px;
|
||||||
|
top: auto; right: auto;
|
||||||
width: auto; height: auto;
|
width: auto; height: auto;
|
||||||
bottom: auto;
|
padding: 1px 6px;
|
||||||
padding: 1px 7px;
|
|
||||||
background: var(--bg-active);
|
background: var(--bg-active);
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
font-size: 10px; font-weight: 600;
|
font-size: 9px; font-weight: 600;
|
||||||
color: var(--text-2);
|
color: var(--text-2);
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Status-bar safe area inside full-screen modals (PWA) ── */
|
/* ── Status-bar safe area inside full-screen modals (PWA) ── */
|
||||||
@@ -1404,6 +1410,64 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
.settings-page-body {
|
.settings-page-body {
|
||||||
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
|
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Topbar: hide desktop view-switcher + settings, show
|
||||||
|
mobile view-toggle and let the title breathe ──────────── */
|
||||||
|
.topbar .view-switcher { display: none; }
|
||||||
|
.topbar #btn-settings { display: none; }
|
||||||
|
.view-mobile-wrapper { display: inline-block; }
|
||||||
|
.dropdown-item-mobile-only { display: flex; }
|
||||||
|
|
||||||
|
/* Hide "Heute" button on desktop topbar (it's in the view popup now) */
|
||||||
|
.topbar #btn-today { display: none; }
|
||||||
|
|
||||||
|
/* The title is the most important info — let it grow */
|
||||||
|
.topbar-center { flex: 1; min-width: 0; }
|
||||||
|
.topbar-center .view-title {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding-left: 4px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.topbar-left { gap: 0; }
|
||||||
|
.topbar-right { gap: 0; }
|
||||||
|
|
||||||
|
/* ── Settings modal: nav becomes a slide-in overlay ──────── */
|
||||||
|
.settings-nav-toggle { display: inline-flex !important; }
|
||||||
|
.settings-page-body { position: relative; }
|
||||||
|
.settings-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; bottom: 0;
|
||||||
|
width: min(75vw, 280px);
|
||||||
|
z-index: 50;
|
||||||
|
background: var(--bg-app);
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform .25s ease;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
.settings-page-card.nav-open .settings-nav {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
.settings-nav-backdrop {
|
||||||
|
display: block;
|
||||||
|
position: absolute; inset: 0;
|
||||||
|
background: rgba(0,0,0,.5);
|
||||||
|
z-index: 40;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity .2s ease;
|
||||||
|
}
|
||||||
|
.settings-page-card.nav-open .settings-nav-backdrop {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.settings-panels { padding: 16px; }
|
||||||
|
|
||||||
|
/* Modal headers: tighter on mobile */
|
||||||
|
.modal-header { padding: 12px 16px; }
|
||||||
|
.modal-body { padding: 16px; }
|
||||||
|
.modal-footer { padding: 12px 16px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iOS notch / home-indicator safe areas (PWA standalone) */
|
/* iOS notch / home-indicator safe areas (PWA standalone) */
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
<!-- APP_VERSION: update here + version.js on every release -->
|
<!-- APP_VERSION: update here + version.js on every release -->
|
||||||
<title>Calendarr v4</title>
|
<title>Calendarr v5</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<meta name="theme-color" content="#4285f4" />
|
<meta name="theme-color" content="#4285f4" />
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
<button type="submit" class="btn btn-primary btn-full">Anmelden</button>
|
<button type="submit" class="btn btn-primary btn-full">Anmelden</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button class="impressum-link" onclick="openImpressum()">© 2026 Scarriffleservices · v4</button>
|
<button class="impressum-link" onclick="openImpressum()">© 2026 Scarriffleservices · v5</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ─── MAIN APP ──────────────────────────────────────────── -->
|
<!-- ─── MAIN APP ──────────────────────────────────────────── -->
|
||||||
@@ -112,6 +112,19 @@
|
|||||||
<button class="view-btn" data-view="day" data-i18n="view_day">Tag</button>
|
<button class="view-btn" data-view="day" data-i18n="view_day">Tag</button>
|
||||||
<button class="view-btn" data-view="agenda" data-i18n="view_agenda">Termine</button>
|
<button class="view-btn" data-view="agenda" data-i18n="view_agenda">Termine</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="view-mobile-wrapper">
|
||||||
|
<button class="icon-btn" id="btn-view-mobile" title="Ansicht wechseln" aria-label="Ansicht wechseln">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>
|
||||||
|
</button>
|
||||||
|
<div id="view-mobile-dropdown" class="user-dropdown hidden" style="right:0;top:50px;min-width:180px">
|
||||||
|
<button class="dropdown-item" data-mobile-view="quarter" data-i18n="view_quarter">Quartal</button>
|
||||||
|
<button class="dropdown-item" data-mobile-view="month" data-i18n="view_month">Monat</button>
|
||||||
|
<button class="dropdown-item" data-mobile-view="week" data-i18n="view_week">Woche</button>
|
||||||
|
<button class="dropdown-item" data-mobile-view="day" data-i18n="view_day">Tag</button>
|
||||||
|
<button class="dropdown-item" data-mobile-view="agenda" data-i18n="view_agenda">Termine</button>
|
||||||
|
<button class="dropdown-item" id="btn-today-mobile" style="border-top:1px solid var(--border)" data-i18n="btn_today">Heute</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button class="icon-btn" id="btn-settings" data-i18n-title="settings_title" title="Einstellungen">
|
<button class="icon-btn" id="btn-settings" data-i18n-title="settings_title" title="Einstellungen">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -123,6 +136,10 @@
|
|||||||
<svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
|
||||||
<span data-i18n="btn_profile">Profil</span>
|
<span data-i18n="btn_profile">Profil</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="dropdown-item dropdown-item-mobile-only" id="btn-settings-from-user">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>
|
||||||
|
<span data-i18n="settings_title">Einstellungen</span>
|
||||||
|
</button>
|
||||||
<button class="dropdown-item" id="btn-logout">
|
<button class="dropdown-item" id="btn-logout">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v24c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v24c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>
|
||||||
<span data-i18n="btn_logout">Abmelden</span>
|
<span data-i18n="btn_logout">Abmelden</span>
|
||||||
@@ -179,7 +196,7 @@
|
|||||||
<div id="cal-list-items"></div>
|
<div id="cal-list-items"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="sidebar-copyright" onclick="openImpressum()">© 2026 Scarriffleservices · v4</button>
|
<button class="sidebar-copyright" onclick="openImpressum()">© 2026 Scarriffleservices · v5</button>
|
||||||
</aside>
|
</aside>
|
||||||
<div id="sidebar-backdrop" class="sidebar-backdrop"></div>
|
<div id="sidebar-backdrop" class="sidebar-backdrop"></div>
|
||||||
|
|
||||||
@@ -534,10 +551,14 @@
|
|||||||
<button class="icon-btn modal-close" data-modal="modal-settings" style="margin-right:8px">
|
<button class="icon-btn modal-close" data-modal="modal-settings" style="margin-right:8px">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="icon-btn settings-nav-toggle" id="settings-nav-toggle" title="Menü" aria-label="Menü">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>
|
||||||
|
</button>
|
||||||
<h3 data-i18n="settings_title">Einstellungen</h3>
|
<h3 data-i18n="settings_title">Einstellungen</h3>
|
||||||
<button class="btn btn-primary" id="settings-save" style="margin-left:auto" data-i18n="save">Speichern</button>
|
<button class="btn btn-primary" id="settings-save" style="margin-left:auto" data-i18n="save">Speichern</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-page-body">
|
<div class="settings-page-body">
|
||||||
|
<div class="settings-nav-backdrop" id="settings-nav-backdrop"></div>
|
||||||
<nav class="settings-nav">
|
<nav class="settings-nav">
|
||||||
<button class="settings-nav-btn active" data-panel="general" data-i18n="settings_nav_appearance">Darstellung</button>
|
<button class="settings-nav-btn active" data-panel="general" data-i18n="settings_nav_appearance">Darstellung</button>
|
||||||
<button class="settings-nav-btn" data-panel="accounts" data-i18n="settings_nav_accounts">Konten</button>
|
<button class="settings-nav-btn" data-panel="accounts" data-i18n="settings_nav_accounts">Konten</button>
|
||||||
@@ -841,7 +862,7 @@
|
|||||||
<a href="mailto:scarriffleservices@gmail.com">scarriffleservices@gmail.com</a></p>
|
<a href="mailto:scarriffleservices@gmail.com">scarriffleservices@gmail.com</a></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer" style="justify-content:space-between;align-items:center">
|
<div class="modal-footer" style="justify-content:space-between;align-items:center">
|
||||||
<span style="font-size:12px;color:var(--text-3)">Calendarr v4</span>
|
<span style="font-size:12px;color:var(--text-3)">Calendarr v5</span>
|
||||||
<button class="btn btn-ghost" onclick="closeImpressum()">Schliessen</button>
|
<button class="btn btn-ghost" onclick="closeImpressum()">Schliessen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -756,21 +756,57 @@ function renderCalendarList() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Swipe navigation (mobile) ─────────────────────────────
|
// ── Swipe navigation + long-press → context menu (mobile) ──
|
||||||
function bindSwipeNavigation() {
|
function bindSwipeNavigation() {
|
||||||
const container = document.getElementById('view-container');
|
const container = document.getElementById('view-container');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
let startX = 0, startY = 0, startT = 0, active = false;
|
let startX = 0, startY = 0, startT = 0, active = false;
|
||||||
|
let lpTimer = null, lpTarget = null, lpFired = false;
|
||||||
|
|
||||||
container.addEventListener('touchstart', e => {
|
container.addEventListener('touchstart', e => {
|
||||||
if (e.touches.length !== 1) { active = false; return; }
|
if (e.touches.length !== 1) { active = false; return; }
|
||||||
startX = e.touches[0].clientX;
|
startX = e.touches[0].clientX;
|
||||||
startY = e.touches[0].clientY;
|
startY = e.touches[0].clientY;
|
||||||
startT = Date.now();
|
startT = Date.now();
|
||||||
active = true;
|
active = true;
|
||||||
|
lpFired = false;
|
||||||
|
|
||||||
|
// Long-press → context menu (only on day cells, not on events)
|
||||||
|
lpTarget = e.target.closest('.month-col, .week-day-col');
|
||||||
|
if (lpTarget && !e.target.closest('.month-span-event, .week-event')) {
|
||||||
|
lpTimer = setTimeout(() => {
|
||||||
|
const t = e.touches[0];
|
||||||
|
const ev = new MouseEvent('contextmenu', {
|
||||||
|
bubbles: true, cancelable: true,
|
||||||
|
clientX: t.clientX, clientY: t.clientY,
|
||||||
|
});
|
||||||
|
lpTarget.dispatchEvent(ev);
|
||||||
|
lpFired = true;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
|
container.addEventListener('touchmove', e => {
|
||||||
|
if (!active) return;
|
||||||
|
const t = e.touches[0];
|
||||||
|
if (Math.abs(t.clientX - startX) > 8 || Math.abs(t.clientY - startY) > 8) {
|
||||||
|
if (lpTimer) { clearTimeout(lpTimer); lpTimer = null; }
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
container.addEventListener('touchend', e => {
|
container.addEventListener('touchend', e => {
|
||||||
|
if (lpTimer) { clearTimeout(lpTimer); lpTimer = null; }
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
active = false;
|
active = false;
|
||||||
|
|
||||||
|
// Suppress the click that follows a long-press
|
||||||
|
if (lpFired) {
|
||||||
|
const blocker = ev => { ev.stopPropagation(); ev.preventDefault(); };
|
||||||
|
document.addEventListener('click', blocker, { capture: true, once: true });
|
||||||
|
lpFired = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const t = e.changedTouches[0];
|
const t = e.changedTouches[0];
|
||||||
const dx = t.clientX - startX;
|
const dx = t.clientX - startX;
|
||||||
const dy = t.clientY - startY;
|
const dy = t.clientY - startY;
|
||||||
@@ -781,6 +817,11 @@ function bindSwipeNavigation() {
|
|||||||
fetchAndRender();
|
fetchAndRender();
|
||||||
}
|
}
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
|
container.addEventListener('touchcancel', () => {
|
||||||
|
if (lpTimer) { clearTimeout(lpTimer); lpTimer = null; }
|
||||||
|
active = false;
|
||||||
|
}, { passive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Navigation ────────────────────────────────────────────
|
// ── Navigation ────────────────────────────────────────────
|
||||||
@@ -825,6 +866,59 @@ function bindTopbar() {
|
|||||||
document.getElementById('btn-settings').onclick = openSettingsModal;
|
document.getElementById('btn-settings').onclick = openSettingsModal;
|
||||||
document.getElementById('btn-create-event').onclick = () => openNewEventModal(state.selectedDate || state.currentDate);
|
document.getElementById('btn-create-event').onclick = () => openNewEventModal(state.selectedDate || state.currentDate);
|
||||||
|
|
||||||
|
// Mobile view-toggle popup
|
||||||
|
const viewMobileBtn = document.getElementById('btn-view-mobile');
|
||||||
|
const viewMobileDropdown = document.getElementById('view-mobile-dropdown');
|
||||||
|
if (viewMobileBtn && viewMobileDropdown) {
|
||||||
|
viewMobileBtn.onclick = e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
viewMobileDropdown.classList.toggle('hidden');
|
||||||
|
};
|
||||||
|
document.addEventListener('click', e => {
|
||||||
|
if (!viewMobileDropdown.contains(e.target) && !viewMobileBtn.contains(e.target)) {
|
||||||
|
viewMobileDropdown.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewMobileDropdown.querySelectorAll('[data-mobile-view]').forEach(btn => {
|
||||||
|
btn.onclick = () => {
|
||||||
|
state.currentView = btn.dataset.mobileView;
|
||||||
|
updateViewButtons();
|
||||||
|
fetchAndRender();
|
||||||
|
viewMobileDropdown.classList.add('hidden');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const todayMobile = document.getElementById('btn-today-mobile');
|
||||||
|
if (todayMobile) todayMobile.onclick = () => {
|
||||||
|
state.currentDate = new Date();
|
||||||
|
fetchAndRender();
|
||||||
|
viewMobileDropdown.classList.add('hidden');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings entry inside the user dropdown (mobile)
|
||||||
|
const settingsFromUser = document.getElementById('btn-settings-from-user');
|
||||||
|
if (settingsFromUser) settingsFromUser.onclick = () => {
|
||||||
|
document.getElementById('user-dropdown').classList.add('hidden');
|
||||||
|
openSettingsModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Settings nav hamburger (only does something on mobile via CSS)
|
||||||
|
const settingsNavToggle = document.getElementById('settings-nav-toggle');
|
||||||
|
const settingsCard = document.querySelector('#modal-settings .settings-page-card');
|
||||||
|
const settingsNavBackdrop = document.getElementById('settings-nav-backdrop');
|
||||||
|
if (settingsNavToggle && settingsCard) {
|
||||||
|
settingsNavToggle.onclick = () => settingsCard.classList.toggle('nav-open');
|
||||||
|
}
|
||||||
|
if (settingsNavBackdrop && settingsCard) {
|
||||||
|
settingsNavBackdrop.onclick = () => settingsCard.classList.remove('nav-open');
|
||||||
|
}
|
||||||
|
// After picking a section in the nav, close the overlay (mobile UX)
|
||||||
|
document.querySelectorAll('.settings-nav-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
if (settingsCard) settingsCard.classList.remove('nav-open');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Mouse wheel / trackpad scroll navigation – only for month & quarter
|
// Mouse wheel / trackpad scroll navigation – only for month & quarter
|
||||||
let _wheelLast = 0;
|
let _wheelLast = 0;
|
||||||
document.getElementById('view-container').addEventListener('wheel', e => {
|
document.getElementById('view-container').addEventListener('wheel', e => {
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Increment APP_VERSION with every code change
|
// Increment APP_VERSION with every code change
|
||||||
export const APP_VERSION = 'v4';
|
export const APP_VERSION = 'v5';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Calendarr Service Worker
|
// Calendarr Service Worker
|
||||||
// Cache-first for static assets, network-first for /api/* (graceful offline)
|
// Cache-first for static assets, network-first for /api/* (graceful offline)
|
||||||
|
|
||||||
const CACHE_VERSION = 'calendarr-v4';
|
const CACHE_VERSION = 'calendarr-v5';
|
||||||
const STATIC_ASSETS = [
|
const STATIC_ASSETS = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/index.html',
|
||||||
|
|||||||
Reference in New Issue
Block a user