diff options
author | polo <ordipolo@gmx.fr> | 2025-09-05 11:27:37 +0200 |
---|---|---|
committer | polo <ordipolo@gmx.fr> | 2025-09-05 11:27:37 +0200 |
commit | 46b455a0d3e96099e78975f53a25365a2ca9dcb4 (patch) | |
tree | 54f4616a103410d2bc77827b738f592806dbff67 | |
parent | f477314613beb26b0ce4c61ec0b1900df1de1cb1 (diff) | |
download | cms-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.css | 6 | ||||
-rw-r--r-- | public/css/modif_page.css | 13 | ||||
-rw-r--r-- | public/js/main.js | 17 | ||||
-rw-r--r-- | public/js/menu.js | 87 | ||||
-rw-r--r-- | public/js/modif_page.js | 9 | ||||
-rw-r--r-- | public/js/new_page.js | 5 | ||||
-rw-r--r-- | public/js/tinymce.js | 647 | ||||
-rw-r--r-- | src/controller/ArticleController.php | 26 | ||||
-rw-r--r-- | src/controller/PageManagementController.php | 7 | ||||
-rw-r--r-- | src/view/MainBuilder.php | 6 | ||||
-rw-r--r-- | src/view/NewBuilder.php | 20 | ||||
-rw-r--r-- | src/view/NewsBlockBuilder.php | 2 | ||||
-rw-r--r-- | src/view/PostBlockBuilder.php | 13 | ||||
-rw-r--r-- | src/view/PostBuilder.php | 1 | ||||
-rw-r--r-- | src/view/templates/head.php | 1 |
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 @@ | |||
1 | function newPassword(page, id = ''){ | 1 | function 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 |
79 | function switchPositions(article_id, direction) | 81 | function 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 | ||
235 | function findParent(element, tag_name){ | 237 | function 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 | ||
155 | function switchMenuPositions(page_id, direction) | 68 | function 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 |
4 | function makePageNamePath(){ | 4 | function 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 | ||
133 | function switchBlocsPositions(bloc_id, direction, current_page) { | 130 | function 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 |
4 | function makePageNamePath(){ | 4 | function 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 @@ | |||
1 | let 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 | ||
3 | function 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'){ | 7 | let editors = {}; |
10 | creation_mode = true; | 8 | function 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); | 15 | function 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({ | 20 | function 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 | 25 | function submitArticle(id, clone = null) | |
174 | function 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 | ||
36 | function 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 | ||
202 | function closeEditor(id, page = '', restore_old = true) | 64 | |
65 | |||
66 | class 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 | ||
251 | function 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 | ||
330 | function makeNewArticleButtons(id, article_id, clone) | 378 | |
379 | |||
380 | |||
381 | |||
382 | // restera ici jusqu'à ce que la gestion des balises soient faite ailleurs | ||
383 | function 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 |