aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2025-09-05 11:27:37 +0200
committerpolo <ordipolo@gmx.fr>2025-09-05 11:27:37 +0200
commit46b455a0d3e96099e78975f53a25365a2ca9dcb4 (patch)
tree54f4616a103410d2bc77827b738f592806dbff67
parentf477314613beb26b0ce4c61ec0b1900df1de1cb1 (diff)
downloadcms-46b455a0d3e96099e78975f53a25365a2ca9dcb4.zip
classe Editor et encapsulation, placement article premier ou dernier, variable window.Config.page, séparation openEditor et initEditor
-rw-r--r--public/css/calendar.css6
-rw-r--r--public/css/modif_page.css13
-rw-r--r--public/js/main.js17
-rw-r--r--public/js/menu.js87
-rw-r--r--public/js/modif_page.js9
-rw-r--r--public/js/new_page.js5
-rw-r--r--public/js/tinymce.js647
-rw-r--r--src/controller/ArticleController.php26
-rw-r--r--src/controller/PageManagementController.php7
-rw-r--r--src/view/MainBuilder.php6
-rw-r--r--src/view/NewBuilder.php20
-rw-r--r--src/view/NewsBlockBuilder.php2
-rw-r--r--src/view/PostBlockBuilder.php13
-rw-r--r--src/view/PostBuilder.php1
-rw-r--r--src/view/templates/head.php1
15 files changed, 428 insertions, 432 deletions
diff --git a/public/css/calendar.css b/public/css/calendar.css
index 314a5ad..1b05fbd 100644
--- a/public/css/calendar.css
+++ b/public/css/calendar.css
@@ -63,15 +63,15 @@ td .fc-timegrid-axis{
63 #calendar_zone{ 63 #calendar_zone{
64 padding: 0; 64 padding: 0;
65 } 65 }
66 #calendar_zone p, .event > button{
67 font-size: smaller;
68 }
66 .fc-toolbar-title{ 69 .fc-toolbar-title{
67 /*font-size: large !important;*/ 70 /*font-size: large !important;*/
68 } 71 }
69 .fc-header-toolbar{ 72 .fc-header-toolbar{
70 font-size: smaller; 73 font-size: smaller;
71 } 74 }
72 h3, p, .event > button{
73 font-size: smaller;
74 }
75} 75}
76@media screen and (max-width: 650px){ 76@media screen and (max-width: 650px){
77 .fc-header-toolbar{ 77 .fc-header-toolbar{
diff --git a/public/css/modif_page.css b/public/css/modif_page.css
index 9048f05..8095766 100644
--- a/public/css/modif_page.css
+++ b/public/css/modif_page.css
@@ -85,13 +85,20 @@
85.modify_one_block > div 85.modify_one_block > div
86{ 86{
87 display: flex; 87 display: flex;
88 align-items: baseline; 88 align-items: center;
89}
90.block_options
91{
92 flex-wrap: wrap;
93}
94.block_options > div /* bloc boutons flèches et supprimer */
95{
96 display: flex;
89} 97}
90.block_options > * 98.block_options > *
91{ 99{
92 /*display: flex;
93 flex-wrap: wrap;*/
94 text-wrap: nowrap; 100 text-wrap: nowrap;
101 align-items: center;
95} 102}
96.grid_options 103.grid_options
97{ 104{
diff --git a/public/js/main.js b/public/js/main.js
index 4be7843..8f2b214 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -1,10 +1,10 @@
1function newPassword(page, id = ''){ 1function newPassword(id = ''){
2 if(id != ''){ 2 if(id != ''){
3 id = '&id=' + id; 3 id = '&id=' + id;
4 } 4 }
5 alert('Le mot de passe a été modifié.'); 5 alert('Le mot de passe a été modifié.');
6 window.setTimeout(function(){ 6 window.setTimeout(function(){
7 location.href = "index.php?page=" + page + "&message=nouveau_mdp" + id; 7 location.href = "index.php?page=" + window.Config.page + "&message=nouveau_mdp" + id;
8 }, 0); 8 }, 0);
9} 9}
10 10
@@ -71,14 +71,16 @@ document.addEventListener('DOMContentLoaded', () => { // pour pouvoir attraper l
71 nav_zone.style.height = entry.contentRect.height + 'px'; 71 nav_zone.style.height = entry.contentRect.height + 'px';
72 } 72 }
73 }); 73 });
74 resize_observer.observe(nav); 74 if(nav){
75 resize_observer.observe(nav);
76 }
75}); 77});
76 78
77 79
78// complète les fonctions dans tinymce.js 80// complète les fonctions dans tinymce.js
79function switchPositions(article_id, direction) 81function switchPositions(article_id, direction)
80{ 82{
81 const current_article = findParent(document.getElementById(article_id), 'article'); // l'id n'est pas sur la bonne balise 83 const current_article = findParentByTagName(document.getElementById(article_id), 'article'); // l'id n'est pas sur la bonne balise
82 var other_article; 84 var other_article;
83 85
84 if(direction == 'down'){ 86 if(direction == 'down'){
@@ -232,10 +234,9 @@ function updateDate(id_date, date_input){
232 return date_input; 234 return date_input;
233} 235}
234 236
235function findParent(element, tag_name){ 237function findParentByTagName(element, tag_name){
236 while (element !== null) { 238 while(element !== null){
237 if (element.tagName === tag_name.toUpperCase()) // tagName est en majuscules 239 if(element.tagName === tag_name.toUpperCase()){ // tagName est en majuscules
238 {
239 return element; 240 return element;
240 } 241 }
241 element = element.parentElement; 242 element = element.parentElement;
diff --git a/public/js/menu.js b/public/js/menu.js
index ac6d35e..7f48ac9 100644
--- a/public/js/menu.js
+++ b/public/js/menu.js
@@ -63,93 +63,6 @@ function moveOneLevelDown(page_id)
63 .catch(error => { 63 .catch(error => {
64 console.error('Erreur:', error); 64 console.error('Erreur:', error);
65 }); 65 });
66
67 /*const element = document.getElementById(page_id); // div parente du bouton cliqué
68 let previous_element = element.previousElementSibling;
69
70 if(previous_element != null)
71 {
72 // si l'element précédent n'a pas de chemin relatif, donc est une URL, on vérifie le précédent également
73 if(previous_element.querySelector(".path") == null){
74 let test_previous = previous_element;
75 while(test_previous.querySelector(".url") != null){
76 console.log(test_previous);
77 //if()
78 test_previous = test_previous.previousElementSibling;
79 if(test_previous == null){
80 console.log("pas d'élément précédent");
81 return;
82 }
83 console.log(test_previous);
84 }
85 previous_element = test_previous;
86 }
87
88 fetch('index.php?menu_edit=move_one_level_down', {
89 method: 'POST',
90 headers: {
91 'Content-Type': 'application/json'
92 },
93 body: JSON.stringify({ id: element.id })
94 })
95 .then(response => response.json())
96 .then(data => {
97 if(data.success)
98 {
99 //
100
101 // menu régénéré
102 nav_zone.innerHTML = '';
103 nav_zone.insertAdjacentHTML('afterbegin', data.nav);
104 }
105 else {
106
107 console.error('Échec de l\'inversion');
108 }
109 })
110 .catch(error => {
111 console.error('Erreur:', error);
112 });
113
114 // nouveau parent
115 let level_div = previous_element.querySelector(".level");
116 if(level_div == null){
117 // créer une <div class="level">
118 level_div = document.createElement("div");
119 level_div.classList.add("level");
120 previous_element.appendChild(level_div);
121 }
122
123 // déplacement
124 level_div.appendChild(element);
125
126 // marges
127 let margin_left = parseInt(element.style.marginLeft);
128 margin_left += 29;
129 element.style.marginLeft = String(margin_left) + "px";
130
131 // MAJ des chemins affichés si c'est un chemin relatif (les liens URL ne peuvent avoir d'enfants)
132 const element_path = element.querySelector(".path");
133 if(element_path != null){
134 const previous_element_path = previous_element.querySelector(".path");
135 element_path.innerHTML = previous_element_path.innerHTML + "/" + element_path.innerHTML.split("/").slice(-1);
136
137 // même chose pour tous les enfants sauf les URL vers l'extérieur
138 if(element.querySelector(".level") != null){
139 element.querySelector(".level").querySelectorAll(".path").forEach( (one_elem) => {
140 const parent_elem_path = one_elem.parentNode.parentNode.parentNode.querySelector(".path"); // => div de l'élém => div class level => div du parent
141 const end_of_path = one_elem.innerHTML.split("/").slice(-1);
142 one_elem.innerHTML = parent_elem_path.innerHTML + "/" + end_of_path[0];
143 });
144 }
145 }
146
147 // dernier problème à corriger: le parent est une URL vers l'extérieur
148 }
149 else{
150 // ne rien faire
151 console.log("pas d'élément précédent");
152 }*/
153} 66}
154 67
155function switchMenuPositions(page_id, direction) 68function switchMenuPositions(page_id, direction)
diff --git a/public/js/modif_page.js b/public/js/modif_page.js
index 615f34a..dd7271e 100644
--- a/public/js/modif_page.js
+++ b/public/js/modif_page.js
@@ -2,10 +2,7 @@
2 2
3// même fonction que dans new_page.js 3// même fonction que dans new_page.js
4function makePageNamePath(){ 4function makePageNamePath(){
5 const page_name = document.getElementById("page_name"); 5 document.getElementById("page_name_path").value = document.getElementById("page_name").value
6 const page_name_path = document.getElementById("page_name_path");
7
8 page_name_path.value = page_name.value
9 .normalize("NFD") // décompose lettres + accents: é devient "e + accent aigu" 6 .normalize("NFD") // décompose lettres + accents: é devient "e + accent aigu"
10 .replace(/[\u0300-\u036f]/g, "") // supprime les accents 7 .replace(/[\u0300-\u036f]/g, "") // supprime les accents
11 .replace(/[^a-zA-Z0-9]+/g, " ") // supprime tout ce qu'il n'est pas alphanuméric 8 .replace(/[^a-zA-Z0-9]+/g, " ") // supprime tout ce qu'il n'est pas alphanuméric
@@ -130,7 +127,7 @@ function renamePageBloc(bloc_id){
130 }); 127 });
131} 128}
132 129
133function switchBlocsPositions(bloc_id, direction, current_page) { 130function switchBlocsPositions(bloc_id, direction) {
134 const current_bloc = document.getElementById(bloc_id); 131 const current_bloc = document.getElementById(bloc_id);
135 const current_bloc_edit_zone = document.getElementById("bloc_edit_" + bloc_id); 132 const current_bloc_edit_zone = document.getElementById("bloc_edit_" + bloc_id);
136 var other_bloc; 133 var other_bloc;
@@ -149,7 +146,7 @@ function switchBlocsPositions(bloc_id, direction, current_page) {
149 } 146 }
150 const other_bloc_edit_zone = document.getElementById("bloc_edit_" + other_bloc.id); 147 const other_bloc_edit_zone = document.getElementById("bloc_edit_" + other_bloc.id);
151 148
152 fetch('index.php?page=' + current_page + '&bloc_edit=switch_blocs_positions', { 149 fetch('index.php?page=' + window.Config.page + '&bloc_edit=switch_blocs_positions', {
153 method: 'POST', 150 method: 'POST',
154 headers: { 'Content-Type': 'application/json' }, 151 headers: { 'Content-Type': 'application/json' },
155 body: JSON.stringify({ id1: bloc_id, id2: parseInt(other_bloc.id) }) 152 body: JSON.stringify({ id1: bloc_id, id2: parseInt(other_bloc.id) })
diff --git a/public/js/new_page.js b/public/js/new_page.js
index 5b1c5c5..4b49060 100644
--- a/public/js/new_page.js
+++ b/public/js/new_page.js
@@ -2,10 +2,7 @@
2 2
3// même fonction que dans modif_page.js 3// même fonction que dans modif_page.js
4function makePageNamePath(){ 4function makePageNamePath(){
5 const page_name = document.getElementById("page_name"); 5 document.getElementById("page_name_path").value = document.getElementById("page_name").value
6 const page_name_path = document.getElementById("page_name_path");
7
8 page_name_path.value = page_name.value
9 .normalize("NFD") // décompose lettres + accents: é devient "e + accent aigu" 6 .normalize("NFD") // décompose lettres + accents: é devient "e + accent aigu"
10 .replace(/[\u0300-\u036f]/g, "") // supprime les accents 7 .replace(/[\u0300-\u036f]/g, "") // supprime les accents
11 .replace(/[^a-zA-Z0-9]+/g, " ") // supprime tout ce qu'il n'est pas alphanuméric 8 .replace(/[^a-zA-Z0-9]+/g, " ") // supprime tout ce qu'il n'est pas alphanuméric
diff --git a/public/js/tinymce.js b/public/js/tinymce.js
index f153246..071c61b 100644
--- a/public/js/tinymce.js
+++ b/public/js/tinymce.js
@@ -1,178 +1,40 @@
1let editors = {}; 1// code à réorganiser
2// seule certaines fonctions ont leur place dans Editor, d'autres servent à manipuler les articles d'une autre manière (déplacer, supprimer...)
3// => encapsuler Editor dans une classe Article (comme la balise) qui existe même quand l'éditeur est fermé
2 4
3function openEditor(id, page = '') {
4 var creation_mode;
5 var real_id;
6 var article;
7 5
8 // création ou modification d'un article? 6/* -- utilisé par les évènements -- */
9 if(id[0] === 'n'){ 7let editors = {};
10 creation_mode = true; 8function openEditor(id){
11 article = document.getElementById(id); 9 if(!editors[id]){
10 editors[id] = new Editor(id); // appel de init à l'intérieur
12 } 11 }
13 else{ 12 //else{editors[id].reopen();}
14 creation_mode = false; 13}
15 // Récupérer et sauvegarder le contenu d'origine de l'article 14// placement d'un nouvel article dans un bloc "Articles libres"
16 real_id = 'i' + id.slice(1); 15function setArticlePlacement(id){
17 article = document.getElementById(id); 16 if(editors[id]){
18 document.getElementById(id).setAttribute('data-original-content', article.innerHTML); 17 editors[id].setArticlePlacement(id);
19 } 18 }
20 19}
21 tinymce.init({ 20function closeEditor(id, restore_old){
22 selector: `#${id}`, 21 if(editors[id]){
23 language: 'fr_FR', // téléchargement ici: https://www.tiny.cloud/get-tiny/language-packages/ 22 editors[id].close(restore_old);
24 language_url: 'js/tinymce-langs/fr_FR.js', // ou installer tweeb/tinymce-i18n avec composer
25 license_key: 'gpl',
26 branding: false,
27 plugins: 'lists link autolink table image media autoresize help',
28 toolbar: 'undo redo newdocument print selectall styles bold italic underline strikethrough fontsizeinput forecolor backcolor fontfamily align numlist bullist outdent indent table link image media help',
29 menubar: false,
30 toolbar_mode: 'wrap',
31 statusbar: false,
32 setup: function (editor) {
33 editor.on('init', function (){
34 editors[id] = editor;
35
36 // boutons "Modifier", "Supprimer", "déplacer vers le haut", "déplacer vers le bas", "Annuler" et "Soumettre"
37 document.querySelector(`#cancel-${id}`).classList.remove('hidden');
38 document.querySelector(`#submit-${id}`).classList.remove('hidden');
39 if(creation_mode === false){
40 document.querySelector(`#edit-${id}`).classList.add('hidden');
41 if(page != 'article'){
42 document.querySelector(`#position_up-${id}`).classList.add('hidden');
43 document.querySelector(`#position_down-${id}`).classList.add('hidden');
44 document.querySelector(`#delete-${real_id}`).classList.add('hidden');
45 }
46 }
47 else{
48 document.querySelector(`#new-${id}`).classList.add('hidden'); // id = new-new-id_node
49 }
50 });
51 let skipPastePreProcess = false;
52 editor.on('Paste', function (e){ // déclenchement AVANT PastePreProcess et quelque que soit le contenu collé
53 const clipboardData = (e.clipboardData || e.originalEvent.clipboardData);
54 if(!clipboardData){
55 return;
56 }
57 const items = clipboardData.items;
58 let foundImage = false;
59
60 for(let i = 0; i < items.length; i++){
61 let item = items[i];
62
63 if(item.type.indexOf('image') !== -1){ // test type MIME contenant image
64 foundImage = true;
65
66 const file = item.getAsFile(); // presse-papier => fichier lisible
67 const reader = new FileReader();
68
69 reader.onload = function (event){ // fonction exécutée lorsque reader.readAsDataURL(file) est terminée
70 const base64Data = event.target.result; // données de l'image
71
72 fetch('index.php?action=upload_image_base64', {
73 method: 'POST',
74 headers: { 'Content-Type': 'application/json' },
75 body: JSON.stringify({ image_base64: base64Data })
76 })
77 .then(response => response.json())
78 .then(data => {
79 if(data.location){
80 editor.insertContent('<img src="' + data.location + '">');
81 }
82 })
83 .catch(error => {
84 console.error('Erreur lors de l’upload de l’image base64 :', error);
85 });
86 };
87 reader.readAsDataURL(file); // lecture asynchrone du fichier
88 }
89 }
90
91 if(foundImage){
92 e.preventDefault(); // supprime le collage automatiue
93 skipPastePreProcess = true; // désactiver le PastePreProcess pour ce collage
94 }
95 });
96 editor.on('PastePreProcess', function (e){ // déclenchement au collage AVANT insertion dans l'éditeur
97 const parser = new DOMParser();
98 const doc = parser.parseFromString(e.content, 'text/html');
99 const images = doc.querySelectorAll('img');
100
101 let downloads_in_progress = [];
102
103 images.forEach(img => {
104 if(img.src.startsWith('file://')){ // détection d'images non insérables
105 console.warn('Image locale non insérable dans tinymce :', img.src);
106 img.outerHTML = `<div style="border:1px solid red; padding:10px; margin:5px 0; background-color:#ffe6e6; color:#a94442; font-size:14px;">
107 "Image locale non insérée (vient-elle d'un document LibreOffice ?). Effacez ce message rouge et copiez-collez l'image seule.</div>`;
108 }
109 else if(img.src.startsWith('http')){ // détection d'images web
110 const promise = fetch('index.php?action=upload_image_url', { // promesse d'un fichier téléchargeable sur le serveur
111 method: 'POST',
112 headers: { 'Content-Type': 'application/json' },
113 body: JSON.stringify({ image_url: img.src })
114 })
115 .then(response => response.json())
116 .then(data => {
117 if(data.location){
118 img.src = data.location; // remplacer l'image par celle du serveur
119 }
120 })
121 .catch(error => {
122 console.error('Erreur lors de l’upload de l’image distante:', error);
123 });
124
125 downloads_in_progress.push(promise);
126 }
127 });
128
129 // une image web ou plus: différer l'insertion dans l'éditeur le temps que le serveur télécharge les images
130 if(downloads_in_progress.length > 0){
131 e.preventDefault();
132
133 Promise.all(downloads_in_progress).then(() => {
134 e.content = doc.body.innerHTML; // remplacement du HTML dans l'éditeur par la copie modifiée (doc)
135 editor.insertContent(e.content);
136 });
137 }
138 else{
139 e.content = doc.body.innerHTML; // remplacement du HTML dans l'éditeur par la copie modifiée (doc)
140 }
141 }); // fin editor.on('PastePreProcess'...
142 },
143 // upload d'image natif de tinymce avec le bouton "Insérer une image"
144 images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {
145 const formData = new FormData();
146 formData.append("file", blobInfo.blob());
147
148 fetch("index.php?action=upload_image_tinymce", {
149 method: "POST",
150 body: formData
151 })
152 .then(response => response.json())
153 .then(data => {
154 if(data.location) {
155 resolve(data.location);
156 }
157 else {
158 reject("Erreur: Chemin d'image invalide");
159 }
160 })
161 .catch(error => {
162 reject("Erreur lors de l'upload");
163 });
164 }),
165 image_caption: true
166 });
167
168 // Remplacer le contenu de l'article par l'éditeur
169 if(creation_mode === false){
170 document.getElementById(id).innerHTML = article.innerHTML;
171 } 23 }
172} 24}
173 25function submitArticle(id, clone = null)
174function deleteArticle(id, page = '') { 26{
175 if (confirm('Voulez-vous vraiment supprimer cet article ?')) 27 if(editors[id]){
28 editors[id].submit(clone);
29 }
30 else if(window.Config.page === "article" && id[0] === 'n'){ // bouton Tout enregistrer (pas d'éditeur)
31 editors[id] = new Editor(id);
32 editors[id].submit();
33 }
34}
35// standalone contraîrement aux autres fonctions ici
36function deleteArticle(id){
37 if(confirm('Voulez-vous vraiment supprimer cet article ?'))
176 { 38 {
177 // Envoyer une requête au serveur pour supprimer l'article 39 // Envoyer une requête au serveur pour supprimer l'article
178 fetch('index.php?action=delete_article', { 40 fetch('index.php?action=delete_article', {
@@ -199,146 +61,337 @@ function deleteArticle(id, page = '') {
199 } 61 }
200} 62}
201 63
202function closeEditor(id, page = '', restore_old = true) 64
65
66class Editor
203{ 67{
204 var creation_mode; 68 constructor(id){
205 var real_id; 69 this.id = id;
206 var article; 70 this.article = document.getElementById(this.id);
207 var parent; 71 this.creation_mode = this.id[0] === 'n' ? true : false;
208 72 //this.isOpen = false;
209 // création ou modification d'un article? 73 this.tiny_instance = null;
210 if(id[0] === 'n'){
211 creation_mode = true;
212 }
213 else{
214 creation_mode = false;
215 }
216 74
217 // Fermer l'éditeur 75 // moche, on ne devrait sortir l'envoi des données avec fetch de Editor.submit
218 tinymce.remove(`#${id}`); 76 if(!this.creation_mode || window.Config.page !== 'article'){
219 delete editors[id]; 77 if(this.creation_mode && window.Config.page !== 'article'){
220 78 this.setArticlePlacement(this.id);
221 if(creation_mode){ 79 }
222 article = document.getElementById(id); 80 else{
223 parent = findParent(article, 'section'); 81 // insérer le contenu de l'article dans l'éditeur
224 } 82 this.article.setAttribute('data-original-content', this.article.innerHTML);
225 else{ 83 }
226 real_id = 'i' + id.slice(1); 84 this.init();
85 }
86 //else // bouton Tout enregistrer, pas d'éditeur
227 } 87 }
228 88
229 // Restaurer le contenu d'origine de l'article 89 setArticlePlacement(id_block){
230 if(restore_old){ 90 const checked_button = document.querySelector('input[name="article_placement-' + id_block + '"]:checked');
231 const originalContent = document.getElementById(id).getAttribute('data-original-content'); 91 if(checked_button){ // vrai clic
232 document.getElementById(id).innerHTML = originalContent; 92 this.placement = checked_button.value;
93 }
94 else{
95 document.getElementById('radio_last-' + id_block).checked = true; // faux clic
96 this.placement = 'last';
97 }
233 } 98 }
99
100 init(){
101 tinymce.init({
102 selector: `#${this.id}`, // avec un # comme dans querySelector
103 language: 'fr_FR', // téléchargement ici: https://www.tiny.cloud/get-tiny/language-packages/
104 language_url: 'js/tinymce-langs/fr_FR.js', // ou installer tweeb/tinymce-i18n avec composer
105 license_key: 'gpl',
106 branding: false,
107 plugins: 'lists link autolink table image media autoresize help',
108 toolbar: 'undo redo newdocument print selectall styles bold italic underline strikethrough fontsizeinput forecolor backcolor fontfamily align numlist bullist outdent indent table link image media help',
109 menubar: false,
110 toolbar_mode: 'wrap',
111 statusbar: false,
112 // les fonctions fléchées permettent de garder le contexte (= this)
113 setup: (editor) => {
114 editor.on('init', () => {
115 this.tiny_instance = editor;
116
117 // boutons "Modifier", "Supprimer", "déplacer vers le haut", "déplacer vers le bas", "Annuler" et "Soumettre"
118 document.getElementById(`cancel-${this.id}`).classList.remove('hidden');
119 document.getElementById(`submit-${this.id}`).classList.remove('hidden');
120 const radio = document.getElementById(`radio-${this.id}`);
121 if(radio){
122 radio.classList.remove('hidden');
123 }
124 if(this.creation_mode){
125 document.getElementById(`new-${this.id}`).classList.add('hidden'); // id = new-new-id_node
126 }
127 else{
128 document.getElementById(`edit-${this.id}`).classList.add('hidden');
129 if(window.Config.page !== 'article'){
130 document.getElementById(`position_up-${this.id}`).classList.add('hidden');
131 document.getElementById(`position_down-${this.id}`).classList.add('hidden');
132 document.getElementById(`delete-${('i' + this.id.slice(1))}`).classList.add('hidden');
133 }
134 }
135 });
136 let skipPastePreProcess = false;
137 editor.on('Paste', function (e){ // déclenchement AVANT PastePreProcess et quelque que soit le contenu collé
138 const clipboardData = (e.clipboardData || e.originalEvent.clipboardData);
139 if(!clipboardData){
140 return;
141 }
142 const items = clipboardData.items;
143 let foundImage = false;
144
145 for(let i = 0; i < items.length; i++){
146 let item = items[i];
147
148 if(item.type.indexOf('image') !== -1){ // test type MIME contenant image
149 foundImage = true;
234 150
235 // boutons: "Nouvel article", Modifier", "Supprimer", "déplacer vers le haut", "déplacer vers le bas", "Annuler" et "Valider" 151 const file = item.getAsFile(); // presse-papier => fichier lisible
236 document.querySelector(`#cancel-${id}`).classList.add('hidden'); 152 const reader = new FileReader();
237 document.querySelector(`#submit-${id}`).classList.add('hidden'); 153
238 if(creation_mode){ 154 reader.onload = function (event){ // fonction exécutée lorsque reader.readAsDataURL(file) est terminée
239 document.querySelector(`#new-${id}`).classList.remove('hidden'); // id = new-new-id_node 155 const base64Data = event.target.result; // données de l'image
156
157 fetch('index.php?action=upload_image_base64', {
158 method: 'POST',
159 headers: { 'Content-Type': 'application/json' },
160 body: JSON.stringify({ image_base64: base64Data })
161 })
162 .then(response => response.json())
163 .then(data => {
164 if(data.location){
165 editor.insertContent('<img src="' + data.location + '">');
166 }
167 })
168 .catch(error => {
169 console.error('Erreur lors de l’upload de l’image base64 :', error);
170 });
171 };
172 reader.readAsDataURL(file); // lecture asynchrone du fichier
173 }
174 }
175
176 if(foundImage){
177 e.preventDefault(); // supprime le collage automatiue
178 skipPastePreProcess = true; // désactiver le PastePreProcess pour ce collage
179 }
180 });
181 editor.on('PastePreProcess', function (e){ // déclenchement au collage AVANT insertion dans l'éditeur
182 const parser = new DOMParser();
183 const doc = parser.parseFromString(e.content, 'text/html');
184 const images = doc.querySelectorAll('img');
185
186 let downloads_in_progress = [];
187
188 images.forEach(img => {
189 if(img.src.startsWith('file://')){ // détection d'images non insérables
190 console.warn('Image locale non insérable dans tinymce :', img.src);
191 img.outerHTML = `<div style="border:1px solid red; padding:10px; margin:5px 0; background-color:#ffe6e6; color:#a94442; font-size:14px;">
192 "Image locale non insérée (vient-elle d'un document LibreOffice ?). Effacez ce message rouge et copiez-collez l'image seule.</div>`;
193 }
194 else if(img.src.startsWith('http')){ // détection d'images web
195 const promise = fetch('index.php?action=upload_image_url', { // promesse d'un fichier téléchargeable sur le serveur
196 method: 'POST',
197 headers: { 'Content-Type': 'application/json' },
198 body: JSON.stringify({ image_url: img.src })
199 })
200 .then(response => response.json())
201 .then(data => {
202 if(data.location){
203 img.src = data.location; // remplacer l'image par celle du serveur
204 }
205 })
206 .catch(error => {
207 console.error('Erreur lors de l’upload de l’image distante:', error);
208 });
209
210 downloads_in_progress.push(promise);
211 }
212 });
213
214 // une image web ou plus: différer l'insertion dans l'éditeur le temps que le serveur télécharge les images
215 if(downloads_in_progress.length > 0){
216 e.preventDefault();
217
218 Promise.all(downloads_in_progress).then(() => {
219 e.content = doc.body.innerHTML; // remplacement du HTML dans l'éditeur par la copie modifiée (doc)
220 editor.insertContent(e.content);
221 });
222 }
223 else{
224 e.content = doc.body.innerHTML; // remplacement du HTML dans l'éditeur par la copie modifiée (doc)
225 }
226 }); // fin editor.on('PastePreProcess'...
227 },
228 // upload d'image natif de tinymce avec le bouton "Insérer une image"
229 images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {
230 const formData = new FormData();
231 formData.append("file", blobInfo.blob());
232
233 fetch("index.php?action=upload_image_tinymce", {
234 method: "POST",
235 body: formData
236 })
237 .then(response => response.json())
238 .then(data => {
239 if(data.location) {
240 resolve(data.location);
241 }
242 else {
243 reject("Erreur: Chemin d'image invalide");
244 }
245 })
246 .catch(error => {
247 reject("Erreur lors de l'upload");
248 });
249 }),
250 image_caption: true
251 });
240 } 252 }
241 else{ 253
242 document.querySelector(`#edit-${id}`).classList.remove('hidden'); 254 close(restore_old = true){
243 if(page != 'article'){ 255 tinymce.remove(`#${this.id}`); // avec un # comme dans querySelector
244 document.querySelector(`#position_up-${id}`).classList.remove('hidden'); 256 delete editors[this.id];
245 document.querySelector(`#position_down-${id}`).classList.remove('hidden'); 257
246 document.querySelector(`#delete-${id}`).classList.remove('hidden'); 258 // Restaurer le contenu d'origine de l'article
259 if(restore_old){
260 const original_content = document.getElementById(this.id).getAttribute('data-original-content');
261 document.getElementById(this.id).innerHTML = original_content;
247 } 262 }
248 }
249}
250 263
251function submitArticle(id, page = '', clone = null) 264 // boutons: "Nouvel article", Modifier", "Supprimer", "déplacer vers le haut", "déplacer vers le bas", "Annuler" et "Valider"
252{ 265 document.getElementById(`cancel-${this.id}`).classList.add('hidden');
253 var editor; 266 document.getElementById(`submit-${this.id}`).classList.add('hidden');
254 var content; 267
255 const params = new URL(document.location).searchParams; // "search" = ? et paramètres, searchParams = objet avec des getters 268 const radio = document.getElementById(`radio-${this.id}`);
256 269 if(radio){
257 // clic sur "Tout enregistrer" 270 document.querySelector('input[name="article_placement-' + this.id + '"]:checked').checked = false; // décoche l'option "en mémoire"
258 if(id[0] === 'n' && page === 'article'){ 271 radio.classList.add('hidden');
259 const prefixes = ['t', 'p', 'i', 'd']; 272 }
260 const allElemsWithId = document.querySelectorAll('.data'); 273
261 content = {}; 274 if(this.creation_mode){
262 var id_from_builder; 275 document.getElementById(`new-${this.id}`).classList.remove('hidden'); // id = new-new-id_node
263 276 }
264 allElemsWithId.forEach(element => { 277 else{
265 const first_letter = element.id.charAt(0).toLowerCase(); 278 document.getElementById(`edit-${this.id}`).classList.remove('hidden');
266 if(prefixes.includes(first_letter)){ 279 if(window.Config.page !== 'article'){
267 content[first_letter] = element.innerHTML; 280 document.getElementById(`position_up-${this.id}`).classList.remove('hidden');
268 if(first_letter === 'i'){ 281 document.getElementById(`position_down-${this.id}`).classList.remove('hidden');
269 id_from_builder = element.id; 282 document.getElementById(`delete-${this.id}`).classList.remove('hidden');
270 }
271 } 283 }
272 })
273 content['d'] = dateToISO(content['d']);
274 }
275 // champs à remplir des nouvelles "news"
276 else if(page === 'article' && params != null && params.get("id")[0] === 'n'){
277 closeEditor(id, page, false);
278 //makeNewArticleButtons(id, id, clone);
279 return;
280 }
281 // dans les autres cas, on doit pouvoir récupérer l'éditeur
282 else{
283 // l'éditeur correspond à l'article OU page "article" à un élément: titre, aperçu, article
284 editor = editors[id];
285 if(!editor) {
286 console.error('Éditeur non trouvé pour l\'article:', id);
287 return;
288 } 284 }
289 content = editor.getContent();
290 } 285 }
291 286
292 // Envoi AJAX au serveur 287 submit(clone = null){
293 fetch('index.php?action=editor_submit', { 288 //var editor;
294 method: 'POST', 289 var content;
295 headers: { 290 const params = new URL(document.location).searchParams; // "search" = ? et paramètres, searchParams = objet avec des getters
296 'Content-Type': 'application/json' 291 // à comparer avec: new URLSearchParams(window.location.search);
297 }, 292 // c'est pareil ou pas?
298 body: JSON.stringify({id: id, content: content}) 293
299 }) 294 // clic sur "Tout enregistrer" (ne devrait pas se situer dans Editor)
300 .then(response => response.json()) 295 if(this.creation_mode && window.Config.page === 'article'){
301 .then(data => { 296 const prefixes = ['t', 'p', 'i', 'd'];
302 if(data.success) { 297 const allElemsWithId = document.querySelectorAll('.data');
303 //console.log(data.article_id); 298 content = {};
304 if(id[0] === 'n' && page === 'article'){ 299 var id_from_builder;
305 console.log('données envoyées au serveur avec succès.'); 300
306 301 allElemsWithId.forEach(element => {
307 // redirection page de l'article 302 const first_letter = element.id.charAt(0).toLowerCase();
308 window.setTimeout(function(){ 303 if(prefixes.includes(first_letter)){
309 const url_params = new URLSearchParams(window.location.search); // le "$_GET" de javascript 304 content[first_letter] = element.innerHTML;
310 location.href = "index.php?page=article&id=" + data.article_id + "&from=" + url_params.get('from'); 305 if(first_letter === 'i'){
311 }, 0); 306 id_from_builder = element.id;
312 } 307 }
313 else{
314 // Fermer l'éditeur et mettre à jour le contenu de l'article
315 closeEditor(id, page, false);
316 if(id[0] === 'n'){
317 makeNewArticleButtons(id, data.article_id, clone);
318 } 308 }
319 } 309 })
310 content['d'] = dateToISO(content['d']);
320 } 311 }
312 // champs à remplir des nouvelles "news"
313 else if(window.Config.page === 'article' && params != null && params.get("id")[0] === 'n'){
314 this.close(false);
315 return;
316 }
317 // dans les autres cas, on doit pouvoir récupérer l'éditeur
321 else{ 318 else{
322 alert('Erreur lors de la sauvegarde de l\'article.'); 319 // l'éditeur correspond à l'article OU si page = "article" à un élément: titre, aperçu, article
320 //editor = editors[id];
321 if(!this.tiny_instance){
322 console.error("Éditeur non trouvé pour l'article:", this.id);
323 return;
324 }
325 content = this.tiny_instance.getContent();
323 } 326 }
324 }) 327
325 .catch(error => { 328 let fetch_params = {id: this.id, content: content};
326 console.error('Erreur:', error); 329 if(this.placement){
327 }); 330 fetch_params['placement'] = this.placement;
331 }
332
333 // Envoi AJAX au serveur
334 fetch('index.php?action=editor_submit', {
335 method: 'POST',
336 headers: {'Content-Type': 'application/json'},
337 body: JSON.stringify(fetch_params)
338 })
339 .then(response => response.json())
340 .then(data => {
341 if(data.success)
342 {
343 if(this.creation_mode && window.Config.page === 'article'){
344 console.log('données envoyées au serveur avec succès.');
345
346 // redirection page de l'article
347 window.setTimeout(function(){
348 const url_params = new URLSearchParams(window.location.search); // le "$_GET" de javascript
349 location.href = "index.php?page=article&id=" + data.article_id + "&from=" + url_params.get('from');
350 }, 0);
351 }
352 else{
353 // Fermer l'éditeur et mettre à jour le contenu de l'article
354 this.close(false);
355 if(this.creation_mode){
356 makeNewArticleButtons(this.id, data.article_id, clone, this.placement);
357 }
358 }
359 }
360 else{
361 alert('Erreur lors de la sauvegarde de l\'article.');
362 }
363 })
364 .catch(error => {
365 console.error('Erreur:', error);
366 });
367 }
368
369 //reopen(){}
370
371 /*destroy(){
372 this.close();
373 delete editors[this.id];
374 console.log(`Editor ${this.id} détruit.`);
375 }*/
328} 376}
329 377
330function makeNewArticleButtons(id, article_id, clone) 378
379
380
381
382// restera ici jusqu'à ce que la gestion des balises soient faite ailleurs
383function makeNewArticleButtons(id, article_id, clone, placement = 'last')
331{ 384{
332 var share_btn = document.querySelector(`.share.hidden`); // combinaison de deux classes 385 var share_btn = document.querySelector(`.share.hidden`); // combinaison de deux classes
333 var new_btn = document.querySelector(`#new-${id}`); 386 var new_btn = document.getElementById(`new-${id}`);
334 var edit_btn = document.querySelector(`#edit-${id}`); 387 var edit_btn = document.getElementById(`edit-${id}`);
335 var pos_up_btn = document.querySelector(`#position_up-${id}`); 388 var pos_up_btn = document.getElementById(`position_up-${id}`);
336 var pos_down_btn = document.querySelector(`#position_down-${id}`); 389 var pos_down_btn = document.getElementById(`position_down-${id}`);
337 var delete_btn = document.querySelector(`#delete-${id}`); 390 var delete_btn = document.getElementById(`delete-${id}`);
338 var cancel_btn = document.querySelector(`#cancel-${id}`); 391 var cancel_btn = document.getElementById(`cancel-${id}`);
339 var submit_btn = document.querySelector(`#submit-${id}`); 392 var submit_btn = document.getElementById(`submit-${id}`);
340 393
341 share_btn.classList.remove('hidden') 394 share_btn.classList.remove('hidden');
342 new_btn.classList.add('hidden'); 395 new_btn.classList.add('hidden');
343 edit_btn.classList.remove('hidden'); 396 edit_btn.classList.remove('hidden');
344 pos_up_btn.classList.remove('hidden'); 397 pos_up_btn.classList.remove('hidden');
@@ -348,7 +401,7 @@ function makeNewArticleButtons(id, article_id, clone)
348 //submit_btn.classList.add('hidden'); 401 //submit_btn.classList.add('hidden');
349 402
350 var article = document.getElementById(id); 403 var article = document.getElementById(id);
351 var parent = findParent(article, 'article'); 404 var article_elem_parent = findParentByTagName(article, 'article');
352 405
353 share_btn.setAttribute('onclick', "copyInClipBoard('" + window.location.href + article_id + "')"); // # de l'ancre ajouté au clic sur le lien ouvrant l'éditeur 406 share_btn.setAttribute('onclick', "copyInClipBoard('" + window.location.href + article_id + "')"); // # de l'ancre ajouté au clic sur le lien ouvrant l'éditeur
354 article.id = article_id; 407 article.id = article_id;
@@ -364,8 +417,16 @@ function makeNewArticleButtons(id, article_id, clone)
364 cancel_btn.querySelector('button').setAttribute('onclick', "closeEditor('" + article_id + "')"); 417 cancel_btn.querySelector('button').setAttribute('onclick', "closeEditor('" + article_id + "')");
365 submit_btn.id = 'submit-' + article_id; 418 submit_btn.id = 'submit-' + article_id;
366 submit_btn.querySelector('button').setAttribute('onclick', "submitArticle('" + article_id + "')"); 419 submit_btn.querySelector('button').setAttribute('onclick', "submitArticle('" + article_id + "')");
420
421 var section_child = article_elem_parent.parentNode.querySelector('.section_child'); // renommer section_child
422
423 // parentNode vise la balise section
424 article_elem_parent.parentNode.replaceChild(clone.cloneNode(true), article_elem_parent); // clone du squelette pour le garder intact
367 425
368 var next_div = parent.nextElementSibling.nextElementSibling; 426 if(placement === 'first'){
369 parent.parentNode.replaceChild(clone.cloneNode(true), parent); // clone du squelette pour le garder intact 427 section_child.insertBefore(article_elem_parent, section_child.firstChild);
370 next_div.appendChild(parent); 428 }
429 else{ // = 'last'
430 section_child.appendChild(article_elem_parent);
431 }
371} \ No newline at end of file 432} \ No newline at end of file
diff --git a/src/controller/ArticleController.php b/src/controller/ArticleController.php
index b8af290..5cebad6 100644
--- a/src/controller/ArticleController.php
+++ b/src/controller/ArticleController.php
@@ -16,15 +16,15 @@ class ArticleController
16 { 16 {
17 $id = $json['id']; 17 $id = $json['id'];
18 $director = new Director($entityManager); 18 $director = new Director($entityManager);
19 $content = $json['content'];
19 20
20 // cas d'une nouvelle "news" 21 // nettoyage
21 if(is_array($json['content'])){ 22 if(is_array($content)){ // cas d'une nouvelle "news"
22 foreach($json['content'] as $one_input){ 23 foreach($content as $one_input){
23 $one_input = Security::secureHTML($one_input); 24 $one_input = Security::secureHTML($one_input);
24 } 25 }
25 $content = $json['content'];
26 } 26 }
27 else{ 27 else{ // autres cas
28 $content = Security::secureHTML($json['content']); 28 $content = Security::secureHTML($json['content']);
29 } 29 }
30 30
@@ -39,21 +39,25 @@ class ArticleController
39 $director->makeSectionNode(); 39 $director->makeSectionNode();
40 $node = $director->getNode(); // = <section> 40 $node = $director->getNode(); // = <section>
41 41
42 if(is_array($content)){ 42 if(is_array($content)){ // cas d'une nouvelle "news"
43 $date = new \DateTime($content['d']); 43 $date = new \DateTime($content['d']);
44 $article = new Article($content['i'], $date, $content['t'], $content['p']); 44 $article = new Article($content['i'], $date, $content['t'], $content['p']);
45 $article_node = new Node('new', 'i' . (string)$date->getTimestamp(), [], count($node->getChildren()) + 1, $node, $node->getPage(), $article); 45 $article_node = new Node('new', 'i' . (string)$date->getTimestamp(), [], count($node->getChildren()) + 1, $node, $node->getPage(), $article);
46
47 // id_node tout juste généré
48 //$article_node->getId();
49 } 46 }
50 else{ 47 else{ // autres cas
51 $timestamp = time(); 48 $timestamp = time();
52 $date = new \DateTime; 49 $date = new \DateTime;
53 $date->setTimestamp($timestamp); 50 $date->setTimestamp($timestamp);
54 51
55 $article = new Article($content, $date); // le "current" timestamp est obtenu par la BDD 52 $article = new Article($content, $date); // le "current" timestamp est obtenu par la BDD
56 $article_node = new Node('post', 'i' . (string)$timestamp, [], count($node->getChildren()) + 1, $node, $node->getPage(), $article); 53
54 $placement = $json['placement'] === 'first' ? 0 : count($node->getChildren()) + 1; //
55 $article_node = new Node('post', 'i' . (string)$timestamp, [], $placement, $node, $node->getPage(), $article);
56
57 if($json['placement'] === 'first'){
58 $node->addChild($article_node);
59 $node->reindexPositions(); // régénère les positions (0 devient 1, 1 devient 2...)
60 }
57 } 61 }
58 62
59 $entityManager->persist($article_node); 63 $entityManager->persist($article_node);
diff --git a/src/controller/PageManagementController.php b/src/controller/PageManagementController.php
index 4528810..75967eb 100644
--- a/src/controller/PageManagementController.php
+++ b/src/controller/PageManagementController.php
@@ -286,7 +286,12 @@ class PageManagementController
286 $director->getNode()->getNodeData()->setPresentation($presentation); 286 $director->getNode()->getNodeData()->setPresentation($presentation);
287 287
288 $entityManager->flush(); 288 $entityManager->flush();
289 echo json_encode(['success' => true, 'presentation' => $json['presentation'], 'cols_min_width' => $director->getNode()->getNodeData()->getColsMinWidth()]); 289
290 $response_data = ['success' => true, 'presentation' => $json['presentation']];
291 if($json['presentation'] === 'grid'){
292 $response_data['cols_min_width'] = $director->getNode()->getNodeData()->getColsMinWidth();
293 }
294 echo json_encode($response_data);
290 } 295 }
291 else{ 296 else{
292 echo json_encode(['success' => false]); 297 echo json_encode(['success' => false]);
diff --git a/src/view/MainBuilder.php b/src/view/MainBuilder.php
index fc80cd1..b07fa81 100644
--- a/src/view/MainBuilder.php
+++ b/src/view/MainBuilder.php
@@ -85,10 +85,10 @@ class MainBuilder extends AbstractBuilder
85 <button onclick="renamePageBloc(' . $child_node->getId() . ')">Renommer</button> 85 <button onclick="renamePageBloc(' . $child_node->getId() . ')">Renommer</button>
86 </p>'. "\n"; 86 </p>'. "\n";
87 // déplacement d'un bloc 87 // déplacement d'un bloc
88 $bloc_edit .= '<div style="display: flex; flex-wrap: wrap;"> 88 $bloc_edit .= '<div>
89 <p> 89 <p>
90 <img class="action_icon" onclick="switchBlocsPositions(' . $child_node->getId() . ', \'up\', \'' . CURRENT_PAGE . '\')" src="assets/arrow-up.svg"> 90 <img class="action_icon" onclick="switchBlocsPositions(' . $child_node->getId() . ', \'up\')" src="assets/arrow-up.svg">
91 <img class="action_icon" onclick="switchBlocsPositions(' . $child_node->getId() . ', \'down\', \'' . CURRENT_PAGE . '\')" src="assets/arrow-down.svg"> 91 <img class="action_icon" onclick="switchBlocsPositions(' . $child_node->getId() . ', \'down\')" src="assets/arrow-down.svg">
92 </p>' . "\n"; 92 </p>' . "\n";
93 // suppression d'un bloc 93 // suppression d'un bloc
94 $bloc_edit .= '<form method="post" action="' . new URL(['page' => CURRENT_PAGE]) . '"> 94 $bloc_edit .= '<form method="post" action="' . new URL(['page' => CURRENT_PAGE]) . '">
diff --git a/src/view/NewBuilder.php b/src/view/NewBuilder.php
index 2d66238..332d92b 100644
--- a/src/view/NewBuilder.php
+++ b/src/view/NewBuilder.php
@@ -69,27 +69,27 @@ class NewBuilder extends AbstractBuilder
69 if($_SESSION['admin']) 69 if($_SESSION['admin'])
70 { 70 {
71 if(Director::$page_path->getLast()->getEndOfPath() === 'article'){ 71 if(Director::$page_path->getLast()->getEndOfPath() === 'article'){
72 $title_js = 'onclick="openEditor(\'' . $id_title . '\', \'article\')"'; 72 $title_js = 'onclick="openEditor(\'' . $id_title . '\')"';
73 $modify_title = '<p id="edit-' . $id_title . '"><button ' . $title_js . '><img class="action_icon" src="assets/edit.svg">Titre</button></p>' . "\n"; 73 $modify_title = '<p id="edit-' . $id_title . '"><button ' . $title_js . '><img class="action_icon" src="assets/edit.svg">Titre</button></p>' . "\n";
74 $close_js_title = 'onclick="closeEditor(\'' . $id_title . '\', \'article\', \'preview\')"'; 74 $close_js_title = 'onclick="closeEditor(\'' . $id_title . '\')"';
75 $close_editor_title = '<p id="cancel-' . $id_title . '" class="hidden"><button ' . $close_js_title . '>Annuler</button></p>'; 75 $close_editor_title = '<p id="cancel-' . $id_title . '" class="hidden"><button ' . $close_js_title . '>Annuler</button></p>';
76 $submit_js_title = 'onclick="submitArticle(\'' . $id_title . '\', \'article\')"'; 76 $submit_js_title = 'onclick="submitArticle(\'' . $id_title . '\')"';
77 $submit_title = '<p id="submit-' . $id_title . '" class="hidden"><button ' . $submit_js_title . '>Valider</button></p>'; 77 $submit_title = '<p id="submit-' . $id_title . '" class="hidden"><button ' . $submit_js_title . '>Valider</button></p>';
78 $title_buttons = '<div class="button_zone">' . $modify_title . $close_editor_title . $submit_title . '</div>'; 78 $title_buttons = '<div class="button_zone">' . $modify_title . $close_editor_title . $submit_title . '</div>';
79 79
80 $preview_js = 'onclick="openEditor(\'' . $id_preview . '\', \'article\')"'; 80 $preview_js = 'onclick="openEditor(\'' . $id_preview . '\')"';
81 $modify_preview = '<p id="edit-' . $id_preview . '"><button ' . $preview_js . '><img class="action_icon" src="assets/edit.svg">Aperçu</button></a></p>' . "\n"; 81 $modify_preview = '<p id="edit-' . $id_preview . '"><button ' . $preview_js . '><img class="action_icon" src="assets/edit.svg">Aperçu</button></a></p>' . "\n";
82 $close_js_preview = 'onclick="closeEditor(\'' . $id_preview . '\', \'article\', \'preview\')"'; 82 $close_js_preview = 'onclick="closeEditor(\'' . $id_preview . '\')"';
83 $close_editor_preview = '<p id="cancel-' . $id_preview . '" class="hidden"><button ' . $close_js_preview . '>Annuler</button></p>'; 83 $close_editor_preview = '<p id="cancel-' . $id_preview . '" class="hidden"><button ' . $close_js_preview . '>Annuler</button></p>';
84 $submit_js_preview = 'onclick="submitArticle(\'' . $id_preview . '\', \'article\')"'; 84 $submit_js_preview = 'onclick="submitArticle(\'' . $id_preview . '\')"';
85 $submit_preview = '<p id="submit-' . $id_preview . '" class="hidden"><button ' . $submit_js_preview . '>Valider</button></p>'; 85 $submit_preview = '<p id="submit-' . $id_preview . '" class="hidden"><button ' . $submit_js_preview . '>Valider</button></p>';
86 $preview_buttons = '<div class="button_zone">' . $modify_preview . $close_editor_preview . $submit_preview . '</div>'; 86 $preview_buttons = '<div class="button_zone">' . $modify_preview . $close_editor_preview . $submit_preview . '</div>';
87 87
88 $article_js = 'onclick="openEditor(\'' . $id . '\', \'article\')"'; 88 $article_js = 'onclick="openEditor(\'' . $id . '\')"';
89 $modify_article = '<p id="edit-' . $id . '"><button ' . $article_js . '><img class="action_icon" src="assets/edit.svg">Article</button></p>' . "\n"; 89 $modify_article = '<p id="edit-' . $id . '"><button ' . $article_js . '><img class="action_icon" src="assets/edit.svg">Article</button></p>' . "\n";
90 $close_js_article = 'onclick="closeEditor(\'' . $id . '\', \'article\')"'; 90 $close_js_article = 'onclick="closeEditor(\'' . $id . '\')"';
91 $close_editor_article = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js_article . '>Annuler</button></p>'; 91 $close_editor_article = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js_article . '>Annuler</button></p>';
92 $submit_js_article = 'onclick="submitArticle(\'' . $id . '\', \'article\')"'; 92 $submit_js_article = 'onclick="submitArticle(\'' . $id . '\')"';
93 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js_article . '>Valider</button></p>'; 93 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js_article . '>Valider</button></p>';
94 $article_buttons = '<div class="button_zone">' . $modify_article . $close_editor_article . $submit_article . '</div>'; 94 $article_buttons = '<div class="button_zone">' . $modify_article . $close_editor_article . $submit_article . '</div>';
95 95
@@ -105,7 +105,7 @@ class NewBuilder extends AbstractBuilder
105 if(self::$new_article_mode){ 105 if(self::$new_article_mode){
106 $delete_article = ''; 106 $delete_article = '';
107 // valider la création d'un nouvel article 107 // valider la création d'un nouvel article
108 $submit_js = 'onclick="submitArticle(\'' . $_GET['id'] . '\', \'' . Director::$page_path->getLast()->getEndOfPath() . '\')"'; 108 $submit_js = 'onclick="submitArticle(\'' . $_GET['id'] . '\')"';
109 $submit_article = '<p id="save-' . $id . '"><button ' . $submit_js . '><img class="action_icon" src="assets/edit.svg"><span class="delete_button">Tout<br>enregistrer</span></button></p>' . "\n"; 109 $submit_article = '<p id="save-' . $id . '"><button ' . $submit_js . '><img class="action_icon" src="assets/edit.svg"><span class="delete_button">Tout<br>enregistrer</span></button></p>' . "\n";
110 } 110 }
111 // mode article existant 111 // mode article existant
diff --git a/src/view/NewsBlockBuilder.php b/src/view/NewsBlockBuilder.php
index 861949a..4716c63 100644
--- a/src/view/NewsBlockBuilder.php
+++ b/src/view/NewsBlockBuilder.php
@@ -53,7 +53,7 @@ class NewsBlockBuilder extends AbstractBuilder
53 $close_js = 'onclick="closeEditor(\'' . $id . '\')"'; 53 $close_js = 'onclick="closeEditor(\'' . $id . '\')"';
54 $close_editor = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js . '>Annuler</button></p>'; 54 $close_editor = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js . '>Annuler</button></p>';
55 55
56 $submit_js = 'onclick="submitArticle(\'' . $id . '\', \'\', clone' . $this->id_node . ')"'; 56 $submit_js = 'onclick="submitArticle(\'' . $id . '\', clone' . $this->id_node . ')"';
57 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js . '>Valider</button></p>'; 57 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js . '>Valider</button></p>';
58 58
59 $html = ''; 59 $html = '';
diff --git a/src/view/PostBlockBuilder.php b/src/view/PostBlockBuilder.php
index 8ad0498..017e78e 100644
--- a/src/view/PostBlockBuilder.php
+++ b/src/view/PostBlockBuilder.php
@@ -29,6 +29,7 @@ class PostBlockBuilder extends AbstractBuilder
29 } 29 }
30 30
31 // ajouter un article 31 // ajouter un article
32 // => fait un peu double emploi avec PostBuilder
32 $new_article = ''; 33 $new_article = '';
33 if($_SESSION['admin']) 34 if($_SESSION['admin'])
34 { 35 {
@@ -52,12 +53,20 @@ class PostBlockBuilder extends AbstractBuilder
52 53
53 $close_js = 'onclick="closeEditor(\'' . $id . '\')"'; 54 $close_js = 'onclick="closeEditor(\'' . $id . '\')"';
54 $close_editor = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js . '>Annuler</button></p>'; 55 $close_editor = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js . '>Annuler</button></p>';
56
57 $position = '<div id="radio-' . $id . '" class="hidden" style="margin: 5px; font-size: 90%;">
58 Placement:<br>
59 <input type="radio" id="radio_first-' . $id . '" name="article_placement-' . $id . '" value="first" onchange="setArticlePlacement(\'' . $id . '\')">
60 <label for="radio_start">en premier</label><br>
61 <input type="radio" id="radio_last-' . $id . '" name="article_placement-' . $id . '" value="last" onchange="setArticlePlacement(\'' . $id . '\')">
62 <label for="radio_end">en dernier</label>
63 </div>';
55 64
56 $submit_js = 'onclick="submitArticle(\'' . $id . '\', \'\', clone' . $this->id_node . ')"'; 65 $submit_js = 'onclick="submitArticle(\'' . $id . '\', clone' . $this->id_node . ')"';
57 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js . '>Valider</button></p>'; 66 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js . '>Valider</button></p>';
58 67
59 $html = ''; 68 $html = '';
60 $admin_buttons = $new_button . $modify_article . $up_button . $down_button . $delete_article . $close_editor . $submit_article; 69 $admin_buttons = $new_button . $modify_article . $up_button . $down_button . $delete_article . $close_editor . $submit_article . $position;
61 70
62 // squelette d'un nouvel article 71 // squelette d'un nouvel article
63 ob_start(); 72 ob_start();
diff --git a/src/view/PostBuilder.php b/src/view/PostBuilder.php
index 03a6990..ae5cc9b 100644
--- a/src/view/PostBuilder.php
+++ b/src/view/PostBuilder.php
@@ -30,6 +30,7 @@ class PostBuilder extends AbstractBuilder
30 $share_button = '<p class="share" ' . $share_js . '><img class="action_icon" src="assets/share.svg"></p>' . "\n"; 30 $share_button = '<p class="share" ' . $share_js . '><img class="action_icon" src="assets/share.svg"></p>' . "\n";
31 31
32 // modifier un article 32 // modifier un article
33 // => fait un peu double emploi avec PostBlockBuilder
33 $admin_buttons = ''; 34 $admin_buttons = '';
34 if($_SESSION['admin']) 35 if($_SESSION['admin'])
35 { 36 {
diff --git a/src/view/templates/head.php b/src/view/templates/head.php
index b74335e..83a0e7c 100644
--- a/src/view/templates/head.php
+++ b/src/view/templates/head.php
@@ -7,6 +7,7 @@
7 <link rel="icon" type="image/png" href="<?= $favicon ?>" alt="<?= $alt ?>"> 7 <link rel="icon" type="image/png" href="<?= $favicon ?>" alt="<?= $alt ?>">
8 <meta name="description" content="<?= $description ?>"> 8 <meta name="description" content="<?= $description ?>">
9 <meta name="viewport" content="width=device-width"> 9 <meta name="viewport" content="width=device-width">
10 <script>window.Config = {page: "<?= CURRENT_PAGE ?>"};</script>
10 <?= $css ?> 11 <?= $css ?>
11 <?= $js ?> 12 <?= $js ?>
12 </head> \ No newline at end of file 13 </head> \ No newline at end of file