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.tar.gz cms-f977313ff095a10478291334109d9aae40528a34.tar.bz2 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 ?> |
