Skip to content

Commit d93bbdb

Browse files
committed
fix: revised ICS format
1 parent 3077216 commit d93bbdb

4 files changed

Lines changed: 24 additions & 46 deletions

File tree

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
/**
22
* @file icsGenerator.js
3-
* @version 2.5
4-
* @description Générateur ICS complet avec calcul de rotation déterministe et fusion des overrides.
3+
* @version 2.6
4+
* @description Version "Mobile-First" : Suppression BOM, limitation 1 an, et nettoyage RFC 5545.
55
*/
66

77
const ICS_CONFIG = Object.freeze({
8-
MAX_FUTURE_YEARS: 2,
8+
MAX_FUTURE_YEARS: 1, // Réduit à 1 an pour performance mobile
99
PRODUCT_ID: '-//ScripturaUA0//ICS Generator v1.0//FR',
1010
STORAGE_KEY: 'scheduleData',
1111

12-
// Mapping des libellés (aligné sur les versions précédentes)
1312
MAPPING: {
1413
M: { s: 'M', d: 'Poste du matin' },
1514
S: { s: 'S', d: 'Poste du soir' },
@@ -33,57 +32,53 @@ const ICS_CONFIG = Object.freeze({
3332
});
3433

3534
const TimeUtils = {
36-
// Calcul du jour de l'époque pour alignement AOT
3735
toEpochDay: (date) => Math.floor(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / 86400000),
38-
// Formatage conforme RFC 5545
3936
toIcsDay: (date) => date.getFullYear() + String(date.getMonth() + 1).padStart(2, '0') + String(date.getDate()).padStart(2, '0'),
4037
toIcsTimestamp: (date) => date.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'
4138
};
4239

43-
/**
44-
* Construit un bloc VEVENT avec UID déterministe (alignement historique) [cite: 2]
45-
*/
4640
function buildEvent(date, summary, description, timestamp) {
4741
const start = TimeUtils.toIcsDay(date);
4842
const next = new Date(date);
4943
next.setDate(next.getDate() + 1);
5044
const end = TimeUtils.toIcsDay(next);
5145

46+
// Échappement minimaliste pour éviter les caractères de contrôle
47+
const cleanSummary = summary.replace(/[,;]/g, '\\$&');
48+
const cleanDesc = description.replace(/[,;]/g, '\\$&');
49+
5250
return [
5351
'BEGIN:VEVENT',
54-
`UID:${start}@UA0`, // Clé primaire persistante pour éviter les doublons
52+
`UID:${start}@UA0`,
5553
`DTSTAMP:${timestamp}`,
54+
`SEQUENCE:0`,
55+
`STATUS:CONFIRMED`,
56+
`TRANSP:TRANSPARENT`, // Pour ne pas bloquer le calendrier (disponible)
5657
`DTSTART;VALUE=DATE:${start}`,
5758
`DTEND;VALUE=DATE:${end}`,
58-
`SUMMARY:${summary.replace(/[,;]/g, '\\$&')}`,
59-
`DESCRIPTION:${description.replace(/[,;]/g, '\\$&')}`,
59+
`SUMMARY:${cleanSummary}`,
60+
`DESCRIPTION:${cleanDesc}`,
6061
'END:VEVENT'
6162
].join('\r\n');
6263
}
6364

64-
/**
65-
* Pipeline principal de génération
66-
*/
6765
async function generateIcsFile() {
6866
const buffer = [
6967
'BEGIN:VCALENDAR',
7068
'VERSION:2.0',
7169
`PRODID:${ICS_CONFIG.PRODUCT_ID}`,
7270
'CALSCALE:GREGORIAN',
7371
'METHOD:PUBLISH',
74-
'X-WR-CALNAME:Mon Planning',
75-
'X-WR-TIMEZONE:Europe/Paris'
72+
'X-WR-CALNAME:Mon Planning'
7673
];
7774

7875
try {
79-
// 1. Récupération des paramètres de rotation du localStorage
8076
const rotationOriginStr = localStorage.getItem('startDate');
81-
if (!rotationOriginStr) throw new Error('Date de début de rotation (lundi) manquante.');
77+
if (!rotationOriginStr) throw new Error('Date de début de rotation manquante.');
8278

8379
const rotationOrigin = new Date(rotationOriginStr);
8480
const originEpoch = TimeUtils.toEpochDay(rotationOrigin);
8581

86-
// 2. Résolution du pattern actif via l'objet window
8782
const patternType = localStorage.getItem('patternSelect') || 'IDE';
8883
let activePattern = [];
8984

@@ -97,13 +92,9 @@ async function generateIcsFile() {
9792
}
9893
}
9994

100-
if (!activePattern || activePattern.length === 0) {
101-
throw new Error(`Pattern "${patternType}" introuvable dans window.RotationPatterns.`);
102-
}
95+
if (!activePattern || activePattern.length === 0) throw new Error('Pattern introuvable.');
10396

104-
// 3. Chargement des modifications manuelles (overrides)
10597
const scheduleData = JSON.parse(localStorage.getItem(ICS_CONFIG.STORAGE_KEY) || '{}');
106-
10798
const now = new Date();
10899
const timestamp = TimeUtils.toIcsTimestamp(now);
109100

@@ -113,63 +104,51 @@ async function generateIcsFile() {
113104
const stopDate = new Date(now);
114105
stopDate.setFullYear(stopDate.getFullYear() + ICS_CONFIG.MAX_FUTURE_YEARS);
115106

116-
let eventCount = 0;
117-
118-
// 4. Itération sur la plage temporelle
119107
while (cursor <= stopDate) {
120108
const year = cursor.getFullYear();
121109
const month = cursor.getMonth() + 1;
122110
const day = cursor.getDate();
123111

124-
// Calcul de la valeur théorique (Rotation Pattern)
125112
const currentEpoch = TimeUtils.toEpochDay(cursor);
126113
const delta = currentEpoch - originEpoch;
127114
const pLen = activePattern.length;
128115
const pIdx = ((delta % pLen) + pLen) % pLen;
129116
const theoreticalCode = activePattern[pIdx];
130117

131-
// Vérification des overrides dans scheduleData
132118
const monthKey = `${year}-${month}`;
133119
const dayData = scheduleData[monthKey]?.[day];
134120

135-
// Si dayData est un tableau [Base, Modif], on prend l'index 1, sinon on prend la valeur simple
136121
const manualCode = Array.isArray(dayData) ? (dayData[1] || dayData[0]) : dayData;
137122
const eventCode = manualCode || theoreticalCode;
138123

139124
if (eventCode) {
140125
const meta = ICS_CONFIG.MAPPING[eventCode] || ICS_CONFIG.DEFAULT_META;
141126
const baseMeta = ICS_CONFIG.MAPPING[theoreticalCode] || ICS_CONFIG.DEFAULT_META;
142127

143-
// Composition du titre (ex: "S (M)") si modification
144128
const finalSummary = (theoreticalCode === eventCode || !theoreticalCode)
145129
? meta.s
146130
: `${meta.s} (${baseMeta.s})`;
147131

148132
buffer.push(buildEvent(cursor, finalSummary, meta.d, timestamp));
149-
eventCount++;
150133
}
151-
152134
cursor.setDate(cursor.getDate() + 1);
153135
}
154136

155137
buffer.push('END:VCALENDAR');
156138

157-
// Export final avec CRLF et BOM UTF-8 pour Windows/Google Calendar
139+
// Jointure finale avec saut de ligne RFC et SANS BOM au début
158140
const content = buffer.join('\r\n') + '\r\n';
159-
downloadFile(content, 'schedule.ics', 'text/calendar;charset=utf-8');
160-
console.log(`[ICS] Export réussi : ${eventCount} événements.`);
141+
downloadFile(content, 'planning.ics', 'text/calendar;charset=utf-8');
161142

162143
} catch (error) {
163144
console.error('[ICS ERROR]:', error.message);
164-
alert(`Erreur de génération : ${error.message}`);
145+
alert(`Erreur : ${error.message}`);
165146
}
166147
}
167148

168-
/**
169-
* Téléchargement du Blob avec BOM UTF-8
170-
*/
171149
function downloadFile(content, fileName, mimeType) {
172-
const blob = new Blob(['\ufeff', content], { type: mimeType });
150+
// Suppression du '\ufeff' (BOM) pour une meilleure compatibilité Android
151+
const blob = new Blob([content], { type: mimeType });
173152
const url = URL.createObjectURL(blob);
174153
const link = document.createElement('a');
175154
link.href = url;
@@ -178,7 +157,6 @@ function downloadFile(content, fileName, mimeType) {
178157
setTimeout(() => URL.revokeObjectURL(url), 500);
179158
}
180159

181-
// Initialisation au chargement du DOM
182160
document.addEventListener('DOMContentLoaded', () => {
183161
document.getElementById('generate-ics')?.addEventListener('click', generateIcsFile);
184162
});

app/UA0/scripts/development/serviceWorker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const CACHE_NAME = 'v3'
1+
const CACHE_NAME = 'v4'
22
const MEDIA_CACHE_NAME = `media-${CACHE_NAME}`
33
const ROOT_PATH = `/app/UA0/`
44
const OFFLINE_URL = `${ROOT_PATH}index.html`

app/UA0/scripts/main.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/UA0/sw.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)