diff options
author | polo <ordipolo@gmx.fr> | 2025-09-18 17:26:24 +0200 |
---|---|---|
committer | polo <ordipolo@gmx.fr> | 2025-09-18 17:26:24 +0200 |
commit | f977313ff095a10478291334109d9aae40528a34 (patch) | |
tree | 7e17b780c15c6882bdd962fb9d97ed2425847245 | |
parent | fa3c582a2bd91433399a5b275616052028a5a011 (diff) | |
download | cms-f977313ff095a10478291334109d9aae40528a34.zip |
gestion correcte des dates des articles: UTC côté serveur, locale côté client + date UTC dans l'attribut date-utc
-rw-r--r-- | public/js/main.js | 142 | ||||
-rw-r--r-- | public/js/tinymce.js | 7 | ||||
-rw-r--r-- | src/view/NewBuilder.php | 7 | ||||
-rw-r--r-- | src/view/templates/new.php | 2 |
4 files changed, 86 insertions, 72 deletions
diff --git a/public/js/main.js b/public/js/main.js index 2936ea0..d2f8876 100644 --- a/public/js/main.js +++ b/public/js/main.js | |||
@@ -29,7 +29,7 @@ function copyInClipBoard(link){ | |||
29 | alert('Cette adresse a été copiée dans le presse-papier:\n\n' + link); | 29 | alert('Cette adresse a été copiée dans le presse-papier:\n\n' + link); |
30 | } | 30 | } |
31 | 31 | ||
32 | function toastNotify(message) { | 32 | function toastNotify(message){ |
33 | var toast = document.getElementById('toast'); | 33 | var toast = document.getElementById('toast'); |
34 | toast.textContent = message; | 34 | toast.textContent = message; |
35 | toast.className = 'toast show'; | 35 | toast.className = 'toast show'; |
@@ -37,7 +37,15 @@ function toastNotify(message) { | |||
37 | } | 37 | } |
38 | 38 | ||
39 | 39 | ||
40 | document.addEventListener('DOMContentLoaded', () => { // pour pouvoir attraper les balises | 40 | // exécuté à la fin du chargement de la page |
41 | document.addEventListener('DOMContentLoaded', () => { | ||
42 | |||
43 | // détection des dates et conversion à l'heure locale | ||
44 | document.querySelectorAll('.local_date').forEach(function(element){ | ||
45 | const utc_date = element.getAttribute('date-utc'); // forme: 2025-10-10T12:17:00+00:00 | ||
46 | element.innerText = toFormatedLocalDate(utc_date); | ||
47 | }); | ||
48 | |||
41 | // ouvrir/fermer les sous-menus avec écran tactile | 49 | // ouvrir/fermer les sous-menus avec écran tactile |
42 | document.querySelectorAll('.sub-menu-toggle').forEach(button => { | 50 | document.querySelectorAll('.sub-menu-toggle').forEach(button => { |
43 | button.addEventListener('click', e => { | 51 | button.addEventListener('click', e => { |
@@ -108,8 +116,7 @@ function switchPositions(article_id, direction) | |||
108 | }) | 116 | }) |
109 | .then(response => response.json()) | 117 | .then(response => response.json()) |
110 | .then(data => { | 118 | .then(data => { |
111 | if(data.success) | 119 | if(data.success){ |
112 | { | ||
113 | if(direction == 'down'){ | 120 | if(direction == 'down'){ |
114 | current_article.parentElement.insertBefore(other_article, current_article); | 121 | current_article.parentElement.insertBefore(other_article, current_article); |
115 | console.log('Inversion réussie'); | 122 | console.log('Inversion réussie'); |
@@ -119,12 +126,11 @@ function switchPositions(article_id, direction) | |||
119 | console.log('Inversion réussie'); | 126 | console.log('Inversion réussie'); |
120 | } | 127 | } |
121 | else{ | 128 | else{ |
122 | console.error('Échec de l\'inversion'); | 129 | console.error("Échec de l'inversion"); |
123 | } | 130 | } |
124 | } | 131 | } |
125 | else { | 132 | else{ |
126 | 133 | console.error("Échec de l'inversion"); | |
127 | console.error('Échec de l\'inversion'); | ||
128 | } | 134 | } |
129 | }) | 135 | }) |
130 | .catch(error => { | 136 | .catch(error => { |
@@ -132,14 +138,42 @@ function switchPositions(article_id, direction) | |||
132 | }); | 138 | }); |
133 | } | 139 | } |
134 | 140 | ||
135 | function changeDate(id_date) | 141 | function closeInput(id) |
142 | { | ||
143 | const date_span = document.getElementById(id); | ||
144 | const date_input = document.getElementById('input-' + id); | ||
145 | const date_label = document.getElementById('label-' + id); | ||
146 | |||
147 | date_span.classList.remove('hidden'); | ||
148 | date_input.remove(); | ||
149 | date_label.remove(); | ||
150 | document.querySelector(`#edit-${id}`).classList.remove('hidden'); | ||
151 | document.querySelector(`#cancel-${id}`).classList.add('hidden'); | ||
152 | document.querySelector(`#submit-${id}`).classList.add('hidden'); | ||
153 | } | ||
154 | |||
155 | function findParentByTagName(element, tag_name){ | ||
156 | while(element !== null){ | ||
157 | if(element.tagName === tag_name.toUpperCase()){ // tagName est en majuscules | ||
158 | return element; | ||
159 | } | ||
160 | element = element.parentElement; | ||
161 | } | ||
162 | return null; | ||
163 | } | ||
164 | |||
165 | |||
166 | /* -- fonctions spécifiques à la gestion des dates -- */ | ||
167 | |||
168 | function openDatetimeLocalInput(id_date) | ||
136 | { | 169 | { |
137 | const real_id = 'i' + id_date.slice(1); | 170 | const real_id = 'i' + id_date.slice(1); |
138 | const date_span = document.getElementById(id_date); // = <span> | 171 | const date_span = document.getElementById(id_date); // = <span> |
139 | var old_date = date_span.innerHTML; | ||
140 | |||
141 | old_date = dateToISO(old_date); | ||
142 | 172 | ||
173 | /*var old_date = date_span.innerHTML;*/ | ||
174 | let old_date = new Date(date_span.getAttribute('date-utc')); | ||
175 | old_date = forInputTypeDatetimeLocal(old_date); // 2025-06-03T17:24 | ||
176 | |||
143 | var label = document.createElement('label'); | 177 | var label = document.createElement('label'); |
144 | label.textContent = 'Choisir une date: '; | 178 | label.textContent = 'Choisir une date: '; |
145 | label.id = 'label-' + id_date; | 179 | label.id = 'label-' + id_date; |
@@ -159,39 +193,22 @@ function changeDate(id_date) | |||
159 | document.querySelector(`#submit-${id_date}`).classList.remove('hidden'); | 193 | document.querySelector(`#submit-${id_date}`).classList.remove('hidden'); |
160 | } | 194 | } |
161 | 195 | ||
162 | function dateToISO(date){ | ||
163 | // changer "le 28-12-2024 à 23h14" en "2024-12-28T23:14" | ||
164 | let values = date.split(" à "); // 2 parties: date et heure | ||
165 | values[1] = values[1].replace('h', ':'); | ||
166 | values[0] = values[0].replace("le ", ""); | ||
167 | let date_array = values[0].split("-"); // tableau jj-mm-aaaa | ||
168 | return date_array[2] + '-' + date_array[1] + "-" + date_array[0] + "T" + values[1]; | ||
169 | } | ||
170 | |||
171 | function closeInput(id) | ||
172 | { | ||
173 | const date_span = document.getElementById(id); | ||
174 | const date_input = document.getElementById('input-' + id); | ||
175 | const date_label = document.getElementById('label-' + id); | ||
176 | |||
177 | date_span.classList.remove('hidden'); | ||
178 | date_input.remove(); | ||
179 | date_label.remove(); | ||
180 | document.querySelector(`#edit-${id}`).classList.remove('hidden'); | ||
181 | document.querySelector(`#cancel-${id}`).classList.add('hidden'); | ||
182 | document.querySelector(`#submit-${id}`).classList.add('hidden'); | ||
183 | } | ||
184 | |||
185 | function submitDate(id_date) | 196 | function submitDate(id_date) |
186 | { | 197 | { |
187 | var date_input = document.getElementById('input-' + id_date); | 198 | const date_input = document.getElementById('input-' + id_date); |
199 | const date_span = document.getElementById(id_date); | ||
200 | |||
201 | let date = new Date(date_input.value); // forme: 2025-02-04T00:24 | ||
202 | let utc_date = date.toISOString(); // forme: 2025-02-03T23:24:00.000Z | ||
188 | 203 | ||
189 | // cas des nouvelles "news" | 204 | // cas des nouvelles "news" |
190 | const params = new URL(document.location).searchParams; // "search" = ? et paramètres, searchParams = objet avec des getters | 205 | const params = new URL(document.location).searchParams; // "search" = ? et paramètres, searchParams = objet avec des getters |
191 | if(params != null && params.get("id")[0] === 'n') | 206 | if(params != null && params.get("id")[0] === 'n') |
192 | { | 207 | { |
193 | // modifier la date dans le <span> caché | 208 | // modifier la date dans le <span> caché ET l'attribut date-utc |
194 | date_input = updateDate(id_date, date_input); | 209 | date_span.setAttribute('date-utc', utc_date); // heure UTC |
210 | date_span.innerHTML = toFormatedLocalDate(utc_date); // heure locale | ||
211 | |||
195 | closeInput(id_date); | 212 | closeInput(id_date); |
196 | return; | 213 | return; |
197 | } | 214 | } |
@@ -201,44 +218,41 @@ function submitDate(id_date) | |||
201 | headers: { | 218 | headers: { |
202 | 'Content-Type': 'application/json' | 219 | 'Content-Type': 'application/json' |
203 | }, | 220 | }, |
204 | body: JSON.stringify({id: id_date, date: date_input.value}) | 221 | body: JSON.stringify({id: id_date, date: utc_date.slice(0, 16)}) // heure UTC |
205 | }) | 222 | }) |
206 | .then(response => response.json()) | 223 | .then(response => response.json()) |
207 | .then(data => { | 224 | .then(data => { |
208 | if (data.success) { | 225 | if(data.success){ |
209 | // modifier la date dans le <span> caché | 226 | // modifier la date dans le <span> caché ET l'attribut date-utc |
210 | date_input = updateDate(id_date, date_input); | 227 | date_span.setAttribute('date-utc', utc_date); // heure UTC |
228 | date_span.innerHTML = toFormatedLocalDate(utc_date); // heure locale | ||
229 | |||
211 | closeInput(id_date); | 230 | closeInput(id_date); |
212 | } | 231 | } |
213 | else { | 232 | else{ |
214 | console.error('Erreur lors de la sauvegarde de la date.'); | 233 | console.error('Erreur lors de la sauvegarde de la date.'); |
215 | } | 234 | } |
216 | }) | 235 | }) |
217 | .catch(error => { | 236 | .catch(error => { |
218 | console.error('Erreur:', error); | 237 | console.error('Erreur:', error); |
219 | }); | 238 | }); |
220 | } | 239 | } |
221 | } | 240 | } |
222 | 241 | ||
223 | function updateDate(id_date, date_input){ | 242 | function toFormatedLocalDate(utc_string_date){ // forme: 2025-07-17T13:54:00.000Z ou 2025-02-04T00:24 |
224 | var date_span = document.getElementById(id_date); | 243 | const date = new Date(utc_string_date); |
225 | let date = new Date(date_input.value); | ||
226 | date_span.innerHTML = | ||
227 | 'le ' + String(date.getDate()).padStart(2, '0') + '-' + | ||
228 | String(date.getMonth() + 1).padStart(2, '0') + '-' + | ||
229 | String(date.getFullYear()).padStart(4, '0') + ' à ' + | ||
230 | String(date.getHours()).padStart(2, '0') + 'h' + | ||
231 | String(date.getMinutes()).padStart(2, '0'); | ||
232 | |||
233 | return date_input; | ||
234 | } | ||
235 | 244 | ||
236 | function findParentByTagName(element, tag_name){ | 245 | // donne l'heure locale, forme: le 10-10-2025 à 14:17 |
237 | while(element !== null){ | 246 | return 'le ' + String(date.getDate()).padStart(2, '0') |
238 | if(element.tagName === tag_name.toUpperCase()){ // tagName est en majuscules | 247 | + '-' + String(date.getMonth() + 1).padStart(2, '0') // mois comptés à partir de 0 !! |
239 | return element; | 248 | + '-' + date.getFullYear() |
240 | } | 249 | + ' à ' + String(date.getHours()).padStart(2, '0') |
241 | element = element.parentElement; | 250 | + 'h' + String(date.getMinutes()).padStart(2, '0'); |
242 | } | 251 | } |
243 | return null; | 252 | function forInputTypeDatetimeLocal(date){ // forme: 2024-12-28T23:14 |
253 | return date.getFullYear() | ||
254 | + '-' + String(date.getMonth() + 1).padStart(2, '0') // mois comptés à partir de 0 !! | ||
255 | + '-' + String(date.getDate()).padStart(2, '0') | ||
256 | + 'T' + String(date.getHours()).padStart(2, '0') | ||
257 | + ':' + String(date.getMinutes()).padStart(2, '0'); | ||
244 | } \ No newline at end of file | 258 | } \ No newline at end of file |
diff --git a/public/js/tinymce.js b/public/js/tinymce.js index 81ba8ea..97ecad8 100644 --- a/public/js/tinymce.js +++ b/public/js/tinymce.js | |||
@@ -292,7 +292,6 @@ class Editor | |||
292 | } | 292 | } |
293 | 293 | ||
294 | submit(clone = null){ | 294 | submit(clone = null){ |
295 | //var editor; | ||
296 | var content; | 295 | var content; |
297 | const params = new URL(document.location).searchParams; // "search" = ? et paramètres, searchParams = objet avec des getters | 296 | const params = new URL(document.location).searchParams; // "search" = ? et paramètres, searchParams = objet avec des getters |
298 | // à comparer avec: new URLSearchParams(window.location.search); | 297 | // à comparer avec: new URLSearchParams(window.location.search); |
@@ -312,9 +311,12 @@ class Editor | |||
312 | if(first_letter === 'i'){ | 311 | if(first_letter === 'i'){ |
313 | id_from_builder = element.id; | 312 | id_from_builder = element.id; |
314 | } | 313 | } |
314 | else if(first_letter === 'd'){ | ||
315 | content[first_letter] = element.getAttribute('date-utc'); | ||
316 | } | ||
315 | } | 317 | } |
316 | }) | 318 | }) |
317 | content['d'] = dateToISO(content['d']); | 319 | content['d'] = new Date(content['d']).toISOString().slice(0, 16); // date UTC, format: 2025-09-18T15:21 |
318 | } | 320 | } |
319 | // champs à remplir des nouvelles "news" | 321 | // champs à remplir des nouvelles "news" |
320 | else if(window.Config.page === 'article' && params != null && params.get("id")[0] === 'n'){ | 322 | else if(window.Config.page === 'article' && params != null && params.get("id")[0] === 'n'){ |
@@ -324,7 +326,6 @@ class Editor | |||
324 | // dans les autres cas, on doit pouvoir récupérer l'éditeur | 326 | // dans les autres cas, on doit pouvoir récupérer l'éditeur |
325 | else{ | 327 | else{ |
326 | // l'éditeur correspond à l'article OU si page = "article" à un élément: titre, aperçu, article | 328 | // l'éditeur correspond à l'article OU si page = "article" à un élément: titre, aperçu, article |
327 | //editor = editors[id]; | ||
328 | if(!this.tiny_instance){ | 329 | if(!this.tiny_instance){ |
329 | console.error("Éditeur non trouvé pour l'article:", this.id); | 330 | console.error("Éditeur non trouvé pour l'article:", this.id); |
330 | return; | 331 | return; |
diff --git a/src/view/NewBuilder.php b/src/view/NewBuilder.php index cd5534b..2a082dd 100644 --- a/src/view/NewBuilder.php +++ b/src/view/NewBuilder.php | |||
@@ -61,9 +61,8 @@ class NewBuilder extends AbstractBuilder | |||
61 | $from_to_button = '<p><a class="link_to_article" href="' . new URL(['page' => 'article', 'id' => $id, 'from' => CURRENT_PAGE]) . '"><button><img class="action_icon" src="assets/book-open.svg">Lire la suite</button></a></p>'; | 61 | $from_to_button = '<p><a class="link_to_article" href="' . new URL(['page' => 'article', 'id' => $id, 'from' => CURRENT_PAGE]) . '"><button><img class="action_icon" src="assets/book-open.svg">Lire la suite</button></a></p>'; |
62 | } | 62 | } |
63 | 63 | ||
64 | 64 | $date = $node->getArticle()->getDateTime()->format('Y-m-d\TH:i:s.v\Z'); // format: 2025-07-17T13:54:00.000Z | |
65 | $date_object = $node->getArticle()->getDateTime(); // class DateTime | 65 | // format(\DateTime::ATOM) produit le format: 2025-10-10T12:17:00+00:00, c'est aussi de la norme ISO, mais à éviter pour être compatible avec date.toISOString en JS |
66 | $date = 'le ' . str_replace(':', 'h', $date_object->format('d-m-Y à H:i')); | ||
67 | 66 | ||
68 | // partage | 67 | // partage |
69 | $share_link = new URL(['page' => 'article', 'id' => $id]); | 68 | $share_link = new URL(['page' => 'article', 'id' => $id]); |
@@ -108,7 +107,7 @@ class NewBuilder extends AbstractBuilder | |||
108 | $submit_article = '<p id="submit-' . $id_content . '" class="hidden"><button ' . $submit_js_article . '>Valider</button></p>'; | 107 | $submit_article = '<p id="submit-' . $id_content . '" class="hidden"><button ' . $submit_js_article . '>Valider</button></p>'; |
109 | $article_buttons = '<div class="button_zone">' . $modify_article . $close_editor_article . $submit_article . '</div>'; | 108 | $article_buttons = '<div class="button_zone">' . $modify_article . $close_editor_article . $submit_article . '</div>'; |
110 | 109 | ||
111 | $date_js = 'onclick="changeDate(\'' . $id_date . '\', \'article\');'; | 110 | $date_js = 'onclick="openDatetimeLocalInput(\'' . $id_date . '\', \'article\');'; |
112 | $modify_date = '<p id="edit-' . $id_date . '"><button ' . $date_js . '"><img class="action_icon" src="assets/edit.svg">Date</button></p>' . "\n"; | 111 | $modify_date = '<p id="edit-' . $id_date . '"><button ' . $date_js . '"><img class="action_icon" src="assets/edit.svg">Date</button></p>' . "\n"; |
113 | $close_js_date = 'onclick="closeInput(\'' . $id_date . '\')"'; | 112 | $close_js_date = 'onclick="closeInput(\'' . $id_date . '\')"'; |
114 | $close_editor_date = '<p id="cancel-' . $id_date . '" class="hidden"><button ' . $close_js_date . '>Annuler</button></p>'; | 113 | $close_editor_date = '<p id="cancel-' . $id_date . '" class="hidden"><button ' . $close_js_date . '>Annuler</button></p>'; |
diff --git a/src/view/templates/new.php b/src/view/templates/new.php index bda7daf..b1b3453 100644 --- a/src/view/templates/new.php +++ b/src/view/templates/new.php | |||
@@ -18,7 +18,7 @@ | |||
18 | <div class="under_an_article"> | 18 | <div class="under_an_article"> |
19 | <p> | 19 | <p> |
20 | <img src="assets/calendar.svg"> | 20 | <img src="assets/calendar.svg"> |
21 | <span class="data" id="<?= $id_date ?>"><?= $date ?></span> | 21 | <span class="data local_date" id="<?= $id_date ?>" date-utc="<?= $date ?>">Chargement...</span> |
22 | </p> | 22 | </p> |
23 | </div> | 23 | </div> |
24 | <?= $date_buttons ?> | 24 | <?= $date_buttons ?> |