calDAV v5
Deploy HouseHub / deploy (push) Successful in 2s

This commit is contained in:
Cedric
2026-05-13 18:26:09 +02:00
parent d9d9587ded
commit a2e0061dde
4 changed files with 412 additions and 4 deletions
+100 -2
View File
@@ -24,6 +24,102 @@
line-height: 1.45;
}
.ios-cal-prefs-details {
margin-top: 14px;
border: 1px solid var(--pf-border, #e2e8f0);
border-radius: 10px;
padding: 10px 14px;
background: var(--pf-bg-page, #fff);
}
.ios-cal-prefs-summary {
cursor: pointer;
font-weight: 600;
font-size: 0.95rem;
}
.ios-cal-prefs-help {
margin: 10px 0 12px;
font-size: 0.85rem;
color: var(--pf-text-muted, #64748b);
line-height: 1.45;
}
.ios-cal-prefs-rows {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 12px;
}
.ios-cal-pref-row {
display: grid;
grid-template-columns: auto 1fr auto auto;
align-items: center;
gap: 10px 12px;
padding: 8px 10px;
border: 1px solid var(--pf-border, #e2e8f0);
border-radius: 8px;
background: var(--pf-bg-lighter, #f8fafc);
}
.ios-cal-pref-row input[type="checkbox"] {
width: 18px;
height: 18px;
}
.ios-cal-pref-label {
font-size: 0.88rem;
min-width: 0;
word-break: break-word;
}
.ios-cal-pref-color {
width: 44px;
height: 36px;
padding: 2px;
border: 1px solid var(--pf-border, #e2e8f0);
border-radius: 8px;
cursor: pointer;
background: var(--pf-bg-page, #fff);
}
.ios-cal-pref-reset {
border: none;
background: transparent;
color: var(--pf-text-muted, #64748b);
font-size: 0.78rem;
text-decoration: underline;
cursor: pointer;
padding: 4px;
font-family: inherit;
white-space: nowrap;
}
.ios-cal-pref-reset:hover {
color: var(--pf-text-main, #0f172a);
}
.ios-cal-prefs-msg {
min-height: 1.25em;
font-size: 0.85rem;
margin: 0 0 10px;
}
@media (max-width: 640px) {
.ios-cal-pref-row {
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
}
.ios-cal-pref-color {
grid-column: 2;
}
.ios-cal-pref-reset {
grid-column: 2;
justify-self: start;
}
}
.ios-form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -349,8 +445,10 @@
box-shadow: inset 3px 0 0 rgba(255, 255, 255, 0.35);
}
.ios-event-block:hover {
filter: brightness(1.08);
.ios-event-block--custom {
border: none !important;
color: #fff !important;
box-shadow: inset 3px 0 0 rgba(255, 255, 255, 0.35) !important;
}
.ios-event-block-title {
+129 -1
View File
@@ -26,6 +26,8 @@ const PALETTE = [
"ios-cal-c5",
];
const SUGGEST_HEX = ["#22c55e", "#3b82f6", "#8b5cf6", "#eab308", "#14b8a6", "#f43f5e"];
function escHtml(s) {
const d = document.createElement("div");
d.textContent = s ?? "";
@@ -50,6 +52,21 @@ function parseEvtDate(iso) {
return Number.isNaN(d.getTime()) ? null : d;
}
function darkenHex(hex, amount) {
const n = parseInt(hex.slice(1), 16);
const r = Math.max(0, (n >> 16) - amount);
const g = Math.max(0, ((n >> 8) & 0xff) - amount);
const b = Math.max(0, (n & 0xff) - amount);
return `#${[r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("")}`;
}
function suggestedColorForKey(key) {
const k = String(key || "");
let h = 0;
for (let i = 0; i < k.length; i++) h = (h * 31 + k.charCodeAt(i)) >>> 0;
return SUGGEST_HEX[h % SUGGEST_HEX.length];
}
function colorClassForEvent(evt) {
const key = String(evt.calendar_source_url || evt.external_uid || evt.id || "");
let h = 0;
@@ -57,6 +74,22 @@ function colorClassForEvent(evt) {
return PALETTE[h % PALETTE.length];
}
/** Applique couleur perso (API) ou palette par défaut */
function styleEventBlock(el, evt) {
el.className = "ios-event-block";
el.style.background = "";
el.style.boxShadow = "";
const hex = evt.display_color;
if (hex && /^#[0-9A-Fa-f]{6}$/i.test(hex)) {
const c = hex.startsWith("#") ? hex : `#${hex.slice(-6)}`;
el.classList.add("ios-event-block--custom");
el.style.background = `linear-gradient(135deg, ${darkenHex(c, 40)} 0%, ${c} 100%)`;
el.style.boxShadow = "inset 3px 0 0 rgba(255,255,255,0.35)";
} else {
el.classList.add(colorClassForEvent(evt));
}
}
function mondayOf(date) {
const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const dow = d.getDay();
@@ -243,7 +276,7 @@ function buildDayColumn(dayMidnight) {
const left = (100 * seg._lane) / seg._laneCount;
const blk = document.createElement("button");
blk.type = "button";
blk.className = `ios-event-block ${colorClassForEvent(seg.evt)}`;
styleEventBlock(blk, seg.evt);
blk.style.top = `${top}px`;
blk.style.height = `${h}px`;
blk.style.left = `calc(${left}% + 2px)`;
@@ -442,6 +475,9 @@ async function loadEvents() {
events.forEach((evt) => {
const card = document.createElement("div");
card.className = "ios-event-card";
if (evt.display_color && /^#[0-9A-Fa-f]{6}$/i.test(evt.display_color)) {
card.style.borderLeft = `4px solid ${evt.display_color}`;
}
card.innerHTML = `
<div class="ios-event-card-head">
<div>
@@ -476,6 +512,94 @@ async function loadSyncStatus() {
iosSyncStatus.textContent = status.message;
}
async function loadCalendarPrefs() {
const wrap = document.getElementById("ios-calendar-prefs-rows");
const msg = document.getElementById("ios-calendar-prefs-msg");
if (!wrap) return;
if (msg) msg.textContent = "";
wrap.innerHTML = "<span class=\"ios-agenda-intro\">Chargement des calendriers…</span>";
try {
const data = await iosApi("calendar_sources");
const rows = data.calendars || [];
wrap.innerHTML = "";
if (!rows.length) {
wrap.innerHTML =
"<span class=\"ios-agenda-intro\">Aucun calendrier détecté. Configure iCloud dans Paramètres puis synchronise.</span>";
return;
}
rows.forEach((row, idx) => {
const id = `ios-cal-cb-${idx}`;
const rowEl = document.createElement("div");
rowEl.className = "ios-cal-pref-row";
rowEl.dataset.urlKey = row.url_key;
const cb = document.createElement("input");
cb.type = "checkbox";
cb.id = id;
cb.checked = row.visible !== false;
const lab = document.createElement("label");
lab.className = "ios-cal-pref-label";
lab.htmlFor = id;
lab.textContent = row.display || row.url_key;
const colInp = document.createElement("input");
colInp.type = "color";
colInp.className = "ios-cal-pref-color";
colInp.value = row.color || suggestedColorForKey(row.url_key);
colInp.title = "Couleur dans lagenda";
const autoBtn = document.createElement("button");
autoBtn.type = "button";
autoBtn.className = "ios-cal-pref-reset";
autoBtn.textContent = "Palette auto";
autoBtn.title = "Couleurs HouseHub par défaut (sans teinte iOS)";
autoBtn.addEventListener("click", () => {
rowEl.dataset.paletteAuto = "1";
colInp.disabled = true;
colInp.style.opacity = "0.35";
});
rowEl.appendChild(cb);
rowEl.appendChild(lab);
rowEl.appendChild(colInp);
rowEl.appendChild(autoBtn);
wrap.appendChild(rowEl);
});
} catch (e) {
wrap.innerHTML = `<span class="ios-agenda-intro">${escHtml(e.message)}</span>`;
}
}
async function saveCalendarPrefs() {
const msg = document.getElementById("ios-calendar-prefs-msg");
const wrap = document.getElementById("ios-calendar-prefs-rows");
if (!wrap) return;
const prefs = {};
wrap.querySelectorAll(".ios-cal-pref-row").forEach((rowEl) => {
const key = rowEl.dataset.urlKey;
if (!key) return;
const vis = rowEl.querySelector('input[type="checkbox"]')?.checked === true;
const colInp = rowEl.querySelector(".ios-cal-pref-color");
let color = null;
if (!rowEl.dataset.paletteAuto && colInp && !colInp.disabled) {
const v = colInp.value;
if (v && /^#[0-9A-Fa-f]{6}$/i.test(v)) {
color = v.startsWith("#") ? v.toLowerCase() : `#${v}`.toLowerCase();
}
}
prefs[key] = { visible: vis, color };
});
try {
await iosApi("calendar_prefs", { method: "POST", body: { prefs } });
if (msg) msg.textContent = "Préférences enregistrées.";
await loadEvents();
await loadCalendarPrefs();
} catch (e) {
if (msg) msg.textContent = e.message;
}
}
iosForm.addEventListener("submit", async (e) => {
e.preventDefault();
const idStr = document.getElementById("ios-event-id").value.trim();
@@ -505,6 +629,7 @@ document.getElementById("ios-sync-btn")?.addEventListener("click", async () => {
const data = await iosApi("sync", { method: "POST", body: {} });
if (iosSyncStatus) iosSyncStatus.textContent = data.message;
await loadEvents();
await loadCalendarPrefs();
} finally {
btn.disabled = false;
}
@@ -558,10 +683,13 @@ document.getElementById("ios-agenda-add")?.addEventListener("click", () => {
document.getElementById("ios-title")?.focus();
});
document.getElementById("ios-calendar-prefs-save")?.addEventListener("click", () => saveCalendarPrefs());
window.addEventListener("DOMContentLoaded", async () => {
try {
iosCursor = new Date();
await loadEvents();
await loadCalendarPrefs();
await loadSyncStatus();
showListSection(false);
startNowTimer();